@Override public void visit(Node n) { if (e.child == null && left.matches(new Environment(n))) { if (e.osm instanceof Way && Geometry.nodeInsidePolygon(n, ((Way) e.osm).getNodes()) || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isNodeInsideMultiPolygon(n, (Relation) e.osm, null)) { e.child = n; } } }
private WayInPolygon advanceNextWay(boolean rightmost) { Node headNode = !lastWayReverse ? lastWay.way.lastNode() : lastWay.way.firstNode(); Node prevNode = !lastWayReverse ? lastWay.way.getNode(lastWay.way.getNodesCount() - 2) : lastWay.way.getNode(1); // find best next way WayInPolygon bestWay = null; Node bestWayNextNode = null; boolean bestWayReverse = false; for (WayInPolygon way : availableWays) { if (way.way.firstNode().equals(headNode)) { // start adjacent to headNode Node nextNode = way.way.getNode(1); if (nextNode.equals(prevNode)) { // this is the path we came from - ignore it. } else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { // the new way is better bestWay = way; bestWayReverse = false; bestWayNextNode = nextNode; } } if (way.way.lastNode().equals(headNode)) { // end adjacent to headNode Node nextNode = way.way.getNode(way.way.getNodesCount() - 2); if (nextNode.equals(prevNode)) { // this is the path we came from - ignore it. } else if (bestWay == null || (Geometry.isToTheRightSideOfLine(prevNode, headNode, bestWayNextNode, nextNode) == rightmost)) { // the new way is better bestWay = way; bestWayReverse = true; bestWayNextNode = nextNode; } } } lastWay = bestWay; lastWayReverse = bestWayReverse; return lastWay; }
@Override public void visit(Way w) { if (e.child == null && left.matches(new Environment(w))) { if (e.osm instanceof Way && Geometry.PolygonIntersection.FIRST_INSIDE_SECOND.equals( Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes())) || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) e.osm, null)) { e.child = w; } } }
@Override public void visit(Way w) { if (!w.isUsable() || !w.isClosed()) return; String natural = w.get("natural"); if (natural == null) return; else if ("coastline".equals(natural) && Geometry.isClockwise(w)) { reportError(w, tr("Reversed coastline: land not on left side"), WRONGLY_ORDERED_COAST); } else if ("land".equals(natural) && Geometry.isClockwise(w)) { reportError(w, tr("Reversed land: land not on left side"), WRONGLY_ORDERED_LAND); } else return; }
@Override public void visit(Way w) { if (e.child == null && left.matches(new Environment(w))) { if (e.osm instanceof Way && Geometry.PolygonIntersection.CROSSING.equals( Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))) { e.child = w; } } }
protected void checkDistance(OsmPrimitive house, Collection<Way> street) { EastNorth centroid; if (house instanceof Node) { centroid = ((Node) house).getEastNorth(); } else if (house instanceof Way) { List<Node> nodes = ((Way) house).getNodes(); if (house.hasKey(ADDR_INTERPOLATION)) { for (Node n : nodes) { if (n.hasKey(ADDR_HOUSE_NUMBER)) { checkDistance(n, street); } } return; } centroid = Geometry.getCentroid(nodes); } else { return; // TODO handle multipolygon houses ? } if (centroid == null) return; // fix #8305 double maxDistance = Main.pref.getDouble("validator.addresses.max_street_distance", 200.0); boolean hasIncompleteWays = false; for (Way streetPart : street) { for (Pair<Node, Node> chunk : streetPart.getNodePairs(false)) { EastNorth closest = Geometry.closestPointToSegment( chunk.a.getEastNorth(), chunk.b.getEastNorth(), centroid); if (closest.distance(centroid) <= maxDistance) { return; } } if (!hasIncompleteWays && streetPart.isIncomplete()) { hasIncompleteWays = true; } } // No street segment found near this house, report error on if the relation does not contain // incomplete street ways (fix #8314) if (hasIncompleteWays) return; List<OsmPrimitive> errorList = new ArrayList<OsmPrimitive>(street); errorList.add(0, house); errors.add( new AddressError(HOUSE_NUMBER_TOO_FAR, errorList, tr("House number too far from street"))); }
/** * Tests if the areas have some intersections to join. * * @param areas Areas to test * @return @{code true} if areas are joinable */ private boolean testJoin(List<Multipolygon> areas) { List<Way> allStartingWays = new ArrayList<Way>(); for (Multipolygon area : areas) { allStartingWays.add(area.outerWay); allStartingWays.addAll(area.innerWays); } // find intersection points Set<Node> nodes = Geometry.addIntersections(allStartingWays, true, cmds); return !nodes.isEmpty(); }
/** * Tests if way is inside other way * * @param outside outer polygon description * @param inside inner polygon description * @return {@code true} if inner is inside outer */ public static boolean wayInsideWay(AssembledPolygon inside, AssembledPolygon outside) { Set<Node> outsideNodes = new HashSet<Node>(outside.getNodes()); List<Node> insideNodes = inside.getNodes(); for (Node insideNode : insideNodes) { if (!outsideNodes.contains(insideNode)) // simply test the one node return Geometry.nodeInsidePolygon(insideNode, outside.getNodes()); } // all nodes shared. return false; }
void addSpecial(OsmPrimitive o) { if (o instanceof Node) { addCoordinates((Node) o); } else if (o instanceof Way) { addBbox(o); add( tr("Centroid: "), Main.getProjection() .eastNorth2latlon(Geometry.getCentroid(((Way) o).getNodes())) .toStringCSV(", ")); addWayNodes((Way) o); } else if (o instanceof Relation) { addBbox(o); addRelationMembers((Relation) o); } }
/** * Returns the coordinate of intersection of segment sp1-sp2 and an altitude to it starting at * point ap. If the line defined with sp1-sp2 intersects its altitude out of sp1-sp2, null is * returned. * * @param sp1 * @param sp2 * @param ap * @return Intersection coordinate or null */ public static EastNorth getSegmentAltituteIntersection( EastNorth sp1, EastNorth sp2, EastNorth ap) { Double segmentLenght = sp1.distance(sp2); Double altitudeAngle = getSegmentAngle(sp1, sp2) + Math.PI / 2; // Taking a random point on the altitude line (angle is known). EastNorth ap2 = new EastNorth( ap.east() + 1000 * Math.cos(altitudeAngle), ap.north() + 1000 * Math.sin(altitudeAngle)); // Finding the intersection of two lines EastNorth resultCandidate = Geometry.getLineLineIntersection(sp1, sp2, ap, ap2); // Filtering result if (resultCandidate != null && resultCandidate.distance(sp1) * .999 < segmentLenght && resultCandidate.distance(sp2) * .999 < segmentLenght) { return resultCandidate; } else { return null; } }
/** * 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; }
/** * Will join two or more overlapping areas * * @param areas list of areas to join * @return new area formed. */ private JoinAreasResult joinAreas(List<Multipolygon> areas) throws UserCancelException { JoinAreasResult result = new JoinAreasResult(); result.hasChanges = false; List<Way> allStartingWays = new ArrayList<Way>(); List<Way> innerStartingWays = new ArrayList<Way>(); List<Way> outerStartingWays = new ArrayList<Way>(); for (Multipolygon area : areas) { outerStartingWays.add(area.outerWay); innerStartingWays.addAll(area.innerWays); } allStartingWays.addAll(innerStartingWays); allStartingWays.addAll(outerStartingWays); // first remove nodes in the same coordinate boolean removedDuplicates = false; removedDuplicates |= removeDuplicateNodes(allStartingWays); if (removedDuplicates) { result.hasChanges = true; commitCommands(marktr("Removed duplicate nodes")); } // find intersection points Set<Node> nodes = Geometry.addIntersections(allStartingWays, false, cmds); // no intersections, return. if (nodes.isEmpty()) return result; commitCommands(marktr("Added node on all intersections")); List<RelationRole> relations = new ArrayList<RelationRole>(); // Remove ways from all relations so ways can be combined/split quietly for (Way way : allStartingWays) { relations.addAll(removeFromAllRelations(way)); } // Don't warn now, because it will really look corrupted boolean warnAboutRelations = !relations.isEmpty() && allStartingWays.size() > 1; List<WayInPolygon> preparedWays = new ArrayList<WayInPolygon>(); for (Way way : outerStartingWays) { List<Way> splitWays = splitWayOnNodes(way, nodes); preparedWays.addAll(markWayInsideSide(splitWays, false)); } for (Way way : innerStartingWays) { List<Way> splitWays = splitWayOnNodes(way, nodes); preparedWays.addAll(markWayInsideSide(splitWays, true)); } // Find boundary ways List<Way> discardedWays = new ArrayList<Way>(); List<AssembledPolygon> bounadries = findBoundaryPolygons(preparedWays, discardedWays); // find polygons List<AssembledMultipolygon> preparedPolygons = findPolygons(bounadries); // assemble final polygons List<Multipolygon> polygons = new ArrayList<Multipolygon>(); Set<Relation> relationsToDelete = new LinkedHashSet<Relation>(); for (AssembledMultipolygon pol : preparedPolygons) { // create the new ways Multipolygon resultPol = joinPolygon(pol); // create multipolygon relation, if necessary. RelationRole ownMultipolygonRelation = addOwnMultigonRelation(resultPol.innerWays, resultPol.outerWay); // add back the original relations, merged with our new multipolygon relation fixRelations(relations, resultPol.outerWay, ownMultipolygonRelation, relationsToDelete); // strip tags from inner ways // TODO: preserve tags on existing inner ways stripTags(resultPol.innerWays); polygons.add(resultPol); } commitCommands(marktr("Assemble new polygons")); for (Relation rel : relationsToDelete) { cmds.add(new DeleteCommand(rel)); } commitCommands(marktr("Delete relations")); // Delete the discarded inner ways if (!discardedWays.isEmpty()) { Command deleteCmd = DeleteCommand.delete(Main.main.getEditLayer(), discardedWays, true); if (deleteCmd != null) { cmds.add(deleteCmd); commitCommands(marktr("Delete Ways that are not part of an inner multipolygon")); } } makeCommitsOneAction(marktr("Joined overlapping areas")); if (warnAboutRelations) { new Notification( tr( "Some of the ways were part of relations that have been modified.<br>Please verify no errors have been introduced.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .setDuration(Notification.TIME_LONG) .show(); } result.hasChanges = true; result.polygons = polygons; return result; }
/** * Perform AlignInCircle action. * * <p>A fixed node is a node for which it is forbidden to change the angle relative to center of * the circle. All other nodes are uniformly distributed. * * <p>Case 1: One unclosed way. --> allow action, and align selected way nodes If nodes * contained by this way are selected, there are fix. If nodes outside from the way are selected * there are ignored. * * <p>Case 2: One or more ways are selected and can be joined into a polygon --> allow action, * and align selected ways nodes If 1 node outside of way is selected, it became center If 1 node * outside and 1 node inside are selected there define center and radius If no outside node and 2 * inside nodes are selected those 2 nodes define diameter In all other cases outside nodes are * ignored In all cases, selected nodes are fix, nodes with more than one referrers are fix (first * referrer is the selected way) * * <p>Case 3: Only nodes are selected --> Align these nodes, all are fix */ @Override public void actionPerformed(ActionEvent e) { if (!isEnabled()) return; Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected(); List<Node> nodes = new LinkedList<>(); // fixNodes: All nodes for which the angle relative to center should not be modified Set<Node> fixNodes = new HashSet<>(); List<Way> ways = new LinkedList<>(); EastNorth center = null; double radius = 0; for (OsmPrimitive osm : sel) { if (osm instanceof Node) { nodes.add((Node) osm); } else if (osm instanceof Way) { ways.add((Way) osm); } } if (ways.size() == 1 && !ways.get(0).isClosed()) { // Case 1 Way w = ways.get(0); fixNodes.add(w.firstNode()); fixNodes.add(w.lastNode()); fixNodes.addAll(nodes); fixNodes.addAll(collectNodesWithExternReferers(ways)); // Temporary closed way used to reorder nodes Way closedWay = new Way(w); closedWay.addNode(w.firstNode()); List<Way> usedWays = new ArrayList<>(1); usedWays.add(closedWay); nodes = collectNodesAnticlockwise(usedWays); } else if (!ways.isEmpty() && checkWaysArePolygon(ways)) { // Case 2 List<Node> inside = new ArrayList<>(); List<Node> outside = new ArrayList<>(); for (Node n : nodes) { boolean isInside = false; for (Way w : ways) { if (w.getNodes().contains(n)) { isInside = true; break; } } if (isInside) inside.add(n); else outside.add(n); } if (outside.size() == 1 && inside.isEmpty()) { center = outside.get(0).getEastNorth(); } else if (outside.size() == 1 && inside.size() == 1) { center = outside.get(0).getEastNorth(); radius = distance(center, inside.get(0).getEastNorth()); } else if (inside.size() == 2 && outside.isEmpty()) { // 2 nodes inside, define diameter EastNorth en0 = inside.get(0).getEastNorth(); EastNorth en1 = inside.get(1).getEastNorth(); center = new EastNorth((en0.east() + en1.east()) / 2, (en0.north() + en1.north()) / 2); radius = distance(en0, en1) / 2; } fixNodes.addAll(inside); fixNodes.addAll(collectNodesWithExternReferers(ways)); nodes = collectNodesAnticlockwise(ways); if (nodes.size() < 4) { new Notification(tr("Not enough nodes in selected ways.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .setDuration(Notification.TIME_SHORT) .show(); return; } } else if (ways.isEmpty() && nodes.size() > 3) { // Case 3 fixNodes.addAll(nodes); // No need to reorder nodes since all are fix } else { // Invalid action new Notification(tr("Please select at least four nodes.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .setDuration(Notification.TIME_SHORT) .show(); return; } if (center == null) { // Compute the center of nodes center = Geometry.getCenter(nodes); if (center == null) { new Notification(tr("Cannot determine center of selected nodes.")) .setIcon(JOptionPane.INFORMATION_MESSAGE) .setDuration(Notification.TIME_SHORT) .show(); return; } } // Now calculate the average distance to each node from the // center. This method is ok as long as distances are short // relative to the distance from the N or S poles. if (radius == 0) { for (Node n : nodes) { radius += distance(center, n.getEastNorth()); } radius = radius / nodes.size(); } if (!actionAllowed(nodes)) return; Collection<Command> cmds = new LinkedList<>(); // Move each node to that distance from the center. // Nodes that are not "fix" will be adjust making regular arcs. int nodeCount = nodes.size(); // Search first fixed node int startPosition = 0; for (startPosition = 0; startPosition < nodeCount; startPosition++) { if (fixNodes.contains(nodes.get(startPosition % nodeCount))) break; } int i = startPosition; // Start position for current arc int j; // End position for current arc while (i < startPosition + nodeCount) { for (j = i + 1; j < startPosition + nodeCount; j++) { if (fixNodes.contains(nodes.get(j % nodeCount))) break; } Node first = nodes.get(i % nodeCount); PolarCoor pcFirst = new PolarCoor(first.getEastNorth(), center, 0); pcFirst.radius = radius; cmds.add(pcFirst.createMoveCommand(first)); if (j > i + 1) { double delta; if (j == i + nodeCount) { delta = 2 * Math.PI / nodeCount; } else { PolarCoor pcLast = new PolarCoor(nodes.get(j % nodeCount).getEastNorth(), center, 0); delta = pcLast.angle - pcFirst.angle; if (delta < 0) // Assume each PolarCoor.angle is in range ]-pi; pi] delta += 2 * Math.PI; delta /= j - i; } for (int k = i + 1; k < j; k++) { PolarCoor p = new PolarCoor(radius, pcFirst.angle + (k - i) * delta, center, 0); cmds.add(p.createMoveCommand(nodes.get(k % nodeCount))); } } i = j; // Update start point for next iteration } Main.main.undoRedo.add(new SequenceCommand(tr("Align Nodes in Circle"), cmds)); Main.map.repaint(); }