From 611e16ecf167b79c4bfcb5e39fc897c07a4a75b2 Mon Sep 17 00:00:00 2001 From: Sander Deryckere Date: Mon, 17 Sep 2012 12:36:26 +0200 Subject: [PATCH 1/5] 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 From e2877d35194aea799f2469eb6e91baa44f812b36 Mon Sep 17 00:00:00 2001 From: Sander Deryckere Date: Tue, 18 Sep 2012 11:06:12 +0200 Subject: [PATCH 2/5] Rings simplify cache, remove obsolete methods and get rid of some unexpected behaviours --- .../src/net/osmand/data/Multipolygon.java | 27 +- .../src/net/osmand/data/Ring.java | 355 +++++++++--------- 2 files changed, 204 insertions(+), 178 deletions(-) diff --git a/DataExtractionOSM/src/net/osmand/data/Multipolygon.java b/DataExtractionOSM/src/net/osmand/data/Multipolygon.java index eed1d4d21b..ac50e84853 100644 --- a/DataExtractionOSM/src/net/osmand/data/Multipolygon.java +++ b/DataExtractionOSM/src/net/osmand/data/Multipolygon.java @@ -105,23 +105,34 @@ public class Multipolygon { * @return true if this multipolygon is correct and contains the point */ public boolean containsPoint(double latitude, double longitude) { - boolean outerContain = false; - for (Ring outer : getOuterRings()) { + Ring containedInOuter = null; + // use a sortedset to get the smallest outer containing the point + SortedSet outers = new TreeSet (getOuterRings()); + for (Ring outer : outers) { if (outer.containsPoint(latitude, longitude)) { - outerContain = true; + containedInOuter = outer; break; } } - if (!outerContain) { + + if (containedInOuter == null) { return false; } - for (Ring inner : getInnerRings()) { + + //use a sortedSet to get the smallest inner Ring + SortedSet inners = new TreeSet (getInnerRings()); + Ring containedInInner = null; + for (Ring inner : inners) { if (inner.containsPoint(latitude, longitude)) { - return false; + containedInInner = inner; + break; } } - return true; + if (containedInInner == null) return true; + + // if it is both, in an inner and in an outer, check if the inner is indeed the smallest one + return !containedInInner.isIn(containedInOuter); } /** @@ -331,7 +342,7 @@ public class Multipolygon { } /** - * This method only works when the multipolygon has exaclt one outer Ring + * This method only works when the multipolygon has exactly one outer Ring * @return the list of nodes in the outer ring */ public List getOuterNodes() { diff --git a/DataExtractionOSM/src/net/osmand/data/Ring.java b/DataExtractionOSM/src/net/osmand/data/Ring.java index f9d110720f..03f720b4aa 100644 --- a/DataExtractionOSM/src/net/osmand/data/Ring.java +++ b/DataExtractionOSM/src/net/osmand/data/Ring.java @@ -1,6 +1,5 @@ package net.osmand.data; -import gnu.trove.list.array.TLongArrayList; import java.util.ArrayList; import java.util.List; @@ -26,7 +25,7 @@ public class Ring implements Comparable { /** * a concatenation of the ways to form the border - * this is not necessarily a closed way + * this is NOT necessarily a CLOSED way * The id is random, so this may never leave the Ring object */ private Way border; @@ -49,7 +48,7 @@ public class Ring implements Comparable { * @return the ways added to the Ring */ public List getWays() { - return ways; + return new ArrayList(ways); } @@ -66,182 +65,124 @@ public class Ring implements Comparable { * get a single closed way that represents the border * this method is CPU intensive * @return a list of Nodes that represents the border + * if the border can't be created, an empty list will be returned */ public List getBorder() { mergeWays(); List l = border.getNodes(); - if (!isClosed()) { + if (border.getNodes().size() != 0 && !isClosed()) { l.add(border.getNodes().get(0)); } - return l; } /** - * Merge all ways from the closedways into a single way - * If the original ways are initialized with nodes, the new one will be so too + * Merge all ways from the into a single border way + * If the original ways are initialized with nodes, the border will be so too + * If the original ways aren't initialized with nodes, the border won't be either + * If only some original ways are initialized with nodes, the border will only have the nodes of the initialized ways */ private void mergeWays() { if (border != null) return; - List closedWays = closeWays(); - border = new Way(randId()); - Long previousConnection = getMultiLineEndNodes(closedWays)[0]; + //make a copy of the ways + List ways = new ArrayList(getWays()); - for (Way w : closedWays) { - boolean firstNode = true; - TLongArrayList nodeIds = w.getNodeIds(); - List nodes = w.getNodes(); + // do we have to include ways with uninitialized nodes? + // Only if all ways have uninitialized nodes + boolean unInitializedNodes = true; + + for (Way w : ways) { + if (w.getNodes() != null && w.getNodes().size() != 0) { + unInitializedNodes = false; + break; + } + } + + List borderWays = new ArrayList(); + + + for (Way w : ways) { + // if the way has no nodes initialized, and we should initialize them, continue + if ((w.getNodes() == null || w.getNodes().size() == 0) && + !unInitializedNodes) continue; - if (w.getFirstNodeId() == previousConnection) { - - for (int i = 0; i< nodeIds.size(); i++) { - // don't need to add the first node, that one was added by the previous way - if (!firstNode) { - if(nodes == null || i>=nodes.size()) { - border.addNode(nodeIds.get(i)); - } else { - border.addNode(nodes.get(i)); - } - - } - firstNode = false; + Way newWay = null; + Way addedTo = null; + + // merge the Way w with the first borderway suitable; + for (Way borderWay : borderWays) { + if (w.getFirstNodeId() == borderWay.getFirstNodeId()) { + newWay = combineTwoWays(w, borderWay, true, true); + addedTo = borderWay; + break; + } else if (w.getFirstNodeId() == borderWay.getLastNodeId()) { + newWay = combineTwoWays(w, borderWay, true, false); + addedTo = borderWay; + break; + } else if (w.getLastNodeId() == borderWay.getLastNodeId()) { + newWay = combineTwoWays(w, borderWay, false, false); + addedTo = borderWay; + break; + } else if (w.getLastNodeId() == borderWay.getFirstNodeId()) { + newWay = combineTwoWays(w, borderWay, false, true); + addedTo = borderWay; + break; } - - previousConnection = w.getLastNodeId(); + } + + if (newWay == null) { + // no suitable borderWay has been found, add this way as one of the boundaries + borderWays.add(w); } else { + // ways are combined, remove the original borderway + borderWays.remove(addedTo); - // add the nodes in reverse order - for (int i = nodeIds.size() - 1; i >= 0; i--) { - // don't need to add the first node, that one was added by the previous way - if (!firstNode) { - if(nodes == null || i>=nodes.size()) { - border.addNode(nodeIds.get(i)); - } else { - border.addNode(nodes.get(i)); - } + addedTo = null; + // search if it can be combined with something else + for (Way borderWay : borderWays) { + if (newWay.getFirstNodeId() == borderWay.getFirstNodeId()) { + newWay = combineTwoWays(newWay, borderWay, true, true); + addedTo = borderWay; + break; + } else if (newWay.getFirstNodeId() == borderWay.getLastNodeId()) { + newWay = combineTwoWays(newWay, borderWay, true, false); + addedTo = borderWay; + break; + } else if (newWay.getLastNodeId() == borderWay.getLastNodeId()) { + newWay = combineTwoWays(newWay, borderWay, false, false); + addedTo = borderWay; + break; + } else if (newWay.getLastNodeId() == borderWay.getFirstNodeId()) { + newWay = combineTwoWays(newWay, borderWay, false, true); + addedTo = borderWay; + break; } - firstNode = false; } - previousConnection = w.getFirstNodeId(); + if (addedTo != null) { + // newWay has enlarged a second time + borderWays.remove(addedTo); + } + // newWay is now a concatenation of 2 or 3 ways, needs to be added to the borderWays + borderWays.add(newWay); } + } + if (borderWays.size() != 1) { + border = new Way(randId()); + return; + } + + border = borderWays.get(0); + + return; + } - - - - - /** - * 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 List closeWays(){ - List closedWays = new ArrayList(); - if (ways.size() == 0) { - closedWays = new ArrayList(); - 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(randId()); - w.addNode(endNodes[0]); - w.addNode(endNodes[1]); - closedWays.add(w); - } else { - Node n1 = null, n2 = null; - if (ways.get(0).getFirstNodeId() == endNodes[0]) { - n1 = ways.get(0).getNodes().get(0); - } else { - int index = ways.get(0).getNodes().size() - 1; - n1 = ways.get(0).getNodes().get(index); - } - - int lastML = ways.size() - 1; - if (ways.get(lastML).getFirstNodeId() == endNodes[0]) { - n2 = ways.get(lastML).getNodes().get(0); - } else { - int index = ways.get(lastML).getNodes().size() - 1; - n2 = ways.get(lastML).getNodes().get(index); - } - - Way w = new Way(randId()); - w.addNode(n1); - w.addNode(n2); - closedWays.add(w); - } - } - - - - 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 @@ -272,7 +213,6 @@ public class Ring implements Comparable { 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), @@ -289,23 +229,6 @@ public class Ring implements Comparable { 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 @@ -317,7 +240,7 @@ public class Ring implements Comparable { * bi-directional check is needed because some concave rings can intersect * and would only fail on one of the checks */ - List points = this.collectPoints(); + List points = this.getBorder(); // r should contain all nodes of this for(Node n : points) { @@ -326,7 +249,7 @@ public class Ring implements Comparable { } } - points = r.collectPoints(); + points = r.getBorder(); // this should not contain a node from r for(Node n : points) { @@ -603,5 +526,97 @@ public class Ring implements Comparable { private static long randId() { return Math.round(Math.random()*Long.MIN_VALUE); } + + /** + * make a new Way with the nodes from two other ways + * @param w1 the first way + * @param w2 the second way + * @param firstNodeW1 set true if the first node of w1 is also in the other way + * @param firstNodeW2 set true if the first node of w2 is also in the other way + */ + private static Way combineTwoWays(Way w1, Way w2, boolean firstNodeW1, boolean firstNodeW2) { + Way newWay = new Way(randId()); + if(w1.getNodes() != null || w1.getNodes().size() != 0) { + if (firstNodeW1 && firstNodeW2) { + // add the nodes of w1 in reversed order, without the first node + for (int i = w1.getNodes().size() - 1; i>0; i--) { + newWay.addNode(w1.getNodes().get(i)); + } + //add the nodes from w2 + for (Node n : w2.getNodes()) { + newWay.addNode(n); + } + } else if (firstNodeW1 && !firstNodeW2) { + // add all nodes from w2 + for (Node n : w2.getNodes()) { + newWay.addNode(n); + } + // add the nodes from w1, except the first one + for (int i = 1; i < w1.getNodes().size(); i++) { + newWay.addNode(w1.getNodes().get(i)); + } + } else if (!firstNodeW1 && firstNodeW2) { + // add all nodes from w1 + for (Node n : w1.getNodes()) { + newWay.addNode(n); + } + // add the nodes from w2, except the first one + for (int i = 1; i < w2.getNodes().size(); i++) { + newWay.addNode(w2.getNodes().get(i)); + } + } else if (!firstNodeW1 && !firstNodeW2) { + // add all nodes from w1 + for (Node n : w1.getNodes()) { + newWay.addNode(n); + } + // add the nodes from w2 in reversed order, except the last one + for (int i = w2.getNodes().size() -2 ; i >= 0; i--) { + newWay.addNode(w2.getNodes().get(i)); + } + } + } else { + if (firstNodeW1 && firstNodeW2) { + // add the nodes of w1 in reversed order, without the first node + for (int i = w1.getNodeIds().size() - 1; i>0; i--) { + newWay.addNode(w1.getNodeIds().get(i)); + } + //add the nodes from w2 + for (int i = 0; i < w2.getNodeIds().size(); i++) { + newWay.addNode(w2.getNodeIds().get(i)); + } + } else if (firstNodeW1 && !firstNodeW2) { + // add all nodes from w2 + for (int i = 0; i < w2.getNodeIds().size(); i++) { + newWay.addNode(w2.getNodeIds().get(i)); + } + // add the nodes from w1, except the first one + for (int i = 1; i < w1.getNodeIds().size(); i++) { + newWay.addNode(w1.getNodeIds().get(i)); + } + } else if (!firstNodeW1 && firstNodeW2) { + // add all nodes from w1 + for (int i = 0; i < w1.getNodeIds().size(); i++) { + newWay.addNode(w1.getNodeIds().get(i)); + } + // add the nodes from w2, except the first one + for (int i = 1; i < w2.getNodeIds().size(); i++) { + newWay.addNode(w2.getNodeIds().get(i)); + } + } else if (!firstNodeW1 && !firstNodeW2) { + // add all nodes from w1 + for (int i = 0; i < w1.getNodeIds().size(); i++) { + newWay.addNode(w1.getNodeIds().get(i)); + } + // add the nodes from w2 in reversed order, except the last one + for (int i = w2.getNodeIds().size() -2 ; i >= 0; i--) { + newWay.addNode(w2.getNodeIds().get(i)); + } + } + } + + return newWay; + + } + } From 2d69e80d57d5ad292b4eab1843222b226bf96689 Mon Sep 17 00:00:00 2001 From: Sander Deryckere Date: Tue, 18 Sep 2012 12:08:10 +0200 Subject: [PATCH 3/5] Fix Junit Multipolygon test and make a new one Small changes --- .../net/osmand/data/MultipolygonTest.java | 24 ++++++++++++++++++- .../src/net/osmand/data/Multipolygon.java | 1 + .../src/net/osmand/data/Ring.java | 4 ++-- .../preparation/IndexVectorMapCreator.java | 11 +++++---- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/DataExtractionOSM/src-tests/net/osmand/data/MultipolygonTest.java b/DataExtractionOSM/src-tests/net/osmand/data/MultipolygonTest.java index 200df51c65..5a6c4c57be 100644 --- a/DataExtractionOSM/src-tests/net/osmand/data/MultipolygonTest.java +++ b/DataExtractionOSM/src-tests/net/osmand/data/MultipolygonTest.java @@ -2,6 +2,7 @@ package net.osmand.data; import static org.junit.Assert.*; + import net.osmand.osm.LatLon; import net.osmand.osm.Node; import net.osmand.osm.Way; @@ -65,6 +66,27 @@ public class MultipolygonTest { assertEquals(1, testee.countOuterPolygons()); assertFalse(testee.hasOpenedPolygons()); } + + @Test + public void test_ringArea(){ + Way w = new Way(0L); + + w.addNode(new Node(0.0, 0.0, 1)); + w.addNode(new Node(1.0, 0.0, 2)); + w.addNode(new Node(1.0, 0.5, 3)); + w.addNode(new Node(1.5, 0.5, 4)); + w.addNode(new Node(1.0, 1.0, 5)); + + Multipolygon m = new Multipolygon(); + m.addOuterWay(w); + + Ring r = m.getOuterRings().get(0); + // calculated with JOSM measurement tool + double expected = 7716818755.73; + // allow 1% deviation because of rounding errors and alternative projections + assertTrue(expected/r.getArea() > 0.99); + assertTrue(expected/r.getArea() < 1.01); + } @Test public void test_oneWayPolygon() { @@ -174,7 +196,7 @@ public class MultipolygonTest { testee.addOuterWay(new Way(111)); testee.addOuterWay(poly1_1_of_2); assertEquals(1, testee.countOuterPolygons()); - // FIXME assertTrue(testee.hasOpenedPolygons()); + assertTrue(testee.hasOpenedPolygons()); } @Test diff --git a/DataExtractionOSM/src/net/osmand/data/Multipolygon.java b/DataExtractionOSM/src/net/osmand/data/Multipolygon.java index ac50e84853..bd59ae1271 100644 --- a/DataExtractionOSM/src/net/osmand/data/Multipolygon.java +++ b/DataExtractionOSM/src/net/osmand/data/Multipolygon.java @@ -308,6 +308,7 @@ public class Multipolygon { ArrayList inners = new ArrayList(getInnerRings()); // get the set of outer rings in a variable. This set will not be changed + // sort it to start with the smallest SortedSet outers = new TreeSet(getOuterRings()); ArrayList multipolygons = new ArrayList(); diff --git a/DataExtractionOSM/src/net/osmand/data/Ring.java b/DataExtractionOSM/src/net/osmand/data/Ring.java index 03f720b4aa..60e284cb76 100644 --- a/DataExtractionOSM/src/net/osmand/data/Ring.java +++ b/DataExtractionOSM/src/net/osmand/data/Ring.java @@ -381,7 +381,7 @@ public class Ring implements Comparable { /* * Check if the way has at least 2 nodes * - * FIXME TO LOG OR NOT TO LOG? + * TO LOG OR NOT TO LOG? * * logging this creates a whole bunch of log lines for all ways * part of a multipolygon but not in the map @@ -499,7 +499,7 @@ public class Ring implements Comparable { } /** - * Combine a list of ways to a list of rings + * Combine a list of ways into a list of rings * * The ways must not have initialized nodes for this * diff --git a/DataExtractionOSM/src/net/osmand/data/preparation/IndexVectorMapCreator.java b/DataExtractionOSM/src/net/osmand/data/preparation/IndexVectorMapCreator.java index bf4a372b42..76870b884e 100644 --- a/DataExtractionOSM/src/net/osmand/data/preparation/IndexVectorMapCreator.java +++ b/DataExtractionOSM/src/net/osmand/data/preparation/IndexVectorMapCreator.java @@ -147,11 +147,6 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator { } } - // Log if something is wrong - if (!original.hasOpenedPolygons()) { - logMapDataWarn.warn("Multipolygon has unclosed parts: Multipoligon id=" + e.getId()); //$NON-NLS-1$ //$NON-NLS-2$ - } - renderingTypes.encodeEntityWithType(e, mapZooms.getLevel(0).getMaxZoom(), typeUse, addtypeUse, namesUse, tempNameUse); //Don't add multipolygons with an unknown type @@ -168,6 +163,12 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator { for (Multipolygon m : multipolygons) { + + if(m.getOuterNodes().size() == 0) { + logMapDataWarn.warn("Multipolygon has an outer ring that can't be formed: "+e.getId()); + // don't index this + continue; + } // innerWays are new closed ways List> innerWays = new ArrayList>(); From a6c5950d31e438482581b9a2b8f856f85daad821 Mon Sep 17 00:00:00 2001 From: Sander Deryckere Date: Tue, 18 Sep 2012 12:53:31 +0200 Subject: [PATCH 4/5] fix boundary indexing outOfRange error --- DataExtractionOSM/src/net/osmand/data/Ring.java | 1 + 1 file changed, 1 insertion(+) diff --git a/DataExtractionOSM/src/net/osmand/data/Ring.java b/DataExtractionOSM/src/net/osmand/data/Ring.java index 60e284cb76..d6ac790ee6 100644 --- a/DataExtractionOSM/src/net/osmand/data/Ring.java +++ b/DataExtractionOSM/src/net/osmand/data/Ring.java @@ -214,6 +214,7 @@ public class Ring implements Comparable { int intersections = 0; List polyNodes = getBorder(); + if (polyNodes.size() == 0) return 0; for (int i = 0; i < polyNodes.size() - 1; i++) { if (MapAlgorithms.ray_intersect_lon(polyNodes.get(i), polyNodes.get(i + 1), latitude, longitude) != -360d) { From 2f74329bd6deeea63d135da797b5109a7434fc6f Mon Sep 17 00:00:00 2001 From: Sander Deryckere Date: Tue, 18 Sep 2012 13:01:15 +0200 Subject: [PATCH 5/5] cache areas of Rings --- DataExtractionOSM/src/net/osmand/data/Ring.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/DataExtractionOSM/src/net/osmand/data/Ring.java b/DataExtractionOSM/src/net/osmand/data/Ring.java index d6ac790ee6..4ef1d2e1fd 100644 --- a/DataExtractionOSM/src/net/osmand/data/Ring.java +++ b/DataExtractionOSM/src/net/osmand/data/Ring.java @@ -30,6 +30,11 @@ public class Ring implements Comparable { */ private Way border; + /** + * area can be asked a lot of times when comparing rings, chace it + */ + private double area = -1; + /** @@ -349,7 +354,11 @@ public class Ring implements Comparable { } public double getArea() { - return MapAlgorithms.getArea(getBorder()); + if (area == -1) { + //cache the area + area = MapAlgorithms.getArea(getBorder()); + } + return area; }