source: josm/trunk/src/org/openstreetmap/josm/data/vector/VectorDataStore.java@ 17862

Last change on this file since 17862 was 17862, checked in by simon04, 3 years ago

fix #17177 - Add support for Mapbox Vector Tile (patch by taylor.smock)

Signed-off-by: Taylor Smock <tsmock@…>

File size: 17.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.vector;
3
4import org.openstreetmap.gui.jmapviewer.Coordinate;
5import org.openstreetmap.gui.jmapviewer.Tile;
6import org.openstreetmap.gui.jmapviewer.interfaces.ICoordinate;
7import org.openstreetmap.josm.data.IQuadBucketType;
8import org.openstreetmap.josm.data.imagery.vectortile.VectorTile;
9import org.openstreetmap.josm.data.imagery.vectortile.mapbox.Layer;
10import org.openstreetmap.josm.data.osm.BBox;
11import org.openstreetmap.josm.data.osm.INode;
12import org.openstreetmap.josm.data.osm.IPrimitive;
13import org.openstreetmap.josm.data.osm.IRelation;
14import org.openstreetmap.josm.data.osm.IWay;
15import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
16import org.openstreetmap.josm.data.osm.SimplePrimitiveId;
17import org.openstreetmap.josm.data.osm.UniqueIdGenerator;
18import org.openstreetmap.josm.gui.dialogs.relation.sort.RelationSorter;
19import org.openstreetmap.josm.tools.Destroyable;
20import org.openstreetmap.josm.tools.Geometry;
21import org.openstreetmap.josm.tools.JosmRuntimeException;
22import org.openstreetmap.josm.tools.Logging;
23
24import java.awt.geom.Area;
25import java.awt.geom.Ellipse2D;
26import java.awt.geom.Path2D;
27import java.awt.geom.PathIterator;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.List;
32import java.util.Objects;
33import java.util.stream.Collectors;
34
35/**
36 * A data store for Vector Data sets
37 * @author Taylor Smock
38 * @since xxx
39 */
40public class VectorDataStore extends DataStore<VectorPrimitive, VectorNode, VectorWay, VectorRelation> implements Destroyable {
41 private static final String JOSM_MERGE_TYPE_KEY = "josm_merge_type";
42 private static final String ORIGINAL_ID = "original_id";
43 private static final String MULTIPOLYGON_TYPE = "multipolygon";
44 private static final String RELATION_TYPE = "type";
45
46 @Override
47 protected void addPrimitive(VectorPrimitive primitive) {
48 // The field is uint64, so we can use negative numbers to indicate that it is a "generated" object (e.g., nodes for ways)
49 if (primitive.getUniqueId() == 0) {
50 final UniqueIdGenerator generator = primitive.getIdGenerator();
51 long id;
52 do {
53 id = generator.generateUniqueId();
54 } while (this.primitivesMap.containsKey(new SimplePrimitiveId(id, primitive.getType())));
55 primitive.setId(primitive.getIdGenerator().generateUniqueId());
56 }
57 if (primitive instanceof VectorRelation && !primitive.isMultipolygon()) {
58 primitive = mergeWays((VectorRelation) primitive);
59 }
60 final VectorPrimitive alreadyAdded = this.primitivesMap.get(primitive.getPrimitiveId());
61 final VectorRelation mergedRelation = (VectorRelation) this.primitivesMap
62 .get(new SimplePrimitiveId(primitive.getPrimitiveId().getUniqueId(),
63 OsmPrimitiveType.RELATION));
64 if (alreadyAdded == null || alreadyAdded.equals(primitive)) {
65 super.addPrimitive(primitive);
66 } else if (mergedRelation != null && mergedRelation.get(JOSM_MERGE_TYPE_KEY) != null) {
67 mergedRelation.addRelationMember(new VectorRelationMember("", primitive));
68 super.addPrimitive(primitive);
69 // Check that all primitives can be merged
70 if (mergedRelation.getMemberPrimitivesList().stream().allMatch(IWay.class::isInstance)) {
71 // This pretty much does the "right" thing
72 this.mergeWays(mergedRelation);
73 } else if (!(primitive instanceof IWay)) {
74 // Can't merge, ever (one of the childs is a node/relation)
75 mergedRelation.remove(JOSM_MERGE_TYPE_KEY);
76 }
77 } else if (mergedRelation != null && primitive instanceof IRelation) {
78 // Just add to the relation
79 ((VectorRelation) primitive).getMembers().forEach(mergedRelation::addRelationMember);
80 } else if (alreadyAdded instanceof VectorWay && primitive instanceof VectorWay) {
81 final VectorRelation temporaryRelation =
82 mergedRelation == null ? new VectorRelation(primitive.getLayer()) : mergedRelation;
83 if (mergedRelation == null) {
84 temporaryRelation.put(JOSM_MERGE_TYPE_KEY, "merge");
85 temporaryRelation.addRelationMember(new VectorRelationMember("", alreadyAdded));
86 }
87 temporaryRelation.addRelationMember(new VectorRelationMember("", primitive));
88 super.addPrimitive(primitive);
89 super.addPrimitive(temporaryRelation);
90 }
91 }
92
93 private VectorPrimitive mergeWays(VectorRelation relation) {
94 List<VectorRelationMember> members = RelationSorter.sortMembersByConnectivity(relation.getMembers());
95 Collection<VectorWay> relationWayList = members.stream().map(VectorRelationMember::getMember)
96 .filter(VectorWay.class::isInstance)
97 .map(VectorWay.class::cast).collect(Collectors.toCollection(ArrayList::new));
98 // Only support way-only relations
99 if (relationWayList.size() != relation.getMemberPrimitivesList().size()) {
100 return relation;
101 }
102 List<VectorWay> wayList = new ArrayList<>(relation.getMembersCount());
103 // Assume that the order may not be correct, worst case O(n), best case O(n/2)
104 // Assume that the ways were drawn in order
105 final int maxIteration = relationWayList.size();
106 int iteration = 0;
107 while (iteration < maxIteration && wayList.size() < relationWayList.size()) {
108 for (VectorWay way : relationWayList) {
109 if (wayList.isEmpty()) {
110 wayList.add(way);
111 continue;
112 }
113 // Check first/last ways (last first, since the list *should* be sorted)
114 if (canMergeWays(wayList.get(wayList.size() - 1), way, false)) {
115 wayList.add(way);
116 } else if (canMergeWays(wayList.get(0), way, false)) {
117 wayList.add(0, way);
118 }
119 }
120 iteration++;
121 relationWayList.removeIf(wayList::contains);
122 }
123 return relation;
124 }
125
126 private static <N extends INode, W extends IWay<N>> boolean canMergeWays(W old, W toAdd, boolean allowReverse) {
127 final List<N> nodes = new ArrayList<>(old.getNodes());
128 boolean added = true;
129 if (allowReverse && old.firstNode().equals(toAdd.firstNode())) {
130 // old <-|-> new becomes old ->|-> new
131 Collections.reverse(nodes);
132 nodes.addAll(toAdd.getNodes());
133 } else if (old.firstNode().equals(toAdd.lastNode())) {
134 // old <-|<- new, so we prepend the new nodes in order
135 nodes.addAll(0, toAdd.getNodes());
136 } else if (old.lastNode().equals(toAdd.firstNode())) {
137 // old ->|-> new, we just add it
138 nodes.addAll(toAdd.getNodes());
139 } else if (allowReverse && old.lastNode().equals(toAdd.lastNode())) {
140 // old ->|<- new, we need to reverse new
141 final List<N> toAddNodes = new ArrayList<>(toAdd.getNodes());
142 Collections.reverse(toAddNodes);
143 nodes.addAll(toAddNodes);
144 } else {
145 added = false;
146 }
147 if (added) {
148 // This is (technically) always correct
149 old.setNodes(nodes);
150 }
151 return added;
152 }
153
154 private synchronized <T extends Tile & VectorTile> VectorNode pointToNode(T tile, Layer layer,
155 Collection<VectorPrimitive> featureObjects, int x, int y) {
156 final BBox tileBbox;
157 if (tile instanceof IQuadBucketType) {
158 tileBbox = ((IQuadBucketType) tile).getBBox();
159 } else {
160 final ICoordinate upperLeft = tile.getTileSource().tileXYToLatLon(tile);
161 final ICoordinate lowerRight = tile.getTileSource()
162 .tileXYToLatLon(tile.getXtile() + 1, tile.getYtile() + 1, tile.getZoom());
163
164 tileBbox = new BBox(upperLeft.getLon(), upperLeft.getLat(), lowerRight.getLon(), lowerRight.getLat());
165 }
166 final int layerExtent = layer.getExtent();
167 final ICoordinate coords = new Coordinate(
168 tileBbox.getMaxLat() - (tileBbox.getMaxLat() - tileBbox.getMinLat()) * y / layerExtent,
169 tileBbox.getMinLon() + (tileBbox.getMaxLon() - tileBbox.getMinLon()) * x / layerExtent
170 );
171 final Collection<VectorNode> nodes = this.store
172 .searchNodes(new BBox(coords.getLon(), coords.getLat(), VectorDataSet.DUPE_NODE_DISTANCE));
173 final VectorNode node;
174 if (!nodes.isEmpty()) {
175 final VectorNode first = nodes.iterator().next();
176 if (first.isDisabled() || !first.isVisible()) {
177 // Only replace nodes that are not visible
178 node = new VectorNode(layer.getName());
179 node.setCoor(node.getCoor());
180 first.getReferrers(true).forEach(primitive -> {
181 if (primitive instanceof VectorWay) {
182 List<VectorNode> nodeList = new ArrayList<>(((VectorWay) primitive).getNodes());
183 nodeList.replaceAll(vnode -> vnode.equals(first) ? node : vnode);
184 ((VectorWay) primitive).setNodes(nodeList);
185 } else if (primitive instanceof VectorRelation) {
186 List<VectorRelationMember> members = new ArrayList<>(((VectorRelation) primitive).getMembers());
187 members.replaceAll(member ->
188 member.getMember().equals(first) ? new VectorRelationMember(member.getRole(), node) : member);
189 ((VectorRelation) primitive).setMembers(members);
190 }
191 });
192 this.removePrimitive(first);
193 } else {
194 node = first;
195 }
196 } else {
197 node = new VectorNode(layer.getName());
198 }
199 node.setCoor(coords);
200 featureObjects.add(node);
201 return node;
202 }
203
204 private <T extends Tile & VectorTile> List<VectorWay> pathToWay(T tile, Layer layer,
205 Collection<VectorPrimitive> featureObjects, Path2D shape) {
206 final PathIterator pathIterator = shape.getPathIterator(null);
207 final List<VectorWay> ways = pathIteratorToObjects(tile, layer, featureObjects, pathIterator).stream()
208 .filter(VectorWay.class::isInstance).map(VectorWay.class::cast).collect(
209 Collectors.toList());
210 // These nodes technically do not exist, so we shouldn't show them
211 ways.stream().flatMap(way -> way.getNodes().stream())
212 .filter(prim -> !prim.isTagged() && prim.getReferrers(true).size() == 1 && prim.getId() <= 0)
213 .forEach(prim -> {
214 prim.setDisabled(true);
215 prim.setVisible(false);
216 });
217 return ways;
218 }
219
220 private <T extends Tile & VectorTile> List<VectorPrimitive> pathIteratorToObjects(T tile, Layer layer,
221 Collection<VectorPrimitive> featureObjects, PathIterator pathIterator) {
222 final List<VectorNode> nodes = new ArrayList<>();
223 final double[] coords = new double[6];
224 final List<VectorPrimitive> ways = new ArrayList<>();
225 do {
226 final int type = pathIterator.currentSegment(coords);
227 pathIterator.next();
228 if ((PathIterator.SEG_MOVETO == type || PathIterator.SEG_CLOSE == type) && !nodes.isEmpty()) {
229 if (PathIterator.SEG_CLOSE == type) {
230 nodes.add(nodes.get(0));
231 }
232 // New line
233 if (!nodes.isEmpty()) {
234 final VectorWay way = new VectorWay(layer.getName());
235 way.setNodes(nodes);
236 featureObjects.add(way);
237 ways.add(way);
238 }
239 nodes.clear();
240 }
241 if (PathIterator.SEG_MOVETO == type || PathIterator.SEG_LINETO == type) {
242 final VectorNode node = pointToNode(tile, layer, featureObjects, (int) coords[0], (int) coords[1]);
243 nodes.add(node);
244 } else if (PathIterator.SEG_CLOSE != type) {
245 // Vector Tiles only have MoveTo, LineTo, and ClosePath. Anything else is not supported at this time.
246 throw new UnsupportedOperationException();
247 }
248 } while (!pathIterator.isDone());
249 if (!nodes.isEmpty()) {
250 final VectorWay way = new VectorWay(layer.getName());
251 way.setNodes(nodes);
252 featureObjects.add(way);
253 ways.add(way);
254 }
255 return ways;
256 }
257
258 private <T extends Tile & VectorTile> VectorRelation areaToRelation(T tile, Layer layer,
259 Collection<VectorPrimitive> featureObjects, Area area) {
260 final PathIterator pathIterator = area.getPathIterator(null);
261 final List<VectorPrimitive> members = pathIteratorToObjects(tile, layer, featureObjects, pathIterator);
262 VectorRelation vectorRelation = new VectorRelation(layer.getName());
263 for (VectorPrimitive member : members) {
264 final String role;
265 if (member instanceof VectorWay && ((VectorWay) member).isClosed()) {
266 role = Geometry.isClockwise(((VectorWay) member).getNodes()) ? "outer" : "inner";
267 } else {
268 role = "";
269 }
270 vectorRelation.addRelationMember(new VectorRelationMember(role, member));
271 }
272 return vectorRelation;
273 }
274
275 /**
276 * Add the information from a tile to this object
277 * @param tile The tile to add
278 * @param <T> The tile type
279 */
280 public <T extends Tile & VectorTile> void addDataTile(T tile) {
281 for (Layer layer : tile.getLayers()) {
282 layer.getFeatures().forEach(feature -> {
283 org.openstreetmap.josm.data.imagery.vectortile.mapbox.Geometry geometry = feature
284 .getGeometryObject();
285 List<VectorPrimitive> featureObjects = new ArrayList<>();
286 List<VectorPrimitive> primaryFeatureObjects = new ArrayList<>();
287 geometry.getShapes().forEach(shape -> {
288 final VectorPrimitive primitive;
289 if (shape instanceof Ellipse2D) {
290 primitive = pointToNode(tile, layer, featureObjects,
291 (int) ((Ellipse2D) shape).getCenterX(), (int) ((Ellipse2D) shape).getCenterY());
292 } else if (shape instanceof Path2D) {
293 primitive = pathToWay(tile, layer, featureObjects, (Path2D) shape).stream().findFirst()
294 .orElse(null);
295 } else if (shape instanceof Area) {
296 primitive = areaToRelation(tile, layer, featureObjects, (Area) shape);
297 primitive.put(RELATION_TYPE, MULTIPOLYGON_TYPE);
298 } else {
299 // We shouldn't hit this, but just in case
300 throw new UnsupportedOperationException();
301 }
302 primaryFeatureObjects.add(primitive);
303 });
304 final VectorPrimitive primitive;
305 if (primaryFeatureObjects.size() == 1) {
306 primitive = primaryFeatureObjects.get(0);
307 if (primitive instanceof IRelation && !primitive.isMultipolygon()) {
308 primitive.put(JOSM_MERGE_TYPE_KEY, "merge");
309 }
310 } else if (!primaryFeatureObjects.isEmpty()) {
311 VectorRelation relation = new VectorRelation(layer.getName());
312 primaryFeatureObjects.stream().map(prim -> new VectorRelationMember("", prim))
313 .forEach(relation::addRelationMember);
314 primitive = relation;
315 } else {
316 return;
317 }
318 primitive.setId(feature.getId());
319 // Version 1 <i>and</i> 2 <i>do not guarantee</i> that non-zero ids are unique
320 // We depend upon unique ids in the data store
321 if (feature.getId() != 0 && this.primitivesMap.containsKey(primitive.getPrimitiveId())) {
322 // This, unfortunately, makes a new string
323 primitive.put(ORIGINAL_ID, Long.toString(feature.getId()));
324 primitive.setId(primitive.getIdGenerator().generateUniqueId());
325 }
326 if (feature.getTags() != null) {
327 feature.getTags().forEach(primitive::put);
328 }
329 featureObjects.forEach(this::addPrimitive);
330 primaryFeatureObjects.forEach(this::addPrimitive);
331 try {
332 this.addPrimitive(primitive);
333 } catch (JosmRuntimeException e) {
334 Logging.error("{0}/{1}/{2}: {3}", tile.getZoom(), tile.getXtile(), tile.getYtile(), primitive.get("key"));
335 throw e;
336 }
337 });
338 }
339 // Replace original_ids with the same object (reduce memory usage)
340 // Strings aren't interned automatically in some GC implementations
341 Collection<IPrimitive> primitives = this.getAllPrimitives().stream().filter(p -> p.hasKey(ORIGINAL_ID))
342 .collect(Collectors.toList());
343 List<String> toReplace = primitives.stream().map(p -> p.get(ORIGINAL_ID)).filter(Objects::nonNull).collect(Collectors.toList());
344 primitives.stream().filter(p -> toReplace.contains(p.get(ORIGINAL_ID)))
345 .forEach(p -> p.put(ORIGINAL_ID, toReplace.stream().filter(shared -> shared.equals(p.get(ORIGINAL_ID)))
346 .findAny().orElse(null)));
347 }
348
349 @Override
350 public void destroy() {
351 this.addedTiles.forEach(tile -> tile.setLoaded(false));
352 this.addedTiles.forEach(tile -> tile.setImage(null));
353 this.addedTiles.clear();
354 this.store.clear();
355 this.allPrimitives.clear();
356 this.primitivesMap.clear();
357 }
358}
Note: See TracBrowser for help on using the repository browser.