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}