/** * Determines which way chunk should reuse the old id and its history. Selects the one with the * highest node count. * * @param wayChunks the way chunks * @return the way to keep */ protected static Way determineWayToKeep(Iterable<Way> wayChunks) { Way wayToKeep = null; for (Way i : wayChunks) { if (wayToKeep == null || i.getNodesCount() > wayToKeep.getNodesCount()) { wayToKeep = i; } } return wayToKeep; }
@Override public void visit(Way w) { if (!w.isUsable()) return; Map<String, String> tags = w.getKeys(); if (!tags.isEmpty()) { String highway = tags.get("highway"); if (highway != null && NAMED_WAYS.contains(highway)) { if (!tags.containsKey("name") && !tags.containsKey("ref")) { boolean isRoundabout = false; boolean hasName = false; for (String key : w.keySet()) { hasName = key.startsWith("name:") || key.endsWith("_name") || key.endsWith("_ref"); if (hasName) { break; } if (key.equals("junction")) { isRoundabout = w.get("junction").equals("roundabout"); break; } } if (!hasName && !isRoundabout) { errors.add(new TestError(this, Severity.WARNING, tr("Unnamed ways"), UNNAMED_WAY, w)); } else if (isRoundabout) { errors.add( new TestError(this, Severity.WARNING, tr("Unnamed junction"), UNNAMED_JUNCTION, w)); } } } } if (!w.isTagged() && !multipolygonways.contains(w)) { if (w.hasKeys()) { errors.add( new TestError( this, Severity.WARNING, tr("Untagged ways (commented)"), COMMENTED_WAY, w)); } else { errors.add(new TestError(this, Severity.WARNING, tr("Untagged ways"), UNTAGGED_WAY, w)); } } if (w.getNodesCount() == 0) { errors.add(new TestError(this, Severity.ERROR, tr("Empty ways"), EMPTY_WAY, w)); } else if (w.getNodesCount() == 1) { errors.add(new TestError(this, Severity.ERROR, tr("One node ways"), ONE_NODE_WAY, w)); } }
void addWayNodes(Way w) { add(tr("{0} Nodes: ", w.getNodesCount())); for (Node n : w.getNodes()) { s.append(INDENT).append(INDENT); addNameAndId(n); s.append(NL); } }
@Test public void testMultiGet10Ways() throws OsmTransferException { MultiFetchServerObjectReader reader = new MultiFetchServerObjectReader(); ArrayList<Way> ways = new ArrayList<>(ds.getWays()); for (int i = 0; i < 10; i++) { reader.append(ways.get(i)); } DataSet out = reader.parseOsm(NullProgressMonitor.INSTANCE); assertEquals(10, out.getWays().size()); for (Way w1 : out.getWays()) { Way w2 = (Way) ds.getPrimitiveById(w1); assertNotNull(w2); assertEquals(w2.getNodesCount(), w1.getNodesCount()); assertEquals(w2.get("name"), w1.get("name")); } assertTrue(reader.getMissingPrimitives().isEmpty()); }
/** * their way has a higher version and different nodes. My way is modified. * * <p>=> merge onto my way not possible, create a conflict */ @Test public void waySimple_DifferentNodesAndMyIsModified() { // -- the target dataset Node n1 = new Node(new LatLon(0, 0)); n1.setOsmId(1, 1); my.addPrimitive(n1); Node n2 = new Node(new LatLon(1, 1)); n2.setOsmId(2, 1); my.addPrimitive(n2); Way myWay = new Way(); myWay.setOsmId(3, 1); myWay.addNode(n1); myWay.addNode(n2); myWay.setModified(true); myWay.put("key1", "value1"); my.addPrimitive(myWay); // -- the source dataset Node n3 = new Node(new LatLon(0, 0)); n3.setOsmId(1, 1); their.addPrimitive(n3); Node n5 = new Node(new LatLon(1, 1)); n5.setOsmId(4, 1); their.addPrimitive(n5); Node n4 = new Node(new LatLon(2, 2)); n4.setOsmId(2, 1); n4.put("key1", "value1"); their.addPrimitive(n4); Way theirWay = new Way(); theirWay.setOsmId(3, 2); theirWay.addNode(n3); theirWay.addNode(n5); // insert a node theirWay.addNode(n4); // this one is updated their.addPrimitive(theirWay); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertEquals(1, visitor.getConflicts().size()); assertEquals(3, merged.getId()); assertEquals(1, merged.getVersion()); assertEquals(2, merged.getNodesCount()); assertEquals(1, merged.getNode(0).getId()); assertEquals(2, merged.getNode(1).getId()); assertEquals("value1", merged.get("key1")); }
@Override public boolean hasEqualSemanticAttributes(OsmPrimitive other) { if (!(other instanceof Way)) return false; if (!super.hasEqualSemanticAttributes(other)) return false; Way w = (Way) other; if (getNodesCount() != w.getNodesCount()) return false; for (int i = 0; i < getNodesCount(); i++) { if (!getNode(i).hasEqualSemanticAttributes(w.getNode(i))) return false; } return true; }
/** * Return a list of all objects in the selection, respecting the different modifier. * * @param alt Whether the alt key was pressed, which means select all objects that are touched, * instead those which are completely covered. * @return The collection of selected objects. */ public Collection<OsmPrimitive> getSelectedObjects(boolean alt) { Collection<OsmPrimitive> selection = new LinkedList<>(); // whether user only clicked, not dragged. boolean clicked = false; Rectangle bounding = lasso.getBounds(); if (bounding.height <= 2 && bounding.width <= 2) { clicked = true; } if (clicked) { Point center = new Point(lasso.xpoints[0], lasso.ypoints[0]); OsmPrimitive osm = nc.getNearestNodeOrWay(center, OsmPrimitive.isSelectablePredicate, false); if (osm != null) { selection.add(osm); } } else { // nodes for (Node n : nc.getCurrentDataSet().getNodes()) { if (n.isSelectable() && lasso.contains(nc.getPoint2D(n))) { selection.add(n); } } // ways for (Way w : nc.getCurrentDataSet().getWays()) { if (!w.isSelectable() || w.getNodesCount() == 0) { continue; } if (alt) { for (Node n : w.getNodes()) { if (!n.isIncomplete() && lasso.contains(nc.getPoint2D(n))) { selection.add(w); break; } } } else { boolean allIn = true; for (Node n : w.getNodes()) { if (!n.isIncomplete() && !lasso.contains(nc.getPoint(n))) { allIn = false; break; } } if (allIn) { selection.add(w); } } } } return selection; }
@Override public void visit(Way w) { /* * If e.parent is already set to the first matching referrer. We skip any following * referrer injected into the visitor. */ if (e.parent != null) return; if (!left.matches(e.withPrimitive(w))) return; for (int i = 0; i < w.getNodesCount(); i++) { Node n = w.getNode(i); if (n.equals(e.osm)) { if (link.matches(e.withParentAndIndexAndLinkContext(w, i, w.getNodesCount()))) { e.parent = w; e.index = i; e.count = w.getNodesCount(); return; } } } }
/** * merge to complete nodes onto an incomplete way with the same two nodes, but incomplete. => both * the nodes and the way should be complete in the target dataset after merging */ @Test public void twoCompleteNodesOntoAnIncompleteWay() { // -- source dataset // an complete node Node n1 = new Node(1, 1); n1.setCoor(new LatLon(1, 1)); their.addPrimitive(n1); // another complete node Node n2 = new Node(2, 1); n2.setCoor(new LatLon(2, 2)); their.addPrimitive(n2); // --- target dataset Node n4 = new Node(1); my.addPrimitive(n4); Node n5 = new Node(2); my.addPrimitive(n5); Way w6 = new Way(3, 1); w6.addNode(n4); w6.addNode(n5); my.addPrimitive(w6); // -- merge it DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); // -- test it assertEquals(0, visitor.getConflicts().size()); Node n = (Node) my.getPrimitiveById(1, OsmPrimitiveType.NODE); assertNotNull(n); assertFalse(n.isIncomplete()); n = (Node) my.getPrimitiveById(2, OsmPrimitiveType.NODE); assertNotNull(n); assertFalse(n.isIncomplete()); Way w = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertNotNull(w); assertFalse(w.hasIncompleteNodes()); assertTrue(w.isUsable()); assertEquals(2, w.getNodesCount()); assertEquals(1, w.getNode(0).getId()); assertEquals(2, w.getNode(1).getId()); }
/** * Merge an incomplete way with two incomplete nodes into a dataset where the way already exists * as complete way. * * <p>Use case: a way loaded with a multiget, i.e. GET /api/0.6/ways?ids=123456 after a "Update * selection " of this way */ @Test public void incompleteWayOntoCompleteWay() { // an incomplete node Node n1 = new Node(1); their.addPrimitive(n1); // another incomplete node Node n2 = new Node(2); their.addPrimitive(n2); // an incomplete way with two incomplete nodes Way w3 = new Way(3); w3.setNodes(Arrays.asList(n1, n2)); their.addPrimitive(w3); Node n4 = new Node(new LatLon(0, 0)); n4.setOsmId(1, 1); my.addPrimitive(n4); Node n5 = new Node(new LatLon(1, 1)); n5.setOsmId(2, 1); my.addPrimitive(n5); Way w6 = new Way(3, 1); w6.setNodes(Arrays.asList(n4, n5)); my.addPrimitive(w6); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); assertEquals(0, visitor.getConflicts().size()); OsmPrimitive p = my.getPrimitiveById(1, OsmPrimitiveType.NODE); assertNotNull(p); assertFalse(p.isIncomplete()); p = my.getPrimitiveById(2, OsmPrimitiveType.NODE); assertNotNull(p); assertFalse(p.isIncomplete()); p = my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertNotNull(p); assertFalse(p.isIncomplete()); Way w = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertNotNull(w); assertFalse(p.isIncomplete()); assertEquals(2, w.getNodesCount()); assertFalse(w.getNode(0).isIncomplete()); assertFalse(w.getNode(1).isIncomplete()); }
/** * Determines whether a way is oriented clockwise. * * <p>Internals: Assuming a closed non-looping way, compute twice the area of the polygon using * the formula {@code 2 * area = sum (X[n] * Y[n+1] - X[n+1] * Y[n])}. If the area is negative the * way is ordered in a clockwise direction. * * <p>See http://paulbourke.net/geometry/polyarea/ * * @param w the way to be checked. * @return true if and only if way is oriented clockwise. * @throws IllegalArgumentException if way is not closed (see {@link Way#isClosed}). */ public static boolean isClockwise(Way w) { if (!w.isClosed()) { throw new IllegalArgumentException("Way must be closed to check orientation."); } double area2 = 0.; int nodesCount = w.getNodesCount(); for (int node = 1; node <= /*sic! consider last-first as well*/ nodesCount; node++) { LatLon coorPrev = w.getNode(node - 1).getCoor(); LatLon coorCurr = w.getNode(node % nodesCount).getCoor(); area2 += coorPrev.lon() * coorCurr.lat(); area2 -= coorCurr.lon() * coorPrev.lat(); } return area2 < 0; }
/** * My dataset includes a deleted node. Their dataset includes a way with three nodes, the first * one being my node. * * <p>=> the merged way should include all three nodes. Deleted node should have deleted=false and * special conflict with isDeleted should exist */ @Test public void wayComplex_mergingADeletedNode() { // -- my dataset Node mn1 = new Node(new LatLon(0, 0)); mn1.setOsmId(1, 1); mn1.setDeleted(true); my.addPrimitive(mn1); Node tn1 = new Node(new LatLon(0, 0)); tn1.setOsmId(1, 1); their.addPrimitive(tn1); Node tn2 = new Node(new LatLon(1, 1)); tn2.setOsmId(2, 1); their.addPrimitive(tn2); Node tn3 = new Node(new LatLon(2, 2)); tn3.setOsmId(3, 1); their.addPrimitive(tn3); // -- their data set Way theirWay = new Way(); theirWay.setOsmId(4, 1); theirWay.addNode(tn1); theirWay.addNode(tn2); theirWay.addNode(tn3); theirWay.setUser(User.createOsmUser(1111, "their")); theirWay.setTimestamp(new Date()); their.addPrimitive(theirWay); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); assertEquals(1, visitor.getConflicts().size()); assertTrue(visitor.getConflicts().get(0).isMyDeleted()); Way myWay = (Way) my.getPrimitiveById(4, OsmPrimitiveType.WAY); assertEquals(3, myWay.getNodesCount()); Node n = (Node) my.getPrimitiveById(1, OsmPrimitiveType.NODE); assertTrue(myWay.getNodes().contains(n)); assertFalse(myWay.isModified()); }
public void visit(Way w) { if (w.isIncomplete() || w.getNodesCount() < 2) return; c.setStrokeStyle(w.isSelected() ? "#ff0000" : "#000000"); c.beginPath(); Iterator<Node> it = w.getNodes().iterator(); if (it.hasNext()) { Point lastP = nc.getPoint(it.next()); c.moveTo(lastP.x, lastP.y); for (int orderNumber = 1; it.hasNext(); orderNumber++) { Point p = nc.getPoint(it.next()); c.lineTo(p.x, p.y); lastP = p; } c.stroke(); } }
@Override public void visit(Way w) { if (w.getNodesCount() > 0 // do not consider empty ways && !w.hasKey( "addr:interpolation") // ignore addr:interpolation ways as they are not physical // features and most of // the time very near the associated highway, which is perfectly normal, see #9332 && !w.hasTag("highway", "platform") && !w.hasTag("railway", "platform") // similarly for public transport platforms ) { ways.addAll(getWaySegments(w)); QuadBuckets<Node> set = endnodes; if (w.hasKey("highway") || w.hasKey("railway")) { set = endnodesHighway; } addNode(w.firstNode(), set); addNode(w.lastNode(), set); } }
@Test public void testBackrefrenceForNode_Full() throws OsmTransferException { Node n = lookupNode(ds, 0); assertNotNull(n); Way w = lookupWay(ds, 0); assertNotNull(w); OsmServerBackreferenceReader reader = new OsmServerBackreferenceReader(n); reader.setReadFull(true); DataSet referers = reader.parseOsm(NullProgressMonitor.INSTANCE); assertEquals(10, referers.getNodes().size()); assertEquals(1, referers.getWays().size()); assertEquals(0, referers.getRelations().size()); for (Way way : referers.getWays()) { assertEquals(w.getId(), way.getId()); assertEquals(false, way.isIncomplete()); assertEquals(10, w.getNodesCount()); } }
List<MyWaySegment> getWaySegments(Way w) { List<MyWaySegment> ret = new ArrayList<>(); if (!w.isUsable() || w.hasKey("barrier") || w.hasTag("natural", "cliff")) return ret; int size = w.getNodesCount(); if (size < 2) return ret; for (int i = 1; i < size; ++i) { if (i < size - 1) { addNode(w.getNode(i), middlenodes); } Node a = w.getNode(i - 1); Node b = w.getNode(i); if (a.isDrawable() && b.isDrawable()) { MyWaySegment ws = new MyWaySegment(w, a, b); if (ws.isBoundary || ws.isAbandoned) { continue; } ret.add(ws); } } return ret; }
/** * Merge an incomplete way with two incomplete nodes into an empty dataset. * * <p>Use case: a way loaded with a multiget, i.e. GET /api/0.6/ways?ids=123456 */ @Test public void newIncompleteWay() { Node n1 = new Node(1); their.addPrimitive(n1); Node n2 = new Node(2); their.addPrimitive(n2); Way w3 = new Way(3); w3.setNodes(Arrays.asList(n1, n2)); their.addPrimitive(w3); assertTrue(w3.isIncomplete()); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); assertEquals(0, visitor.getConflicts().size()); OsmPrimitive p = my.getPrimitiveById(1, OsmPrimitiveType.NODE); assertNotNull(p); assertTrue(p.isIncomplete()); p = my.getPrimitiveById(2, OsmPrimitiveType.NODE); assertNotNull(p); assertTrue(p.isIncomplete()); p = my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertNotNull(p); assertTrue(p.isIncomplete()); Way w = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertNotNull(w); assertTrue(p.isIncomplete()); assertEquals(2, w.getNodesCount()); assertTrue(w.getNode(0).isIncomplete()); assertTrue(w.getNode(1).isIncomplete()); }
/** * This method analyzes the way and assigns each part what direction polygon "inside" is. * * @param parts the split parts of the way * @param isInner - if true, reverts the direction (for multipolygon islands) * @return list of parts, marked with the inside orientation. */ private List<WayInPolygon> markWayInsideSide(List<Way> parts, boolean isInner) { List<WayInPolygon> result = new ArrayList<WayInPolygon>(); // prepare prev and next maps Map<Way, Way> nextWayMap = new HashMap<Way, Way>(); Map<Way, Way> prevWayMap = new HashMap<Way, Way>(); for (int pos = 0; pos < parts.size(); pos++) { if (!parts.get(pos).lastNode().equals(parts.get((pos + 1) % parts.size()).firstNode())) throw new RuntimeException("Way not circular"); nextWayMap.put(parts.get(pos), parts.get((pos + 1) % parts.size())); prevWayMap.put(parts.get(pos), parts.get((pos + parts.size() - 1) % parts.size())); } // find the node with minimum y - it's guaranteed to be outer. (What about the south pole?) Way topWay = null; Node topNode = null; int topIndex = 0; double minY = Double.POSITIVE_INFINITY; for (Way way : parts) { for (int pos = 0; pos < way.getNodesCount(); pos++) { Node node = way.getNode(pos); if (node.getEastNorth().getY() < minY) { minY = node.getEastNorth().getY(); topWay = way; topNode = node; topIndex = pos; } } } // get the upper way and it's orientation. boolean wayClockwise; // orientation of the top way. if (topNode.equals(topWay.firstNode()) || topNode.equals(topWay.lastNode())) { Node headNode = null; // the node at junction Node prevNode = null; // last node from previous path wayClockwise = false; // node is in split point - find the outermost way from this point headNode = topNode; // make a fake node that is downwards from head node (smaller Y). It will be a division point // between paths. prevNode = new Node( new EastNorth(headNode.getEastNorth().getX(), headNode.getEastNorth().getY() - 1e5)); topWay = null; wayClockwise = false; Node bestWayNextNode = null; for (Way way : parts) { if (way.firstNode().equals(headNode)) { Node nextNode = way.getNode(1); if (topWay == null || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { // the new way is better topWay = way; wayClockwise = true; bestWayNextNode = nextNode; } } if (way.lastNode().equals(headNode)) { // end adjacent to headNode Node nextNode = way.getNode(way.getNodesCount() - 2); if (topWay == null || !Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode)) { // the new way is better topWay = way; wayClockwise = false; bestWayNextNode = nextNode; } } } } else { // node is inside way - pick the clockwise going end. Node prev = topWay.getNode(topIndex - 1); Node next = topWay.getNode(topIndex + 1); // there will be no parallel segments in the middle of way, so all fine. wayClockwise = Geometry.angleIsClockwise(prev, topNode, next); } Way curWay = topWay; boolean curWayInsideToTheRight = wayClockwise ^ isInner; // iterate till full circle is reached while (true) { // add cur way WayInPolygon resultWay = new WayInPolygon(curWay, curWayInsideToTheRight); result.add(resultWay); // process next way Way nextWay = nextWayMap.get(curWay); Node prevNode = curWay.getNode(curWay.getNodesCount() - 2); Node headNode = curWay.lastNode(); Node nextNode = nextWay.getNode(1); if (nextWay == topWay) { // full loop traversed - all done. break; } // find intersecting segments // the intersections will look like this: // // ^ // | // X wayBNode // | // wayB | // | // curWay | nextWay // ----X----------------->X----------------------X----> // prevNode ^headNode nextNode // | // | // wayA | // | // X wayANode // | int intersectionCount = 0; for (Way wayA : parts) { if (wayA == curWay) { continue; } if (wayA.lastNode().equals(headNode)) { Way wayB = nextWayMap.get(wayA); // test if wayA is opposite wayB relative to curWay and nextWay Node wayANode = wayA.getNode(wayA.getNodesCount() - 2); Node wayBNode = wayB.getNode(1); boolean wayAToTheRight = Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayANode); boolean wayBToTheRight = Geometry.isToTheRightSideOfLine(prevNode, headNode, nextNode, wayBNode); if (wayAToTheRight != wayBToTheRight) { intersectionCount++; } } } // if odd number of crossings, invert orientation if (intersectionCount % 2 != 0) { curWayInsideToTheRight = !curWayInsideToTheRight; } curWay = nextWay; } return result; }
/** * Try to delete all given primitives. * * <p>If a node is used by a way, it's removed from that way. If a node or a way is used by a * relation, inform the user and do not delete. * * <p>If this would cause ways with less than 2 nodes to be created, delete these ways instead. If * they are part of a relation, inform the user and do not delete. * * @param layer the {@link OsmDataLayer} in whose context the primitives are deleted * @param selection the objects to delete. * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well * @param silent set to true if the user should not be bugged with additional questions * @return command a command to perform the deletions, or null if there is nothing to delete. */ public static Command delete( OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean alsoDeleteNodesInWay, boolean silent) { if (selection == null || selection.isEmpty()) return null; // Diamond operator does not work with Java 9 here Set<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection); Collection<Relation> relationsToDelete = Utils.filteredCollection(primitivesToDelete, Relation.class); if (!relationsToDelete.isEmpty() && !silent && !confirmRelationDeletion(relationsToDelete)) return null; if (alsoDeleteNodesInWay) { // delete untagged nodes only referenced by primitives in primitivesToDelete, too Collection<Node> nodesToDelete = computeNodesToDelete(primitivesToDelete); primitivesToDelete.addAll(nodesToDelete); } if (!silent && !checkAndConfirmOutlyingDelete( primitivesToDelete, Utils.filteredCollection(primitivesToDelete, Way.class))) return null; Collection<Way> waysToBeChanged = new HashSet<>( OsmPrimitive.getFilteredSet(OsmPrimitive.getReferrer(primitivesToDelete), Way.class)); Collection<Command> cmds = new LinkedList<>(); for (Way w : waysToBeChanged) { Way wnew = new Way(w); wnew.removeNodes(OsmPrimitive.getFilteredSet(primitivesToDelete, Node.class)); if (wnew.getNodesCount() < 2) { primitivesToDelete.add(w); } else { cmds.add(new ChangeNodesCommand(w, wnew.getNodes())); } } // get a confirmation that the objects to delete can be removed from their parent relations // if (!silent) { Set<RelationToChildReference> references = RelationToChildReference.getRelationToChildReferences(primitivesToDelete); Iterator<RelationToChildReference> it = references.iterator(); while (it.hasNext()) { RelationToChildReference ref = it.next(); if (ref.getParent().isDeleted()) { it.remove(); } } if (!references.isEmpty()) { DeleteFromRelationConfirmationDialog dialog = DeleteFromRelationConfirmationDialog.getInstance(); dialog.getModel().populate(references); dialog.setVisible(true); if (dialog.isCanceled()) return null; } } // remove the objects from their parent relations // for (Relation cur : OsmPrimitive.getFilteredSet(OsmPrimitive.getReferrer(primitivesToDelete), Relation.class)) { Relation rel = new Relation(cur); rel.removeMembersFor(primitivesToDelete); cmds.add(new ChangeCommand(cur, rel)); } // build the delete command // if (!primitivesToDelete.isEmpty()) { cmds.add(new DeleteCommand(layer, primitivesToDelete)); } return new SequenceCommand(tr("Delete"), cmds); }
@Override public boolean matches(Environment e) { if (!right.matches(e)) return false; if (ChildOrParentSelectorType.ELEMENT_OF.equals(type)) { if (e.osm instanceof Node || e.osm.getDataSet() == null) { // nodes cannot contain elements return false; } ContainsFinder containsFinder; try { // if right selector also matches relations and if matched primitive is a way which is // part of a multipolygon, // use the multipolygon for further analysis if (!(e.osm instanceof Way) || (right instanceof OptimizedGeneralSelector && !((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.RELATION))) { throw new NoSuchElementException(); } final Collection<Relation> multipolygons = Utils.filteredCollection( Utils.filter(e.osm.getReferrers(), Predicates.hasTag("type", "multipolygon")), Relation.class); final Relation multipolygon = multipolygons.iterator().next(); if (multipolygon == null) throw new NoSuchElementException(); containsFinder = new ContainsFinder(new Environment(multipolygon)) { @Override public boolean isPrimitiveUsable(OsmPrimitive p) { return super.isPrimitiveUsable(p) && !multipolygon.getMemberPrimitives().contains(p); } }; } catch (NoSuchElementException ignore) { containsFinder = new ContainsFinder(e); } e.parent = e.osm; if (left instanceof OptimizedGeneralSelector) { if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.NODE)) { containsFinder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox())); } if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) { containsFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); } } else { // use slow test containsFinder.visit(e.osm.getDataSet().allPrimitives()); } return e.child != null; } else if (ChildOrParentSelectorType.CROSSING.equals(type) && e.osm instanceof Way) { e.parent = e.osm; final CrossingFinder crossingFinder = new CrossingFinder(e); if (right instanceof OptimizedGeneralSelector && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) { crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); } return e.child != null; } else if (ChildOrParentSelectorType.SIBLING.equals(type)) { if (e.osm instanceof Node) { for (Way w : Utils.filteredCollection(e.osm.getReferrers(true), Way.class)) { final int i = w.getNodes().indexOf(e.osm); if (i - 1 >= 0) { final Node n = w.getNode(i - 1); final Environment e2 = e.withPrimitive(n).withParent(w).withChild(e.osm); if (left.matches(e2) && link.matches(e2.withLinkContext())) { e.child = n; e.index = i; e.count = w.getNodesCount(); e.parent = w; return true; } } } } } else if (ChildOrParentSelectorType.CHILD.equals(type) && link.conds != null && !link.conds.isEmpty() && link.conds.get(0) instanceof Condition.OpenEndPseudoClassCondition) { if (e.osm instanceof Node) { e.osm.visitReferrers(new MultipolygonOpenEndFinder(e)); return e.parent != null; } } else if (ChildOrParentSelectorType.CHILD.equals(type)) { MatchingReferrerFinder collector = new MatchingReferrerFinder(e); e.osm.visitReferrers(collector); if (e.parent != null) return true; } else if (ChildOrParentSelectorType.PARENT.equals(type)) { if (e.osm instanceof Way) { List<Node> wayNodes = ((Way) e.osm).getNodes(); for (int i = 0; i < wayNodes.size(); i++) { Node n = wayNodes.get(i); if (left.matches(e.withPrimitive(n))) { if (link.matches(e.withChildAndIndexAndLinkContext(n, i, wayNodes.size()))) { e.child = n; e.index = i; e.count = wayNodes.size(); return true; } } } } else if (e.osm instanceof Relation) { List<RelationMember> members = ((Relation) e.osm).getMembers(); for (int i = 0; i < members.size(); i++) { OsmPrimitive member = members.get(i).getMember(); if (left.matches(e.withPrimitive(member))) { if (link.matches(e.withChildAndIndexAndLinkContext(member, i, members.size()))) { e.child = member; e.index = i; e.count = members.size(); return true; } } } } } return false; }
/** * their way has a higher version and different tags. the nodes are the same. My way is not * modified. Merge is possible. No conflict. * * <p>=> merge it onto my way. */ @Test public void waySimple_IdenicalNodesDifferentTags() { // -- the target dataset Node n1 = new Node(); n1.setCoor(new LatLon(0, 0)); n1.setOsmId(1, 1); my.addPrimitive(n1); Node n2 = new Node(); n2.setCoor(new LatLon(0, 0)); n2.setOsmId(2, 1); my.addPrimitive(n2); Way myWay = new Way(); myWay.setOsmId(3, 1); myWay.put("key1", "value1"); myWay.addNode(n1); myWay.addNode(n2); my.addPrimitive(myWay); // -- the source data set Node n3 = new Node(new LatLon(0, 0)); n3.setOsmId(1, 1); their.addPrimitive(n3); Node n4 = new Node(new LatLon(1, 1)); n4.setOsmId(2, 1); their.addPrimitive(n4); Way theirWay = new Way(); theirWay.setOsmId(3, 2); theirWay.put("key1", "value1"); theirWay.put("key2", "value2"); theirWay.addNode(n3); theirWay.addNode(n4); their.addPrimitive(theirWay); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); // -- tests Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertEquals(0, visitor.getConflicts().size()); assertEquals("value1", merged.get("key1")); assertEquals("value2", merged.get("key2")); assertEquals(3, merged.getId()); assertEquals(2, merged.getVersion()); assertEquals(2, merged.getNodesCount()); assertEquals(1, merged.getNode(0).getId()); assertEquals(2, merged.getNode(1).getId()); assertSame(merged, myWay); assertSame(merged.getDataSet(), my); Node mergedNode = (Node) my.getPrimitiveById(1, OsmPrimitiveType.NODE); assertSame(mergedNode, n1); mergedNode = (Node) my.getPrimitiveById(2, OsmPrimitiveType.NODE); assertSame(mergedNode, n2); assertFalse(merged.isModified()); }
/** * their way has a higher version and different tags. And it has more nodes. Two of the existing * nodes are modified. * * <p>=> merge it onto my way, no conflict */ @Test public void waySimple_AdditionalNodesAndChangedNodes() { // -- my data set Node n1 = new Node(new LatLon(0, 0)); n1.setOsmId(1, 1); my.addPrimitive(n1); Node n2 = new Node(new LatLon(1, 1)); n2.setOsmId(2, 1); my.addPrimitive(n2); Way myWay = new Way(); myWay.setOsmId(3, 1); myWay.addNode(n1); myWay.addNode(n2); my.addPrimitive(myWay); // --- their data set Node n3 = new Node(new LatLon(0, 0)); n3.setOsmId(1, 1); their.addPrimitive(n3); Node n5 = new Node(new LatLon(1, 1)); n5.setOsmId(4, 1); their.addPrimitive(n5); Node n4 = new Node(new LatLon(2, 2)); n4.setOsmId(2, 2); n4.put("key1", "value1"); their.addPrimitive(n4); Way theirWay = new Way(); theirWay.setOsmId(3, 2); theirWay.addNode(n3); theirWay.addNode(n5); // insert a node theirWay.addNode(n4); // this one is updated their.addPrimitive(theirWay); DataSetMerger visitor = new DataSetMerger(my, their); visitor.merge(); // -- tests Way merged = (Way) my.getPrimitiveById(3, OsmPrimitiveType.WAY); assertEquals(0, visitor.getConflicts().size()); assertEquals(3, merged.getId()); assertEquals(2, merged.getVersion()); assertEquals(3, merged.getNodesCount()); assertEquals(1, merged.getNode(0).getId()); assertEquals(4, merged.getNode(1).getId()); assertEquals(2, merged.getNode(2).getId()); assertEquals("value1", merged.getNode(2).get("key1")); assertSame(merged.getNode(0), n1); assertNotSame(merged.getNode(1), n5); // must be clone of the original node in their assertSame(merged.getNode(2), n2); assertFalse( merged .isModified()); // the target wasn't modified before merging, it mustn't be after // merging }
/** * Returns the start and end cells of a way. * * @param w The way * @param cellWays The map with all cells * @return A list with all the cells the way starts or ends */ public static List<List<Way>> getWaysInCell(Way w, Map<Point2D, List<Way>> cellWays) { if (w.getNodesCount() == 0) return Collections.emptyList(); Node n1 = w.getNode(0); Node n2 = w.getNode(w.getNodesCount() - 1); List<List<Way>> cells = new ArrayList<>(2); Set<Point2D> cellNodes = new HashSet<>(); Point2D cell; // First, round coordinates long x0 = Math.round(n1.getEastNorth().east() * OsmValidator.griddetail); long y0 = Math.round(n1.getEastNorth().north() * OsmValidator.griddetail); long x1 = Math.round(n2.getEastNorth().east() * OsmValidator.griddetail); long y1 = Math.round(n2.getEastNorth().north() * OsmValidator.griddetail); // Start of the way cell = new Point2D.Double(x0, y0); cellNodes.add(cell); List<Way> ways = cellWays.get(cell); if (ways == null) { ways = new ArrayList<>(); cellWays.put(cell, ways); } cells.add(ways); // End of the way cell = new Point2D.Double(x1, y1); if (!cellNodes.contains(cell)) { cellNodes.add(cell); ways = cellWays.get(cell); if (ways == null) { ways = new ArrayList<>(); cellWays.put(cell, ways); } cells.add(ways); } // Then floor coordinates, in case the way is in the border of the cell. x0 = (long) Math.floor(n1.getEastNorth().east() * OsmValidator.griddetail); y0 = (long) Math.floor(n1.getEastNorth().north() * OsmValidator.griddetail); x1 = (long) Math.floor(n2.getEastNorth().east() * OsmValidator.griddetail); y1 = (long) Math.floor(n2.getEastNorth().north() * OsmValidator.griddetail); // Start of the way cell = new Point2D.Double(x0, y0); if (!cellNodes.contains(cell)) { cellNodes.add(cell); ways = cellWays.get(cell); if (ways == null) { ways = new ArrayList<>(); cellWays.put(cell, ways); } cells.add(ways); } // End of the way cell = new Point2D.Double(x1, y1); if (!cellNodes.contains(cell)) { cellNodes.add(cell); ways = cellWays.get(cell); if (ways == null) { ways = new ArrayList<>(); cellWays.put(cell, ways); } cells.add(ways); } return cells; }