001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.data.osm;
003
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.List;
007import java.util.function.Consumer;
008import java.util.stream.Collectors;
009
010import org.openstreetmap.josm.tools.JosmRuntimeException;
011
012/**
013 * Stores primitives in quad buckets. This can be used to hold a collection of primitives, e.g. in a {@link DataSet}
014 *
015 * This class does not do any synchronization.
016 * @author Michael Zangl
017 * @param <N> type representing OSM nodes
018 * @param <W> type representing OSM ways
019 * @param <R> type representing OSM relations
020 * @since 12048
021 */
022public class QuadBucketPrimitiveStore<N extends INode, W extends IWay<N>, R extends IRelation<?>> {
023    /**
024     * All nodes goes here, even when included in other data (ways etc). This enables the instant
025     * conversion of the whole DataSet by iterating over this data structure.
026     */
027    private final QuadBuckets<N> nodes = new QuadBuckets<>();
028
029    /**
030     * All ways (Streets etc.) in the DataSet.
031     *
032     * The way nodes are stored only in the way list.
033     */
034    private final QuadBuckets<W> ways = new QuadBuckets<>();
035
036    /**
037     * All relations/relationships
038     */
039    private final Collection<R> relations = new ArrayList<>();
040
041    /**
042     * Searches for nodes in the given bounding box.
043     * @param bbox the bounding box
044     * @return List of nodes in the given bbox. Can be empty but not null
045     */
046    public List<N> searchNodes(BBox bbox) {
047        return nodes.search(bbox);
048    }
049
050    /**
051     * Determines if the given node can be retrieved in the store through its bounding box. Useful for dataset consistency test.
052     * @param n The node to search
053     * @return {@code true} if {@code n} can be retrieved in this store, {@code false} otherwise
054     */
055    public boolean containsNode(N n) {
056        return nodes.contains(n);
057    }
058
059    /**
060     * Searches for ways in the given bounding box.
061     * @param bbox the bounding box
062     * @return List of ways in the given bbox. Can be empty but not null
063     */
064    public List<W> searchWays(BBox bbox) {
065        return ways.search(bbox);
066    }
067
068    /**
069     * Determines if the given way can be retrieved in the store through its bounding box. Useful for dataset consistency test.
070     * @param w The way to search
071     * @return {@code true} if {@code w} can be retrieved in this store, {@code false} otherwise
072     */
073    public boolean containsWay(W w) {
074        return ways.contains(w);
075    }
076
077    /**
078     * Searches for relations in the given bounding box.
079     * @param bbox the bounding box
080     * @return List of relations in the given bbox. Can be empty but not null
081     */
082    public List<R> searchRelations(BBox bbox) {
083        // QuadBuckets might be useful here (don't forget to do reindexing after some of rm is changed)
084        return relations.stream()
085                .filter(r -> r.getBBox().intersects(bbox))
086                .collect(Collectors.toList());
087    }
088
089    /**
090     * Determines if the given relation can be retrieved in the store through its bounding box. Useful for dataset consistency test.
091     * @param r The relation to search
092     * @return {@code true} if {@code r} can be retrieved in this store, {@code false} otherwise
093     */
094    public boolean containsRelation(R r) {
095        return relations.contains(r);
096    }
097
098    /**
099     * Adds a primitive to this quad bucket store
100     *
101     * @param primitive the primitive.
102     */
103    @SuppressWarnings("unchecked")
104    public void addPrimitive(IPrimitive primitive) {
105        boolean success = false;
106        if (primitive instanceof INode) {
107            success = nodes.add((N) primitive);
108        } else if (primitive instanceof IWay) {
109            success = ways.add((W) primitive);
110        } else if (primitive instanceof IRelation) {
111            success = relations.add((R) primitive);
112        }
113        if (!success) {
114            throw new JosmRuntimeException("failed to add primitive: "+primitive);
115        }
116    }
117
118    protected void removePrimitive(IPrimitive primitive) {
119        boolean success = false;
120        if (primitive instanceof INode) {
121            success = nodes.remove(primitive);
122        } else if (primitive instanceof IWay) {
123            success = ways.remove(primitive);
124        } else if (primitive instanceof IRelation) {
125            success = relations.remove(primitive);
126        }
127        if (!success) {
128            throw new JosmRuntimeException("failed to remove primitive: "+primitive);
129        }
130    }
131
132    /**
133     * Re-index the node after it's position was changed.
134     * @param node The node to re-index
135     * @param nUpdater update node position
136     * @param wUpdater update way position
137     * @param rUpdater update relation position
138     */
139    @SuppressWarnings("unchecked")
140    protected void reindexNode(N node, Consumer<N> nUpdater, Consumer<W> wUpdater, Consumer<R> rUpdater) {
141        if (!nodes.remove(node))
142            throw new JosmRuntimeException("Reindexing node failed to remove");
143        nUpdater.accept(node);
144        if (!nodes.add(node))
145            throw new JosmRuntimeException("Reindexing node failed to add");
146        for (IPrimitive primitive: node.getReferrers()) {
147            if (primitive instanceof IWay) {
148                reindexWay((W) primitive, wUpdater, rUpdater);
149            } else {
150                reindexRelation((R) primitive, rUpdater);
151            }
152        }
153    }
154
155    /**
156     * Re-index the way after it's position was changed.
157     * @param way The way to re-index
158     * @param wUpdater update way position
159     * @param rUpdater update relation position
160     */
161    @SuppressWarnings("unchecked")
162    protected void reindexWay(W way, Consumer<W> wUpdater, Consumer<R> rUpdater) {
163        BBox before = way.getBBox();
164        if (!ways.remove(way))
165            throw new JosmRuntimeException("Reindexing way failed to remove");
166        wUpdater.accept(way);
167        if (!ways.add(way))
168            throw new JosmRuntimeException("Reindexing way failed to add");
169        if (!way.getBBox().equals(before)) {
170            for (IPrimitive primitive: way.getReferrers()) {
171                reindexRelation((R) primitive, rUpdater);
172            }
173        }
174    }
175
176    /**
177     * Re-index the relation after it's position was changed.
178     * @param relation The relation to re-index
179     * @param rUpdater update relation position
180     */
181    @SuppressWarnings("unchecked")
182    protected void reindexRelation(R relation, Consumer<R> rUpdater) {
183        BBox before = relation.getBBox();
184        rUpdater.accept(relation);
185        if (!before.equals(relation.getBBox())) {
186            for (IPrimitive primitive: relation.getReferrers()) {
187                reindexRelation((R) primitive, rUpdater);
188            }
189        }
190    }
191
192    /**
193     * Removes all primitives from the this store.
194     */
195    public void clear() {
196        nodes.clear();
197        ways.clear();
198        relations.clear();
199    }
200}