// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.utilsplugin2.selection; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.osm.BBox; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.tools.Geometry; import org.openstreetmap.josm.tools.Pair; /** * Class with some useful functions that are reused in extend selection actions * */ public final class NodeWayUtils { static final int maxLevel = Main.pref.getInteger("selection.maxrecursion", 15); static final int maxWays = Main.pref.getInteger("selection.maxfoundways", 2000); static final int maxWays1 = Main.pref.getInteger("selection.maxfoundways.intersection", 500); private NodeWayUtils() { // Hide default constructor for utilities classes } /** * Find the neighbours of node n on the way w and put them in given collection * @param w way on which the search goes * @param n node to find its neighbours * @param nodes collection to place the nodes we found */ static void addNeighbours(Way w, Node n, Collection<Node> nodes) { List<Node> nodeList = w.getNodes(); int idx = nodeList.indexOf(n); if (idx == -1) return; // add previous element if (idx > 0) { nodes.add(nodeList.get(idx - 1)); } // add next element if (idx < nodeList.size() - 1) { nodes.add(nodeList.get(idx + 1)); } if (w.isClosed()) { // cyclic neighbours detection if (idx == 0) { nodes.add(nodeList.get(nodeList.size() - 2)); } if (idx == nodeList.size() - 1) { nodes.add(nodeList.get(1)); } } } /** * Adds all ways attached to way to specified collection * @param w way to find attached ways * @param ways collection to place the ways we found */ static int addWaysConnectedToWay(Way w, Set<Way> ways) { int s = ways.size(); List<Node> nodes = w.getNodes(); boolean flag = ways.contains(w); for (Node n: nodes) { ways.addAll(OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)); } if (!flag) ways.remove(w); return ways.size() - s; } /** * Adds all ways attached to node to specified collection * @param n Node to find attached ways * @param ways collection to place the ways we found */ static int addWaysConnectedToNode(Node n, Set<Way> ways) { int s = ways.size(); ways.addAll(OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)); return ways.size() - s; } /** * Adds all ways intersecting one way to specified set * @param ways collection of ways to search * @param w way to check intersections * @param newWays set to place the ways we found */ static int addWaysIntersectingWay(Collection<Way> ways, Way w, Set<Way> newWays, Set<Way> excludeWays) { List<Pair<Node, Node>> nodePairs = w.getNodePairs(false); int count = 0; for (Way anyway: ways) { if (Objects.equals(anyway, w)) continue; if (newWays.contains(anyway) || excludeWays.contains(anyway)) continue; List<Pair<Node, Node>> nodePairs2 = anyway.getNodePairs(false); loop: for (Pair<Node, Node> p1 : nodePairs) { for (Pair<Node, Node> p2 : nodePairs2) { if (null != Geometry.getSegmentSegmentIntersection( p1.a.getEastNorth(), p1.b.getEastNorth(), p2.a.getEastNorth(), p2.b.getEastNorth())) { newWays.add(anyway); count++; break loop; } } } } return count; } static int addWaysIntersectingWay(Collection<Way> ways, Way w, Set<Way> newWays) { List<Pair<Node, Node>> nodePairs = w.getNodePairs(false); int count = 0; for (Way anyway: ways) { if (Objects.equals(anyway, w)) continue; if (newWays.contains(anyway)) continue; List<Pair<Node, Node>> nodePairs2 = anyway.getNodePairs(false); loop: for (Pair<Node, Node> p1 : nodePairs) { for (Pair<Node, Node> p2 : nodePairs2) { if (null != Geometry.getSegmentSegmentIntersection( p1.a.getEastNorth(), p1.b.getEastNorth(), p2.a.getEastNorth(), p2.b.getEastNorth())) { newWays.add(anyway); count++; break loop; } } } } return count; } /** * Adds all ways from allWays intersecting initWays way to specified set newWays * @param allWays collection of ways to search * @param initWays ways to check intersections * @param newWays set to place the ways we found */ public static int addWaysIntersectingWays(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) { int count = 0; for (Way w : initWays) { count += addWaysIntersectingWay(allWays, w, newWays); } return count; } public static void addWaysConnectedToWays(Collection<Way> ways, Set<Way> newWays) { for (Way w : ways) { NodeWayUtils.addWaysConnectedToWay(w, newWays); } } public static int addWaysConnectedToNodes(Set<Node> selectedNodes, Set<Way> newWays) { int s = newWays.size(); for (Node node: selectedNodes) { addWaysConnectedToNode(node, newWays); } return newWays.size() - s; } public static int addNodesConnectedToWays(Set<Way> initWays, Set<Node> newNodes) { int s = newNodes.size(); for (Way w: initWays) { newNodes.addAll(w.getNodes()); } return newNodes.size()-s; } public static void addWaysIntersectingWaysRecursively(Collection<Way> allWays, Collection<Way> initWays, Set<Way> newWays) { Set<Way> foundWays = new HashSet<>(); foundWays.addAll(initWays); newWays.addAll(initWays); Set<Way> newFoundWays; int level = 0, c; do { c = 0; newFoundWays = new HashSet<>(); for (Way w : foundWays) { c += addWaysIntersectingWay(allWays, w, newFoundWays, newWays); } foundWays = newFoundWays; newWays.addAll(newFoundWays); level++; if (c > maxWays1) { new Notification( tr("Too many ways are added: {0}!", c) ).setIcon(JOptionPane.WARNING_MESSAGE).show(); return; } } while (c > 0 && level < maxLevel); } public static void addWaysConnectedToWaysRecursively(Collection<Way> initWays, Set<Way> newWays) { int level = 0, c; newWays.addAll(initWays); do { c = 0; Set<Way> foundWays = new HashSet<>(); foundWays.addAll(newWays); for (Way w : foundWays) { c += addWaysConnectedToWay(w, newWays); } level++; if (c > maxWays) { new Notification( tr("Too many ways are added: {0}!", c) ).setIcon(JOptionPane.WARNING_MESSAGE).show(); return; } } while (c > 0 && level < maxLevel); } static void addMiddle(Set<Node> selectedNodes, Set<Node> newNodes) { Iterator<Node> it = selectedNodes.iterator(); Node n1 = it.next(); Node n2 = it.next(); Set<Way> ways = new HashSet<>(); ways.addAll(OsmPrimitive.getFilteredList(n1.getReferrers(), Way.class)); for (Way w: ways) { if (w.isUsable() && w.containsNode(n2) && w.containsNode(n1)) { // Way w goes from n1 to n2 List<Node> nodes = w.getNodes(); int i1 = nodes.indexOf(n1); int i2 = nodes.indexOf(n2); int n = nodes.size(); if (i1 > i2) { int p = i2; i2 = i1; i1 = p; // now i1<i2 } if (w.isClosed()) { if ((i2-i1)*2 <= n) { // i1 ... i2 for (int i = i1+1; i != i2; i++) { newNodes.add(nodes.get(i)); } } else { // i2 ... n-1 0 1 ... i1 for (int i = i2+1; i != i1; i = (i+1) % n) { newNodes.add(nodes.get(i)); } } } else { for (int i = i1+1; i < i2; i++) { newNodes.add(nodes.get(i)); } } } } if (newNodes.isEmpty()) { new Notification( tr("Please select two nodes connected by way!") ).setIcon(JOptionPane.WARNING_MESSAGE).show(); } } static boolean addAreaBoundary(Way firstWay, Set<Way> newWays, boolean goingLeft) { Way w = firstWay; Node curNode = w.lastNode(); Node prevNode = w.getNode(w.getNodes().size()-2); Set<Way> newestWays = new HashSet<>(); while (true) { Node nextNode, endNode, otherEnd, preLast; Way nextWay; EastNorth en; double startHeading, bestAngle; en = curNode.getEastNorth(); startHeading = prevNode.getEastNorth().heading(en); bestAngle = goingLeft ? -1e5 : 1e5; otherEnd = null; nextWay = null; for (OsmPrimitive ref : curNode.getReferrers()) { if (ref instanceof Way && !Objects.equals(ref, w) && ref.isSelectable()) { // Way w2 = (Way) ref; // -----[prevNode]-(curNode)-[nextNode]------[preLast]-(endNode) // w | w2 if (w2.getNodesCount() < 2 || w2.isClosed()) continue; if (Objects.equals(curNode, w2.firstNode())) { nextNode = w2.getNode(1); preLast = w2.getNode(w2.getNodesCount()-2); endNode = w2.lastNode(); // forward direction } else if (Objects.equals(curNode, w2.lastNode())) { nextNode = w2.getNode(w2.getNodesCount()-2); preLast = w2.getNode(1); endNode = w2.firstNode(); // backward direction } else continue; // we came to some way middle node double angle = startHeading -Math.PI - en.heading(nextNode.getEastNorth()); while (angle < 0) { angle += 2*Math.PI; } if (angle < bestAngle ^ goingLeft) { bestAngle = angle; otherEnd = endNode; prevNode = preLast; nextWay = w2; } } } if (Objects.equals(firstWay, nextWay)) { //we came to starting way, but not not the right end if (Objects.equals(otherEnd, firstWay.firstNode())) return false; newWays.addAll(newestWays); return true; // correct loop found } if (newestWays.contains(nextWay)) { // P - like loop found return false; } if (nextWay != null) { newestWays.add(nextWay); curNode = otherEnd; w = nextWay; // next way found, continuing } else { // no closed loop found return false; } } } static boolean isPointInsideMultipolygon(EastNorth p, Relation rel) { Set<Way> usedWays = OsmPrimitive.getFilteredSet(rel.getMemberPrimitives(), Way.class); return isPointInsidePolygon(p, buildPointList(usedWays)); } static void addAllInsideMultipolygon(DataSet data, Relation rel, Set<Way> newWays, Set<Node> newNodes) { if (!rel.isMultipolygon()) return; BBox box = rel.getBBox(); Collection<Way> usedWays = rel.getMemberPrimitives(Way.class); List<EastNorth> polyPoints = buildPointList(usedWays); List<Node> searchNodes = data.searchNodes(box); Set<Node> newestNodes = new HashSet<>(); Set<Way> newestWays = new HashSet<>(); for (Node n : searchNodes) { //if (Geometry.nodeInsidePolygon(n, polyNodes)) { if (NodeWayUtils.isPointInsidePolygon(n.getEastNorth(), polyPoints)) { newestNodes.add(n); } } List<Way> searchWays = data.searchWays(box); for (Way w : searchWays) { if (newestNodes.containsAll(w.getNodes())) { newestWays.add(w); } } for (Way w : newestWays) { newestNodes.removeAll(w.getNodes()); // do not select nodes of already selected ways } newNodes.addAll(newestNodes); newWays.addAll(newestWays); } static void addAllInsideWay(DataSet data, Way way, Set<Way> newWays, Set<Node> newNodes) { if (!way.isClosed()) return; BBox box = way.getBBox(); Iterable<EastNorth> polyPoints = getWayPoints(way); List<Node> searchNodes = data.searchNodes(box); Set<Node> newestNodes = new HashSet<>(); Set<Way> newestWays = new HashSet<>(); for (Node n : searchNodes) { //if (Geometry.nodeInsidePolygon(n, polyNodes)) { if (NodeWayUtils.isPointInsidePolygon(n.getEastNorth(), polyPoints)) { newestNodes.add(n); } } List<Way> searchWays = data.searchWays(box); for (Way w : searchWays) { if (newestNodes.containsAll(w.getNodes())) { newestWays.add(w); } } newNodes.addAll(newestNodes); newWays.addAll(newestWays); } public static boolean isPointInsidePolygon(EastNorth point, Iterable<EastNorth> polygonPoints) { int n = getRayIntersectionsCount(point, polygonPoints); if (n < 0) return true; // we are near node or near edge return (n % 2 == 1); } /** * @param point - point to start an OX-parallel ray * @param polygonPoints - poits forming bundary, use null to split unconnected segmants * @return 0 = not inside polygon, 1 = strictly inside, 2 = near edge, 3 = near vertex */ public static int getRayIntersectionsCount(EastNorth point, Iterable<EastNorth> polygonPoints) { if (point == null) return 0; EastNorth oldPoint = null; double n1, n2, n3, e1, e2, e3, d; int interCount = 0; for (EastNorth curPoint : polygonPoints) { if (oldPoint == null || curPoint == null) { oldPoint = curPoint; continue; } n1 = curPoint.north(); n2 = oldPoint.north(); n3 = point.north(); e1 = curPoint.east(); e2 = oldPoint.east(); e3 = point.east(); if (Math.abs(n1-n3) < 1e-5 && Math.abs(e1-e3) < 1e-5) return -3; // vertex if (Math.abs(n2-n3) < 1e-5 && Math.abs(e2-e3) < 1e-5) return -3; // vertex // looking at oldPoint-curPoint segment if (n1 > n2) { if (n1 > n3 && n3 >= n2) { n1 -= n3; n2 -= n3; e1 -= e3; e2 -= e3; d = e1*n2 - n1*e2; if (d < -1e-5) { interCount++; // there is OX intersecthion at e = (e1n2-e2n1)/(n2-n1) >= 0 } else if (d <= 1e-5) return -2; // boundary detected } } else if (n1 == n2) { if (n1 == n3) { e1 -= e3; e2 -= e3; if ((e1 <= 0 && e2 >= 0) || (e1 >= 0 && e2 <= 0)) return -2; // boundary detected } } else { if (n1 <= n3 && n3 < n2) { n1 -= n3; n2 -= n3; e1 -= e3; e2 -= e3; d = e1*n2 - n1*e2; if (d > 1e-5) { interCount++; // there is OX intersecthion at e = (e1n2-e2n1)/(n2-n1) >= 0 } else if (d >= -1e-5) return -2; // boundary detected } } oldPoint = curPoint; } // System.out.printf("Intersected intercount %d %s\n",interCount, point.toString()); return interCount; } public static Collection<OsmPrimitive> selectAllInside(Collection<OsmPrimitive> selected, DataSet dataset) { return selectAllInside(selected, dataset, true); } public static Collection<OsmPrimitive> selectAllInside(Collection<OsmPrimitive> selected, DataSet dataset, boolean ignoreNodesOfFoundWays) { Set<Way> selectedWays = OsmPrimitive.getFilteredSet(selected, Way.class); Set<Relation> selectedRels = OsmPrimitive.getFilteredSet(selected, Relation.class); for (Iterator<Relation> it = selectedRels.iterator(); it.hasNext();) { Relation r = it.next(); if (!r.isMultipolygon()) { it.remove(); } } Set<Way> newWays = new HashSet<>(); Set<Node> newNodes = new HashSet<>(); // select nodes and ways inside slexcted ways and multipolygons if (!selectedWays.isEmpty()) { for (Way w: selectedWays) { addAllInsideWay(dataset, w, newWays, newNodes); } } if (!selectedRels.isEmpty()) { for (Relation r: selectedRels) { addAllInsideMultipolygon(dataset, r, newWays, newNodes); } } if (ignoreNodesOfFoundWays) { for (Way w : newWays) { newNodes.removeAll(w.getNodes()); // do not select nodes of already selected ways } } Set<OsmPrimitive> insideSelection = new HashSet<>(); if (!newWays.isEmpty() || !newNodes.isEmpty()) { insideSelection.addAll(newWays); insideSelection.addAll(newNodes); } return insideSelection; } private static List<EastNorth> buildPointList(Iterable<Way> ways) { ArrayList<EastNorth> points = new ArrayList<>(1000); for (Way way: ways) { for (EastNorth en: getWayPoints(way)) { points.add(en); } points.add(null); // next segment indicator } return points; } public static Iterable<EastNorth> getWayPoints(final Way w) { return new Iterable<EastNorth>() { @Override public Iterator<EastNorth> iterator() { return new Iterator<EastNorth>() { int idx = 0; @Override public boolean hasNext() { return idx < w.getNodesCount(); } @Override public EastNorth next() { return w.getNode(idx++).getEastNorth(); } @Override public void remove() { throw new UnsupportedOperationException(); } }; } }; } }