From 611e16ecf167b79c4bfcb5e39fc897c07a4a75b2 Mon Sep 17 00:00:00 2001 From: Sander Deryckere Date: Mon, 17 Sep 2012 12:36:26 +0200 Subject: [PATCH] fixing id issue + re-introducing sorting, but via area calculation --- .../src/net/osmand/data/MapAlgorithms.java | 139 +++++ .../src/net/osmand/data/Multipolygon.java | 4 +- .../src/net/osmand/data/Ring.java | 573 +++++++++--------- .../preparation/IndexVectorMapCreator.java | 2 +- 4 files changed, 440 insertions(+), 278 deletions(-) diff --git a/DataExtractionOSM/src/net/osmand/data/MapAlgorithms.java b/DataExtractionOSM/src/net/osmand/data/MapAlgorithms.java index 99372023a4..52dc81cb0d 100644 --- a/DataExtractionOSM/src/net/osmand/data/MapAlgorithms.java +++ b/DataExtractionOSM/src/net/osmand/data/MapAlgorithms.java @@ -377,4 +377,143 @@ public class MapAlgorithms { } return -1l; } + + /** + * return true if the line segment [a,b] intersects [c,d] + * @param a point 1 + * @param b point 2 + * @param c point 3 + * @param d point 4 + * @return true if the line segment [a,b] intersects [c,d] + */ + + public static boolean linesIntersect(LatLon a, LatLon b, LatLon c, LatLon d){ + + return linesIntersect( + a.getLatitude(), a.getLongitude(), + b.getLatitude(), b.getLongitude(), + c.getLatitude(), c.getLongitude(), + d.getLatitude(), d.getLongitude()); + + } + + /** + * Return true if two line segments intersect inside the segment + * + * source: http://www.java-gaming.org/index.php?topic=22590.0 + * @param x1 line 1 point 1 latitude + * @param y1 line 1 point 1 longitude + * @param x2 line 1 point 2 latitude + * @param y2 line 1 point 2 longitude + * @param x3 line 2 point 1 latitude + * @param y3 line 2 point 1 longitude + * @param x4 line 2 point 2 latitude + * @param y4 line 2 point 2 longitude + * @return + */ + + public static boolean linesIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4){ + + // Return false if either of the lines have zero length + if (x1 == x2 && y1 == y2 || + x3 == x4 && y3 == y4){ + return false; + } + + // Fastest method, based on Franklin Antonio's "Faster Line Segment Intersection" topic "in Graphics Gems III" book (http://www.graphicsgems.org/) + + double ax = x2-x1; + double ay = y2-y1; + double bx = x3-x4; + double by = y3-y4; + double cx = x1-x3; + double cy = y1-y3; + + + double alphaNumerator = by*cx - bx*cy; + double commonDenominator = ay*bx - ax*by; + if (commonDenominator > 0){ + if (alphaNumerator < 0 || alphaNumerator > commonDenominator){ + return false; + } + }else if (commonDenominator < 0){ + if (alphaNumerator > 0 || alphaNumerator < commonDenominator){ + return false; + } + } + + double betaNumerator = ax*cy - ay*cx; + if (commonDenominator > 0){ + if (betaNumerator < 0 || betaNumerator > commonDenominator){ + return false; + } + }else if (commonDenominator < 0){ + if (betaNumerator > 0 || betaNumerator < commonDenominator){ + return false; + } + } + + if (commonDenominator == 0){ + // This code wasn't in Franklin Antonio's method. It was added by Keith Woodward. + // The lines are parallel. + // Check if they're collinear. + double y3LessY1 = y3-y1; + double collinearityTestForP3 = x1*(y2-y3) + x2*(y3LessY1) + x3*(y1-y2); // see http://mathworld.wolfram.com/Collinear.html + // If p3 is collinear with p1 and p2 then p4 will also be collinear, since p1-p2 is parallel with p3-p4 + + if (collinearityTestForP3 == 0){ + // The lines are collinear. Now check if they overlap. + if (x1 >= x3 && x1 <= x4 || x1 <= x3 && x1 >= x4 || + x2 >= x3 && x2 <= x4 || x2 <= x3 && x2 >= x4 || + x3 >= x1 && x3 <= x2 || x3 <= x1 && x3 >= x2){ + if (y1 >= y3 && y1 <= y4 || y1 <= y3 && y1 >= y4 || + y2 >= y3 && y2 <= y4 || y2 <= y3 && y2 >= y4 || + y3 >= y1 && y3 <= y2 || y3 <= y1 && y3 >= y2){ + return true; + } + } + } + return false; + } + return true; + } + + + /** + * Get the area (in m²) of a closed way, represented as a list of nodes + * @param nodes the list of nodes + * @return the area of it + */ + public static double getArea(List nodes) { + // x = longitude + // y = latitude + // calculate the reference point (lower left corner of the bbox) + // start with an arbitrary value, bigger than any lat or lon + double refX = 500, refY = 500; + for (Node n : nodes){ + if (n.getLatitude() < refY) refY = n.getLatitude(); + if (n.getLongitude() < refX) refX = n.getLongitude(); + } + + List xVal = new ArrayList(); + List yVal = new ArrayList(); + + for (Node n : nodes) { + // distance from bottom line to x coordinate of node + double xDist = MapUtils.getDistance(refY, refX, refY, n.getLongitude()); + // distance from left line to y coordinate of node + double yDist = MapUtils.getDistance(refY, refX, n.getLatitude(), refX); + + xVal.add(xDist); + yVal.add(yDist); + } + + double area = 0; + + for(int i = 1; i inners = new ArrayList(getInnerRings()); // get the set of outer rings in a variable. This set will not be changed - ArrayList outers = new ArrayList(getOuterRings()); + SortedSet outers = new TreeSet(getOuterRings()); ArrayList multipolygons = new ArrayList(); // loop; start with the smallest outer ring @@ -335,7 +335,7 @@ public class Multipolygon { * @return the list of nodes in the outer ring */ public List getOuterNodes() { - return getOuterRings().get(0).getBorder().getNodes(); + return getOuterRings().get(0).getBorder(); } diff --git a/DataExtractionOSM/src/net/osmand/data/Ring.java b/DataExtractionOSM/src/net/osmand/data/Ring.java index 6eec88c883..f9d110720f 100644 --- a/DataExtractionOSM/src/net/osmand/data/Ring.java +++ b/DataExtractionOSM/src/net/osmand/data/Ring.java @@ -17,24 +17,21 @@ import net.osmand.osm.Way; * @author sander * */ -public class Ring { +public class Ring implements Comparable { /** * This is a list of the ways added by the user * The order can be changed with methods from this class */ private final ArrayList ways; + /** - * This is the closure of the ways added by the user - * So simple two-node ways are added to close the ring - * This is a cache from what can calculated with the ways + * a concatenation of the ways to form the border + * this is not necessarily a closed way + * The id is random, so this may never leave the Ring object */ - private ArrayList closedWays; - /** - * This is a single way, consisting of all the nodes - * from ways in the closedWays - * this is a cache from what can be calculated with the closedWays - */ - private Way closedBorder; + private Way border; + + /** * Construct a Ring with a list of ways @@ -54,39 +51,30 @@ public class Ring { public List getWays() { return ways; } - /** - * Get the closed ways that make up the Ring - * This method will sort the ways, so it is CPU intensive - * @return the closed ways - */ - public List getClosedWays() { - // Add ways to close the ring - closeWays(); - return closedWays; - } + /** * check if this ring is closed by nature * @return true if this ring is closed, false otherwise */ public boolean isClosed() { - closeWays(); - for (int i = closedWays.size()-1; i>=0; i--) { - if (!ways.contains(closedWays.get(i))){ - return false; - } - } - return true; + mergeWays(); + return border.getFirstNodeId() == border.getLastNodeId(); } /** * get a single closed way that represents the border * this method is CPU intensive - * @return a closed way that represents the border + * @return a list of Nodes that represents the border */ - public Way getBorder() { + public List getBorder() { mergeWays(); - return closedBorder; + List l = border.getNodes(); + if (!isClosed()) { + l.add(border.getNodes().get(0)); + } + + return l; } /** @@ -94,11 +82,11 @@ public class Ring { * If the original ways are initialized with nodes, the new one will be so too */ private void mergeWays() { - if (closedBorder != null) return; + if (border != null) return; - closeWays(); + List closedWays = closeWays(); - closedBorder = new Way(0L); + border = new Way(randId()); Long previousConnection = getMultiLineEndNodes(closedWays)[0]; @@ -113,9 +101,9 @@ public class Ring { // don't need to add the first node, that one was added by the previous way if (!firstNode) { if(nodes == null || i>=nodes.size()) { - closedBorder.addNode(nodeIds.get(i)); + border.addNode(nodeIds.get(i)); } else { - closedBorder.addNode(nodes.get(i)); + border.addNode(nodes.get(i)); } } @@ -130,9 +118,9 @@ public class Ring { // don't need to add the first node, that one was added by the previous way if (!firstNode) { if(nodes == null || i>=nodes.size()) { - closedBorder.addNode(nodeIds.get(i)); + border.addNode(nodeIds.get(i)); } else { - closedBorder.addNode(nodes.get(i)); + border.addNode(nodes.get(i)); } } firstNode = false; @@ -145,25 +133,27 @@ public class Ring { } + + + /** * Check if there exists a cache, if so, return it * If there isn't a cache, sort the ways to form connected strings

* * If a Ring contains a gap, one way (without initialized nodes and id=0) is added to the list */ - private void closeWays(){ - // If the ways have been closed, return the cache - if (closedWays != null) return; + private List closeWays(){ + List closedWays = new ArrayList(); if (ways.size() == 0) { closedWays = new ArrayList(); - return; + return closedWays; } closedWays = new ArrayList(ways); long[] endNodes = getMultiLineEndNodes(ways); if (endNodes[0] != endNodes[1]) { if(ways.get(0).getNodes() == null) { - Way w = new Way(0L); + Way w = new Way(randId()); w.addNode(endNodes[0]); w.addNode(endNodes[1]); closedWays.add(w); @@ -184,7 +174,7 @@ public class Ring { n2 = ways.get(lastML).getNodes().get(index); } - Way w = new Way(0L); + Way w = new Way(randId()); w.addNode(n1); w.addNode(n2); closedWays.add(w); @@ -193,10 +183,269 @@ public class Ring { - return; + return closedWays; } + + + /** + * Get the end nodes of a multiLine + * The ways in the multiLine don't have to be initialized for this. + * + * @param multiLine the multiLine to get the end nodes of + * @return an array of size two with the end nodes on both sides.
+ * * The first node is the end node of the first way in the multiLine.
+ * * The second node is the end node of the last way in the multiLine. + */ + private long[] getMultiLineEndNodes(List multiLine) { + + // special case, the multiLine contains only a single way, return the end nodes of the way + if (multiLine.size() == 1){ + return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getLastNodeId()}; + } + + if (multiLine.size() == 2) { + // ring of two elements, arbitrary choice of the end nodes + if(multiLine.get(0).getFirstNodeId() == multiLine.get(1).getFirstNodeId() && + multiLine.get(0).getLastNodeId() == multiLine.get(1).getLastNodeId()) { + return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getFirstNodeId()}; + } else if(multiLine.get(0).getFirstNodeId() == multiLine.get(1).getLastNodeId() && + multiLine.get(0).getLastNodeId() == multiLine.get(1).getFirstNodeId()) { + return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getFirstNodeId()}; + } + } + + // For all other multiLine lenghts, or for non-closed multiLines with two elements, proceed + + long n1 = 0, n2 = 0; + + if (multiLine.get(0).getFirstNodeId() == multiLine.get(1).getFirstNodeId() || + multiLine.get(0).getFirstNodeId() == multiLine.get(1).getLastNodeId()) { + n1 = multiLine.get(0).getLastNodeId(); + } else if (multiLine.get(0).getLastNodeId() == multiLine.get(1).getFirstNodeId() || + multiLine.get(0).getLastNodeId() == multiLine.get(1).getLastNodeId()) { + n1 = multiLine.get(0).getFirstNodeId(); + } + + int lastIdx = multiLine.size()-1; + + if (multiLine.get(lastIdx).getFirstNodeId() == multiLine.get(1).getFirstNodeId() || + multiLine.get(lastIdx).getFirstNodeId() == multiLine.get(1).getLastNodeId()) { + n2 = multiLine.get(lastIdx).getLastNodeId(); + } else if (multiLine.get(lastIdx).getLastNodeId() == multiLine.get(lastIdx - 1).getFirstNodeId() || + multiLine.get(lastIdx).getLastNodeId() == multiLine.get(lastIdx - 1).getLastNodeId()) { + n2 = multiLine.get(lastIdx).getFirstNodeId(); + } + + return new long[] {n1, n2}; + } + + + + /** + * check if this Ring contains the node + * @param n the Node to check + * @return yes if the node is inside the ring + */ + public boolean containsNode(Node n) { + return containsPoint(n.getLatitude(), n.getLongitude()); + } + + /** + * check if this Ring contains the point + * @param latitude lat of the point + * @param longitude lon of the point + * @return yes if the point is inside the ring + */ + public boolean containsPoint(double latitude, double longitude){ + return countIntersections(latitude, longitude) % 2 == 1; + } + + /** + * count the intersections when going from lat, lon to outside the ring + * @param latitude the lat to start + * @param longitude the lon to start + * @param intersections the number of intersections to start with + * @return the number of intersections + */ + private int countIntersections(double latitude, double longitude) { + int intersections = 0; + + mergeWays(); + List polyNodes = getBorder(); + for (int i = 0; i < polyNodes.size() - 1; i++) { + if (MapAlgorithms.ray_intersect_lon(polyNodes.get(i), + polyNodes.get(i + 1), latitude, longitude) != -360d) { + intersections++; + } + } + // special handling, also count first and last, might not be closed, but + // we want this! + if (MapAlgorithms.ray_intersect_lon(polyNodes.get(0), + polyNodes.get(polyNodes.size() - 1), latitude, longitude) != -360d) { + intersections++; + } + return intersections; + } + + /** + * collect the points of all ways added by the user
+ * automatically added ways because of closing the Ring won't be added
+ * Only ways with initialized points can be handled. + * @return a List with nodes + */ + public List collectPoints() { + + ArrayList collected = new ArrayList(); + + for (Way w : ways) { + collected.addAll(w.getNodes()); + } + + return collected; + + } + + /** + * Check if this is in Ring r + * @param r the ring to check + * @return true if this Ring is inside Ring r + */ + public boolean isIn(Ring r) { + /* + * bi-directional check is needed because some concave rings can intersect + * and would only fail on one of the checks + */ + List points = this.collectPoints(); + + // r should contain all nodes of this + for(Node n : points) { + if (!r.containsNode(n)) { + return false; + } + } + + points = r.collectPoints(); + + // this should not contain a node from r + for(Node n : points) { + if (this.containsNode(n)) { + return false; + } + } + + return true; + + } + + + /** + * If this Ring is not complete + * (some ways are not initialized + * because they are not included in the OSM file)

+ * + * We are trying to close this Ring by using the other Ring.

+ * + * The other Ring must be complete, and the part of this Ring + * inside the other Ring must also be complete. + * @param other the other Ring (which is complete) used to close this one + */ + public void closeWithOtherRing(Ring other) { + List thisBorder = getBorder(); + List thisSwitchPoints = new ArrayList(); + + boolean insideOther = other.containsNode(thisBorder.get(0)); + + // Search the node pairs for which the ring goes inside or out the other + for (int i = 0; i otherSwitchPoints = new ArrayList(); + + // Search the according node pairs in the other ring + for (int i : thisSwitchPoints) { + LatLon a = thisBorder.get(i-1).getLatLon(); + LatLon b = thisBorder.get(i).getLatLon(); + otherSwitchPoints.add(crossRingBorder(a, b)); + } + + + + /* + * TODO: + * + * * Split the other Ring into ways from splitPoint to splitPoint + * + * * Split this ring into ways from splitPoint to splitPoint + * + * * Filter out the parts of way from this that are inside the other Ring + * Use the insideOther var and the switchPoints list for this. + * + * * For each two parts of way from this, search a part of way connecting the two. + * If there are two, take the shortest. + */ + } + + /** + * Get the segment of the Ring that intersects a segment + * going from point a to point b + * + * @param a the begin point of the segment + * @param b the end point of the segment + * @return an integer i which is the index so that the segment + * from getBorder().get(i-1) to getBorder().get(i) intersects with + * the segment from parameters a to b.

+ * + * 0 if the segment from a to b doesn't intersect with the Ring. + */ + public int crossRingBorder(LatLon a, LatLon b) { + List border = getBorder(); + for (int i = 1; i + * 1 if r is smaller than this
+ * 0 if they have the same size + */ + public int compareTo(Ring r) { + double thisArea = getArea(); + double rArea = r.getArea(); + if (thisArea < rArea) return -1; + if (rArea < thisArea) return 1; + return 0; + } + /** * Join the ways in connected strings for further processing * @return A list with list of connected ways @@ -326,58 +575,6 @@ public class Ring { return multiLines; } - /** - * Get the end nodes of a multiLine - * The ways in the multiLine don't have to be initialized for this. - * - * @param multiLine the multiLine to get the end nodes of - * @return an array of size two with the end nodes on both sides.
- * * The first node is the end node of the first way in the multiLine.
- * * The second node is the end node of the last way in the multiLine. - */ - private long[] getMultiLineEndNodes(ArrayList multiLine) { - - // special case, the multiLine contains only a single way, return the end nodes of the way - if (multiLine.size() == 1){ - return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getLastNodeId()}; - } - - if (multiLine.size() == 2) { - // ring of two elements, arbitrary choice of the end nodes - if(multiLine.get(0).getFirstNodeId() == multiLine.get(1).getFirstNodeId() && - multiLine.get(0).getLastNodeId() == multiLine.get(1).getLastNodeId()) { - return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getFirstNodeId()}; - } else if(multiLine.get(0).getFirstNodeId() == multiLine.get(1).getLastNodeId() && - multiLine.get(0).getLastNodeId() == multiLine.get(1).getFirstNodeId()) { - return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getFirstNodeId()}; - } - } - - // For all other multiLine lenghts, or for non-closed multiLines with two elements, proceed - - long n1 = 0, n2 = 0; - - if (multiLine.get(0).getFirstNodeId() == multiLine.get(1).getFirstNodeId() || - multiLine.get(0).getFirstNodeId() == multiLine.get(1).getLastNodeId()) { - n1 = multiLine.get(0).getLastNodeId(); - } else if (multiLine.get(0).getLastNodeId() == multiLine.get(1).getFirstNodeId() || - multiLine.get(0).getLastNodeId() == multiLine.get(1).getLastNodeId()) { - n1 = multiLine.get(0).getFirstNodeId(); - } - - int lastIdx = multiLine.size()-1; - - if (multiLine.get(lastIdx).getFirstNodeId() == multiLine.get(1).getFirstNodeId() || - multiLine.get(lastIdx).getFirstNodeId() == multiLine.get(1).getLastNodeId()) { - n2 = multiLine.get(lastIdx).getLastNodeId(); - } else if (multiLine.get(lastIdx).getLastNodeId() == multiLine.get(lastIdx - 1).getFirstNodeId() || - multiLine.get(lastIdx).getLastNodeId() == multiLine.get(lastIdx - 1).getLastNodeId()) { - n2 = multiLine.get(lastIdx).getFirstNodeId(); - } - - return new long[] {n1, n2}; - } - /** * Combine a list of ways to a list of rings * @@ -400,185 +597,11 @@ public class Ring { } /** - * check if this Ring contains the node - * @param n the Node to check - * @return yes if the node is inside the ring + * get a random long number + * @return */ - public boolean containsNode(Node n) { - return containsPoint(n.getLatitude(), n.getLongitude()); - } - - /** - * check if this Ring contains the point - * @param latitude lat of the point - * @param longitude lon of the point - * @return yes if the point is inside the ring - */ - public boolean containsPoint(double latitude, double longitude){ - return countIntersections(latitude, longitude) % 2 == 1; - } - - /** - * count the intersections when going from lat, lon to outside the ring - * @param latitude the lat to start - * @param longitude the lon to start - * @param intersections the number of intersections to start with - * @return the number of intersections - */ - private int countIntersections(double latitude, double longitude) { - int intersections = 0; - - mergeWays(); - List polyNodes = closedBorder.getNodes(); - for (int i = 0; i < polyNodes.size() - 1; i++) { - if (MapAlgorithms.ray_intersect_lon(polyNodes.get(i), - polyNodes.get(i + 1), latitude, longitude) != -360d) { - intersections++; - } - } - // special handling, also count first and last, might not be closed, but - // we want this! - if (MapAlgorithms.ray_intersect_lon(polyNodes.get(0), - polyNodes.get(polyNodes.size() - 1), latitude, longitude) != -360d) { - intersections++; - } - return intersections; - } - - /** - * collect the points of all ways added by the user
- * automatically added ways because of closing the Ring won't be added
- * Only ways with initialized points can be handled. - * @return a List with nodes - */ - public List collectPoints() { - - ArrayList collected = new ArrayList(); - - for (Way w : ways) { - collected.addAll(w.getNodes()); - } - - return collected; - - } - - /** - * Check if this is in Ring r - * @param r the ring to check - * @return true if this Ring is inside Ring r - */ - public boolean isIn(Ring r) { - /* - * bi-directional check is needed because some concave rings can intersect - * and would only fail on one of the checks - */ - List points = this.collectPoints(); - - // r should contain all nodes of this - for(Node n : points) { - if (!r.containsNode(n)) { - return false; - } - } - - points = r.collectPoints(); - - // this should not contain a node from r - for(Node n : points) { - if (this.containsNode(n)) { - return false; - } - } - - return true; - - } - - - /** - * If this Ring is not complete - * (some ways are not initialized - * because they are not included in the OSM file)

- * - * We are trying to close this Ring by using the other Ring.

- * - * The other Ring must be complete, and the part of this Ring - * inside the other Ring must also be complete. - * @param other the other Ring (which is complete) used to close this one - */ - public void closeWithOtherRing(Ring other) { - Way thisBorder = getBorder(); - List thisSwitchPoints = new ArrayList(); - - boolean insideOther = other.containsNode(thisBorder.getNodes().get(0)); - - // Search the node pairs for which the ring goes inside or out the other - for (int i = 0; i otherSwitchPoints = new ArrayList(); - - // Search the according node pairs in the other ring - for (int i : thisSwitchPoints) { - LatLon a = thisBorder.getNodes().get(i-1).getLatLon(); - LatLon b = thisBorder.getNodes().get(i).getLatLon(); - otherSwitchPoints.add(crossRingBorder(a, b)); - } - - - - /* - * TODO: - * - * * Split the other Ring into ways from splitPoint to splitPoint - * - * * Split this ring into ways from splitPoint to splitPoint - * - * * Filter out the parts of way from this that are inside the other Ring - * Use the insideOther var and the switchPoints list for this. - * - * * For each two parts of way from this, search a part of way connecting the two. - * If there are two, take the shortest. - */ - } - - /** - * Get the segment of the Ring that intersects a segment - * going from point a to point b - * - * @param a the begin point of the segment - * @param b the end point of the segment - * @return an integer i which is the index so that the segment - * from getBorder().get(i-1) to getBorder().get(i) intersects with - * the segment from parameters a to b.

- * - * 0 if the segment from a to b doesn't intersect with the Ring. - */ - public int crossRingBorder(LatLon a, LatLon b) { - Way border = getBorder(); - for (int i = 1; i> innerWays = new ArrayList>(); for (Ring r : m.getInnerRings()) { - innerWays.add(r.getBorder().getNodes()); + innerWays.add(r.getBorder()); } // don't use the relation ids. Create new ones