Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (revision 1505) +++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (working copy) @@ -1,9 +1,11 @@ package uk.me.parabola.mkgmap.reader.osm; -import java.awt.*; +import java.awt.Polygon; +import java.awt.Rectangle; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; +import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -337,7 +339,7 @@ log.info("from", way.getPoints().get(0).toOSMURL()); log.info("to", way.getPoints().get(way.getPoints().size() - 1) .toOSMURL()); - // mark this ways as artificially closed + // mark this ways as artifically closed way.closeWayArtificial(); } } @@ -357,8 +359,9 @@ JoinedWay tempWay = it.next(); if (tempWay.isClosed() == false) { if (first) { - log.warn( - "Cannot join the following ways to closed polygons. MP-Relation", + log + .warn( + "Cannot join the following ways to closed polygons. MP-Relation", toBrowseURL()); } logWayURLs(Level.WARNING, "- way:", tempWay); @@ -810,7 +813,9 @@ */ private Way singularAreaToWay(Area area, long wayId) { if (area.isSingular() == false) { - log.warn("singularAreaToWay called with non singular area. Multipolygon ", + log + .warn( + "singularAreaToWay called with non singular area. Multipolygon ", toBrowseURL()); } if (area.isEmpty()) { @@ -870,6 +875,9 @@ /** * This is a QA method. All ways of the given wayList are checked if they * they carry the checkRole. If not a warning is logged. + * + * @param wayList + * @param checkRoles */ private void checkRoles(List wayList, String checkRole) { // QA: check that all ways carry the role "inner" and issue warnings @@ -886,37 +894,83 @@ * Creates a matrix which polygon contains which polygon. A polygon does not * contain itself. * - * @param poplygonList + * @param polygonList * a list of polygons */ - private void createContainsMatrix(List poplygonList) { + private void createContainsMatrix(List polygonList) { containsMatrix = new ArrayList(); - for (int i = 0; i < poplygonList.size(); i++) { + for (int i = 0; i < polygonList.size(); i++) { containsMatrix.add(new BitSet()); } - // mark which ring contains which ring - for (int i = 0; i < poplygonList.size(); i++) { - JoinedWay tempRing = poplygonList.get(i); - BitSet bitSet = containsMatrix.get(i); + long t1 = System.currentTimeMillis(); + + if (log.isDebugEnabled()) + log.debug("createContainsMatrix listSize:", polygonList.size()); - for (int j = 0; j < poplygonList.size(); j++) { - boolean contains; - if (i == j) { - // this is special: a way does not contain itself for - // our usage here - contains = false; + // use this matrix to check which matrix element has been + // calculated + ArrayList finishedMatrix = new ArrayList(polygonList + .size()); + + for (int i = 0; i < polygonList.size(); i++) { + BitSet matrixRow = new BitSet(); + // a polygon does not contain itself + matrixRow.set(i); + finishedMatrix.add(matrixRow); + } + + for (int rowIndex = 0; rowIndex < polygonList.size(); rowIndex++) { + JoinedWay potentialOuterRing = polygonList.get(rowIndex); + BitSet containsColumns = containsMatrix.get(rowIndex); + BitSet finishedCol = finishedMatrix.get(rowIndex); + + if (log.isDebugEnabled()) + log.debug("check polygon", rowIndex); + + // get all non calculated columns of the matrix + for (int colIndex = finishedCol.nextClearBit(0); colIndex >= 0 + && colIndex < polygonList.size(); colIndex = finishedCol + .nextClearBit(colIndex + 1)) { + + JoinedWay innerPolygon = polygonList.get(colIndex); + + if (potentialOuterRing.getBounds().intersects( + innerPolygon.getBounds()) == false) { + // both polygons do not intersect + // we can flag both matrix elements as finished + finishedMatrix.get(colIndex).set(rowIndex); + finishedMatrix.get(rowIndex).set(colIndex); } else { - contains = contains(tempRing, poplygonList.get(j)); - } + boolean contains = contains(potentialOuterRing, + innerPolygon); + + if (contains) { + containsColumns.set(colIndex); + + // we also know that the inner ring does not contain the + // outer ring + // so we can set the finished bit for this matrix + // element + finishedMatrix.get(colIndex).set(rowIndex); - if (contains) { - bitSet.set(j); + // additionally we know that the outer ring contains all + // rings + // that are contained by the inner ring + containsColumns.or(containsMatrix.get(colIndex)); + finishedCol.or(containsColumns); + } } + // this matrix element is calculated now + finishedCol.set(colIndex); } } if (log.isDebugEnabled()) { + long t2 = System.currentTimeMillis(); + log.debug("createMatrix for", polygonList.size(), "polygons took", + (t2 - t1), "ms"); + log.debug("Containsmatrix"); for (BitSet b : containsMatrix) { log.debug(b); @@ -924,62 +978,6 @@ } } - /* - * this is an alternative createContainsMatrix method seems to speed up only - * if lots of inner ways are in the mulitpolygon - */ - // private void createContainsMatrix(List wayList) { - // long t1 = System.currentTimeMillis(); - // - // for (int i = 0; i < wayList.size(); i++) { - // containsMatrix.add(new BitSet()); - // } - // - // // use this matrix to check which matrix element has been - // // calculated - // ArrayList finishedMatrix = new ArrayList(wayList.size()); - // - // for (int i = 0; i < wayList.size(); i++) { - // BitSet matrixRow = new BitSet(); - // // an element does not contain itself - // matrixRow.set(i); - // finishedMatrix.add(matrixRow); - // } - // - // for (int rowIndex = 0; rowIndex < wayList.size(); rowIndex++) { - // Way potentialOuterRing = wayList.get(rowIndex); - // BitSet containsColumns = containsMatrix.get(rowIndex); - // BitSet finishedCol = finishedMatrix.get(rowIndex); - // - // // get all non calculated columns of the matrix - // for (int colIndex = finishedCol.nextClearBit(0); colIndex >= 0 && - // colIndex < wayList.size(); colIndex = finishedCol - // .nextClearBit(colIndex + 1)) { - // - // boolean contains = contains(potentialOuterRing, wayList.get(colIndex)); - // - // if (contains) { - // containsColumns.set(colIndex); - // - // // we also know that the inner ring does not contain the outer ring - // // so we can set the finished bit for this matrix element - // finishedMatrix.get(colIndex).set(rowIndex); - // - // // additionally we know that the outer ring contains all rings - // // that are contained by the inner ring - // containsColumns.or(containsMatrix.get(colIndex)); - // finishedCol.or(containsColumns); - // } - // - // // this matrix element is calculated now - // finishedCol.set(colIndex); - // } - // } - // - // long t2 = System.currentTimeMillis(); - // log.warn("createMatrix",(t2-t1),"ms"); - // } - /** * Checks if the ring with ringIndex1 contains the ring with ringIndex2. * @@ -994,7 +992,7 @@ * * @param ring1 * a closed way - * @param ring2 A second closed way. + * @param ring2 * @return true if ring1 contains ring2 */ private boolean contains(JoinedWay ring1, JoinedWay ring2) { @@ -1004,6 +1002,7 @@ if (ring1.isClosed() == false) { return false; } + Polygon p = createPolygon(ring1.getPoints()); Coord p0 = ring2.getPoints().get(0); @@ -1012,26 +1011,80 @@ // way2 return false; } + // TODO check if p0 is on any of the lines of ring2 + + Iterator it1 = ring1.getPoints().iterator(); + Coord p1_1 = it1.next(); + Coord p1_2 = null; + + while (it1.hasNext()) { + p1_2 = p1_1; + p1_1 = it1.next(); + + if (ring2.linePossiblyIntersectsWay(p1_1, p1_2) == false) { + // don't check it - this segment of the outer polygon + // definitely does not intersect the way + continue; + } + + int lonMin = Math.min(p1_1.getLongitude(), p1_2.getLongitude()); + int lonMax = Math.max(p1_1.getLongitude(), p1_2.getLongitude()); + int latMin = Math.min(p1_1.getLatitude(), p1_2.getLatitude()); + int latMax = Math.max(p1_1.getLatitude(), p1_2.getLatitude()); + + // check all lines of way1 and way2 for intersections + Iterator it2 = ring2.getPoints().iterator(); + Coord p2_1 = it2.next(); + Coord p2_2 = null; + + // for speedup we divide the area around the second line into + // a 3x3 matrix with lon(-1,0,1) and lat(-1,0,1). + // -1 means below min lon/lat of bbox line p1_1-p1_2 + // 0 means inside the bounding box of the line p1_1-p1_2 + // 1 means above max lon/lat of bbox line p1_1-p1_2 + int lonField = p2_1.getLongitude() < lonMin ? -1 : p2_1 + .getLongitude() > lonMax ? 1 : 0; + int latField = p2_1.getLatitude() < latMin ? -1 : p2_1 + .getLatitude() > latMax ? 1 : 0; + + int prevLonField = lonField; + int prevLatField = latField; + + while (it2.hasNext()) { + p2_2 = p2_1; + p2_1 = it2.next(); - // check all lines of way1 and way2 for intersections - Iterator it2 = ring2.getPoints().iterator(); - Coord p2_1 = it2.next(); - while (it2.hasNext()) { - Coord p2_2 = p2_1; - p2_1 = it2.next(); + int changes = 0; + // check if the field of the 3x3 matrix has changed + if ((lonField >= 0 && p1_1.getLongitude() < lonMin) + || (lonField <= 0 && p1_1.getLongitude() > lonMax)) { + changes++; + lonField = p1_1.getLongitude() < lonMin ? -1 : p1_1 + .getLongitude() > lonMax ? 1 : 0; + } + if ((latField >= 0 && p1_1.getLatitude() < latMin) + || (latField <= 0 && p1_1.getLatitude() > latMax)) { + changes++; + latField = p1_1.getLatitude() < latMin ? -1 : p1_1 + .getLatitude() > latMax ? 1 : 0; + } - Iterator it1 = ring1.getPoints().iterator(); - Coord p1_1 = it1.next(); - while (it1.hasNext()) { - Coord p1_2 = p1_1; - p1_1 = it1.next(); + // an intersection is possible if + // latField and lonField has changed + // or if we come from or go to the inner matrix field + boolean intersectionPossible = (changes == 2) + || (latField == 0 && lonField == 0) + || (prevLatField == 0 && prevLonField == 0); - boolean intersects = Line2D.linesIntersect(p1_1.getLongitude(), - p1_1.getLatitude(), p1_2.getLongitude(), p1_2 + boolean intersects = intersectionPossible + && Line2D.linesIntersect(p1_1.getLongitude(), p1_1 + .getLatitude(), p1_2.getLongitude(), p1_2 .getLatitude(), p2_1.getLongitude(), p2_1 .getLatitude(), p2_2.getLongitude(), p2_2 .getLatitude()); + // TODO check if one of the points only touches the other ring + if (intersects) { if ((ring1.isClosedArtificially() && it1.hasNext() == false) || (ring2.isClosedArtificially() && it2.hasNext() == false)) { @@ -1045,6 +1098,9 @@ return false; } } + + prevLonField = lonField; + prevLatField = latField; } } @@ -1079,19 +1135,80 @@ */ private static class JoinedWay extends Way { private final List originalWays; - private boolean closedArtifical; + private boolean closedArtifical = false; + + public int minLat; + public int maxLat; + public int minLon; + public int maxLon; + private Rectangle bounds = null; public JoinedWay(Way originalWay) { super(FakeIdGenerator.makeFakeId(), new ArrayList( originalWay.getPoints())); this.originalWays = new ArrayList(); addWay(originalWay); + + // we have to initialize the min/max values + Coord c0 = originalWay.getPoints().get(0); + minLat = maxLat = c0.getLatitude(); + minLon = maxLon = c0.getLongitude(); + + updateBounds(originalWay.getPoints()); } public void addPoint(int index, Coord point) { getPoints().add(index, point); + updateBounds(point); + } + + public void addPoint(Coord point) { + super.addPoint(point); + updateBounds(point); + } + + private void updateBounds(List pointList) { + for (Coord c : pointList) { + updateBounds(c); + } } + private void updateBounds(Coord point) { + if (point.getLatitude() < minLat) { + minLat = point.getLatitude(); + bounds = null; + } else if (point.getLatitude() > maxLat) { + maxLat = point.getLatitude(); + bounds = null; + } + + if (point.getLongitude() < minLon) { + minLon = point.getLongitude(); + bounds = null; + } else if (point.getLongitude() > maxLon) { + maxLon = point.getLongitude(); + bounds = null; + } + + } + + public Rectangle getBounds() { + if (bounds == null) { + // note that we increase the rectangle by 1 because intersects + // checks + // only the interior + bounds = new Rectangle(minLat - 1, minLon - 1, maxLat - minLat + + 2, maxLon - minLon + 2); + } + + return bounds; + } + + public boolean linePossiblyIntersectsWay(Coord p1, Coord p2) { + return getBounds().intersectsLine(p1.getLatitude(), + p1.getLongitude(), p2.getLatitude(), p2.getLongitude()); + } + public void addWay(Way way) { if (way instanceof JoinedWay) { for (Way w : ((JoinedWay) way).getOriginalWays()) { @@ -1145,6 +1262,7 @@ } } + @Override public String toString() { StringBuilder sb = new StringBuilder(200); sb.append(getId()); @@ -1180,4 +1298,5 @@ this.ring = ring; } } + }