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