Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (revision 1571) +++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (working copy) @@ -37,11 +37,19 @@ private final Map roleMap = new HashMap(); private ArrayList containsMatrix; - private ArrayList rings; - private Set intersectingRings; + private ArrayList polygons; + private Set intersectingPolygons; private final uk.me.parabola.imgfmt.app.Area bbox; + /** + * A point that has a lower or equal squared distance from + * a line is treated as if it lies one the line.
+ * 1.0d is very exact. 2.0d covers rounding problems when converting + * OSM locations to mkgmap internal format. A larger value + * is more tolerant against imprecise OSM data. + */ + private final double OVERLAP_TOLERANCE_DISTANCE = 2.0d; /** * if one of these tags are contained in the multipolygon then the outer @@ -416,72 +424,71 @@ } } } - + /** - * Find all rings that are not contained by any other ring. + * Find all polygons that are not contained by any other polygon. * * @param candidates - * all rings that should be checked + * all polygons that should be checked * @param roleFilter * an additional filter - * @return all ring indexes that are not contained by any other ring + * @return all polygon indexes that are not contained by any other polygon */ - private BitSet findOutmostRings(BitSet candidates, BitSet roleFilter) { + private BitSet findOutmostPolygons(BitSet candidates, BitSet roleFilter) { BitSet realCandidates = ((BitSet) candidates.clone()); realCandidates.and(roleFilter); - return findOutmostRings(realCandidates); + return findOutmostPolygons(realCandidates); } /** - * Finds all rings that are not contained by any other rings and that match - * to the given role. All rings with index given by candidates + * Finds all polygons that are not contained by any other polygons and that match + * to the given role. All polygons with index given by candidates * are used. * * @param candidates - * indexes of the rings that should be used - * @return the bits of all outmost rings are set to true + * indexes of the polygons that should be used + * @return the bits of all outmost polygons are set to true */ - private BitSet findOutmostRings(BitSet candidates) { - BitSet outmostRings = new BitSet(); + private BitSet findOutmostPolygons(BitSet candidates) { + BitSet outmostPolygons = new BitSet(); // go through all candidates and check if they are contained by any // other candidate for (int candidateIndex = candidates.nextSetBit(0); candidateIndex >= 0; candidateIndex = candidates .nextSetBit(candidateIndex + 1)) { - // check if the candidateIndex ring is not contained by any - // other candidate ring + // check if the candidateIndex polygon is not contained by any + // other candidate polygon boolean isOutmost = true; for (int otherCandidateIndex = candidates.nextSetBit(0); otherCandidateIndex >= 0; otherCandidateIndex = candidates .nextSetBit(otherCandidateIndex + 1)) { if (contains(otherCandidateIndex, candidateIndex)) { - // candidateIndex is not an outermost ring because it is - // contained by - // the otherCandidateIndex ring + // candidateIndex is not an outermost polygon because it is + // contained by the otherCandidateIndex polygon isOutmost = false; break; } } if (isOutmost) { - // this is an outmost ring + // this is an outmost polygon // put it to the bitset - outmostRings.set(candidateIndex); + outmostPolygons.set(candidateIndex); } } - return outmostRings; + return outmostPolygons; } - private ArrayList getRingStatus(BitSet outmostRings, + private ArrayList getPolygonStatus(BitSet outmostPolygons, boolean outer) { - ArrayList ringStatusList = new ArrayList(); - for (int ringIndex = outmostRings.nextSetBit(0); ringIndex >= 0; ringIndex = outmostRings - .nextSetBit(ringIndex + 1)) { - // ringIndex is the ring that is not contained by any other - // ring - JoinedWay ring = rings.get(ringIndex); - ringStatusList.add(new RingStatus(outer, ringIndex, ring)); + ArrayList polygonStatusList = new ArrayList(); + for (int polyIndex = outmostPolygons.nextSetBit(0); polyIndex >= 0; polyIndex = outmostPolygons + .nextSetBit(polyIndex + 1)) { + // polyIndex is the polygon that is not contained by any other + // polygon + JoinedWay polygon = polygons.get(polyIndex); + polygonStatusList.add(new PolygonStatus(outer, polyIndex, polygon)); } - return ringStatusList; + return polygonStatusList; } /** @@ -504,13 +511,14 @@ } } - rings = joinWays(allWays); - closeWays(rings); - removeUnclosedWays(rings); - // now we have closed ways == rings only + polygons = joinWays(allWays); + closeWays(polygons); + removeUnclosedWays(polygons); + + // now we have closed ways == polygons only - // check if we have at least one ring left - if (rings.isEmpty()) { + // check if we have at least one polygon left + if (polygons.isEmpty()) { // do nothing log.warn("Multipolygon " + toBrowseURL() + " does not contain a closed polygon."); @@ -518,132 +526,134 @@ return; } - removeWaysOutsideBbox(rings); + removeWaysOutsideBbox(polygons); - if (rings.isEmpty()) { + if (polygons.isEmpty()) { // do nothing log.info("Multipolygon " + toBrowseURL() + " is completely outside the bounding box. It is not processed."); cleanup(); return; } - - // the intersectingRings marks all intersecting/overlapping rings - intersectingRings = new HashSet(); - createContainsMatrix(rings); + + // the intersectingPolygons marks all intersecting/overlapping polygons + intersectingPolygons = new HashSet(); + createContainsMatrix(polygons); - BitSet unfinishedRings = new BitSet(rings.size()); - unfinishedRings.set(0, rings.size()); + BitSet unfinishedPolygons = new BitSet(polygons.size()); + unfinishedPolygons.set(0, polygons.size()); - // create bitsets which rings belong to the outer and to the inner role - BitSet innerRings = new BitSet(); - BitSet outerRings = new BitSet(); + // create bitsets which polygons belong to the outer and to the inner role + BitSet innerPolygons = new BitSet(); + BitSet outerPolygons = new BitSet(); int wi = 0; - for (Way w : rings) { + for (Way w : polygons) { String role = getRole(w); if ("inner".equals(role)) { - innerRings.set(wi); + innerPolygons.set(wi); } else if ("outer".equals(role)) { - outerRings.set(wi); + outerPolygons.set(wi); } else { // unknown role => it could be both - innerRings.set(wi); - outerRings.set(wi); + innerPolygons.set(wi); + outerPolygons.set(wi); } wi++; } - if (outerRings.isEmpty()) { - log.warn("Multipolygon",toBrowseURL(),"does not contain any way tagged with role=outer."); + if (outerPolygons.isEmpty()) { + log.warn("Multipolygon", toBrowseURL(), + "does not contain any way tagged with role=outer."); cleanup(); return; } - - Queue ringWorkingQueue = new LinkedBlockingQueue(); - BitSet outmostRings = findOutmostRings(unfinishedRings, outerRings); - if (outmostRings.isEmpty()) { - // WanMil: do not process these rings - // this would probably cause wrong mps. Issue a warning later in the code - -// // there's no outmost outer ring -// // maybe this is a tile problem -// // try to continue with the inner ring -// outmostRings = findOutmostRings(unfinishedRings, innerRings); -// ringWorkingQueue.addAll(getRingStatus(outmostRings, false)); + Queue polygonWorkingQueue = new LinkedBlockingQueue(); + + BitSet outmostPolygons = findOutmostPolygons(unfinishedPolygons, outerPolygons); + if (outmostPolygons.isEmpty()) { + // WanMil: do not process these polygons + // this would probably cause wrong mps. Issue a warning later in the + // code + + // // there's no outmost outer polygon + // // maybe this is a tile problem + // // try to continue with the inner polygons + // outmostPolygons = findOutmostPolygons(unfinishedPolygons, innerPolygons); + // polygonWorkingQueue.addAll(getPolygonStatus(outmostPolygons, false)); } else { - ringWorkingQueue.addAll(getRingStatus(outmostRings, true)); + polygonWorkingQueue.addAll(getPolygonStatus(outmostPolygons, true)); } - while (ringWorkingQueue.isEmpty() == false) { + while (polygonWorkingQueue.isEmpty() == false) { - // the ring is not contained by any other unfinished ring - RingStatus currentRing = ringWorkingQueue.poll(); + // the polygon is not contained by any other unfinished polygon + PolygonStatus currentPolygon = polygonWorkingQueue.poll(); // QA: check that all ways carry the role "outer/inner" and // issue warnings - checkRoles(currentRing.ring.getOriginalWays(), - (currentRing.outer ? "outer" : "inner")); + checkRoles(currentPolygon.polygon.getOriginalWays(), + (currentPolygon.outer ? "outer" : "inner")); - // this ring is now processed and should not be used by any + // this polygon is now processed and should not be used by any // further step - unfinishedRings.clear(currentRing.index); + unfinishedPolygons.clear(currentPolygon.index); - BitSet ringContains = new BitSet(); - ringContains.or(containsMatrix.get(currentRing.index)); - // use only rings that are contained by the ring - ringContains.and(unfinishedRings); - // ringContains is the intersection of the unfinished and - // the contained rings + BitSet polygonContains = new BitSet(); + polygonContains.or(containsMatrix.get(currentPolygon.index)); + // use only polygon that are contained by the polygon + polygonContains.and(unfinishedPolygons); + // polygonContains is the intersection of the unfinished and + // the contained polygons // get the holes - // these are all rings that are in the main ring - // and that are not contained by any other ring - BitSet holeIndexes = findOutmostRings(ringContains, - (currentRing.outer ? innerRings : outerRings)); + // these are all polygons that are in the main polygon + // and that are not contained by any other polygon + BitSet holeIndexes = findOutmostPolygons(polygonContains, + (currentPolygon.outer ? innerPolygons : outerPolygons)); - ArrayList holes = getRingStatus(holeIndexes, - !currentRing.outer); + ArrayList holes = getPolygonStatus(holeIndexes, + !currentPolygon.outer); - // these rings must all be checked for inner rings - ringWorkingQueue.addAll(holes); + // these polygons must all be checked for inner polygons + polygonWorkingQueue.addAll(holes); - // check if the ring has tags and therefore should be processed - boolean processRing = currentRing.outer - || hasPolygonTags(currentRing.ring); + // check if the polygon has tags and therefore should be processed + boolean processPolygon = currentPolygon.outer + || hasPolygonTags(currentPolygon.polygon); - if (processRing) { + if (processPolygon) { List innerWays = new ArrayList(holes.size()); - for (RingStatus holeRingStatus : holes) { - innerWays.add(holeRingStatus.ring); + for (PolygonStatus polygonHoleStatus : holes) { + innerWays.add(polygonHoleStatus.polygon); } - List singularOuterPolygons = cutOutInnerRings( - currentRing.ring, innerWays); + List singularOuterPolygons = cutOutInnerPolygons( + currentPolygon.polygon, innerWays); - if (currentRing.ring.getOriginalWays().size() == 1) { + if (currentPolygon.polygon.getOriginalWays().size() == 1) { // the original way was a closed polygon which // has been replaced by the new cutted polygon // the original way should not appear // so we remove all tags - currentRing.ring.removeAllTagsDeep(); + currentPolygon.polygon.removeAllTagsDeep(); } else { // remove all polygons tags from the original ways // sometimes the ways seem to be autoclosed later on // in mkgmap - for (Way w : currentRing.ring.getOriginalWays()) { + for (Way w : currentPolygon.polygon.getOriginalWays()) { for (String polygonTag : polygonTags) { w.deleteTag(polygonTag); } } } - boolean useRelationTags = currentRing.outer + boolean useRelationTags = currentPolygon.outer && hasPolygonTags(this); if (useRelationTags) { // the multipolygon contains tags that overwhelm the - // tags of the outer ring + // tags of the outer polygon for (Way p : singularOuterPolygons) { p.copyTags(this); } @@ -657,20 +667,20 @@ } } - if (log.isLoggable(Level.WARNING) && unfinishedRings.isEmpty() == false) { - log.warn("Multipolygon", toBrowseURL(),"contains errors."); - - runIntersectionCheck(unfinishedRings); - runWrongInnerRingCheck(unfinishedRings, innerRings); - + if (log.isLoggable(Level.WARNING) && unfinishedPolygons.isEmpty() == false) { + log.warn("Multipolygon", toBrowseURL(), "contains errors."); + + runIntersectionCheck(unfinishedPolygons); + runWrongInnerPolygonCheck(unfinishedPolygons, innerPolygons); + // we have at least one ring that could not be processed // Probably we have intersecting or overlapping polygons // one possible reason is if the relation overlaps the tile // bounds // => issue a warning - List lostWays = getWaysFromRinglist(unfinishedRings); + List lostWays = getWaysFromPolygonList(unfinishedPolygons); for (JoinedWay w : lostWays) { - log.warn("Polygon",w.getId(),"is not processed due to an unknown reason."); + log.warn("Polygon", w, "is not processed due to an unknown reason."); logWayURLs(Level.WARNING, "-", w); } } @@ -680,16 +690,16 @@ private void runIntersectionCheck(BitSet unfinishedRings) { - if (intersectingRings.isEmpty()) { + if (intersectingPolygons.isEmpty()) { // nothing to do return; } - log.warn("Some polygons are intersecting or overlapping. This is not yet supported."); + log.warn("Some polygons are intersecting. This is not allowed in multipolygons."); boolean oneOufOfBbox = false; - for (JoinedWay polygon : intersectingRings) { - int pi = rings.indexOf(polygon); + for (JoinedWay polygon : intersectingPolygons) { + int pi = polygons.indexOf(polygon); unfinishedRings.clear(pi); boolean outOfBbox = false; @@ -707,95 +717,100 @@ log.warn("Some of these intersections/overlaps may be caused by incomplete data on bounding box edges (*)."); } } - - private void runWrongInnerRingCheck(BitSet unfinishedRings, BitSet innerRings) { + + private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons, + BitSet innerPolygons) { // find all unfinished inner rings that are not contained by any - BitSet wrongInnerRings = findOutmostRings(unfinishedRings, innerRings); + BitSet wrongInnerPolygons = findOutmostPolygons(unfinishedPolygons, innerPolygons); if (log.isDebugEnabled()) { - log.debug("unfinished", unfinishedRings); - log.debug("inner", innerRings); - // other ring - log.debug("wrong", wrongInnerRings); + log.debug("unfinished", unfinishedPolygons); + log.debug("inner", innerPolygons); + // other polygon + log.debug("wrong", wrongInnerPolygons); } - if (wrongInnerRings.isEmpty()==false) { - // we have an inner ring that is not contained by any outer ring + if (wrongInnerPolygons.isEmpty() == false) { + // we have an inner polygon that is not contained by any outer polygon // check if - for (int wiIndex = wrongInnerRings.nextSetBit(0); wiIndex >= 0; wiIndex = wrongInnerRings + for (int wiIndex = wrongInnerPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = wrongInnerPolygons .nextSetBit(wiIndex + 1)) { - BitSet containedRings = new BitSet(); - containedRings.or(unfinishedRings); - containedRings.and(containsMatrix.get(wiIndex)); + BitSet containedPolygons = new BitSet(); + containedPolygons.or(unfinishedPolygons); + containedPolygons.and(containsMatrix.get(wiIndex)); - Way innerWay = rings.get(wiIndex); - if (containedRings.isEmpty()) { + Way innerWay = polygons.get(wiIndex); + if (containedPolygons.isEmpty()) { log.warn("Polygon", innerWay, "carries role", getRole(innerWay), "but is not inside any outer polygon. Potentially it does not belong to this multipolygon."); } else { log.warn("Polygon", innerWay, "carries role", getRole(innerWay), - "but is not inside any outer polygon. Potentially the roles are interchanged with the following", - (containedRings.cardinality()>1?"ways":"way"),"."); - - for (int wrIndex = containedRings.nextSetBit(0); wrIndex >= 0; - wrIndex = containedRings.nextSetBit(wrIndex+1)) { - logWayURLs(Level.WARNING, "-", rings.get(wrIndex)); - unfinishedRings.set(wrIndex); - wrongInnerRings.set(wrIndex); + "but is not inside any outer polygon. Potentially the roles are interchanged with the following", + (containedPolygons.cardinality() > 1 ? "ways" : "way"), "."); + + for (int wrIndex = containedPolygons.nextSetBit(0); wrIndex >= 0; wrIndex = containedPolygons + .nextSetBit(wrIndex + 1)) { + logWayURLs(Level.WARNING, "-", polygons.get(wrIndex)); + unfinishedPolygons.set(wrIndex); + wrongInnerPolygons.set(wrIndex); } } - - unfinishedRings.clear(wiIndex); - wrongInnerRings.clear(wiIndex); + + unfinishedPolygons.clear(wiIndex); + wrongInnerPolygons.clear(wiIndex); } } - } - + private void cleanup() { roleMap.clear(); containsMatrix = null; - rings = null; + polygons = null; } /** - * Cut out all inner rings from the outer ring. This will divide the outer - * ring in several polygons. + * Cut out all inner polygons from the outer polygon. This will divide the outer + * polygon in several polygons. * - * @param outerRing - * the outer ring - * @param innerRings - * a list of inner rings - * @return a list of polygons that make the outer ring cut by the inner - * rings + * @param outerPolygon + * the outer polygon + * @param innerPolygons + * a list of inner polygons + * @return a list of polygons that make the outer polygon cut by the inner + * polygons */ - private List cutOutInnerRings(Way outerRing, List innerRings) { + private List cutOutInnerPolygons(Way outerPolygon, List innerPolygons) { // we use the java.awt.geom.Area class because it's a quick // implementation of what we need // this list contains all non overlapping and singular areas - // of the outerRing + // of the outerPolygon List outerAreas = new ArrayList(); - // 1st create an Area object of the outerRing and put it to the list - List oa = createAreas(outerRing); + // 1st create an Area object of the outerPolygon and put it to the list + List oa = createAreas(outerPolygon); - // the polygons will be later clipped in the style converter - // so it is not necessary to clip it here - outerAreas.addAll(oa); + Area bboxArea = new Area(new Rectangle(bbox.getMinLong(), bbox + .getMinLat(), bbox.getMaxLong() - bbox.getMinLong(), + bbox.getMaxLat() - bbox.getMinLat())); + + for (Area outerArea : oa) { + // clip all areas to the bounding box + outerArea.intersect(bboxArea); + outerAreas.add(outerArea); + } List innerAreas = new ArrayList(); - for (Way innerRing : innerRings) { - innerAreas.addAll(createAreas(innerRing)); + for (Way innerPolygon : innerPolygons) { + innerAreas.addAll(createAreas(innerPolygon)); } - // go through all innerRings (holes) and cut them from the outerRing + // go through all innerPolygons (holes) and cut them from the outerPolygon for (Area innerArea : innerAreas) { List outerAfterThisStep = new ArrayList(); for (Area outerArea : outerAreas) { // check if this outerArea is probably intersected by the inner // area to save computation time in case it is not - if (outerArea.getBounds().intersects( - innerArea.getBounds()) == false) { + if (outerArea.getBounds().intersects(innerArea.getBounds()) == false) { outerAfterThisStep.add(outerArea); continue; } @@ -852,16 +867,16 @@ } // convert the java.awt.geom.Area back to the mkgmap way - List cutOuterRing = new ArrayList(outerAreas.size()); + List cutOuterPolygon = new ArrayList(outerAreas.size()); for (Area area : outerAreas) { Way w = singularAreaToWay(area, FakeIdGenerator.makeFakeId()); if (w != null) { - w.copyTags(outerRing); - cutOuterRing.add(w); + w.copyTags(outerPolygon); + cutOuterPolygon.add(w); } } - return cutOuterRing; + return cutOuterPolygon; } /** @@ -954,9 +969,16 @@ Area area = new Area(createPolygon(w.getPoints())); List areaList = areaToSingularAreas(area); if (areaList.size() > 1) { - log.warn("Polygon", w.getId(), "intersects itself."); - log.warn("The polygon is composed of"); - logWayURLs(Level.WARNING, "-", w); + if (bbox.allInsideBoundary(w.getPoints())) { + log.warn("Polygon", w.getId(), "intersects itself. It is splitted into", areaList.size(), "polygons."); + log.warn("The polygon is composed of"); + logWayURLs(Level.WARNING, "-", w); + } else { + log.info("Polygon", w.getId(), + "intersects itself and the tile bounds. Maybe the polygon is not completely contained in the tile."); + log.info("The polygon is composed of"); + logWayURLs(Level.INFO, "-", w); + } } return areaList; } @@ -974,7 +996,7 @@ private Way singularAreaToWay(Area area, long wayId) { if (area.isEmpty()) { if (log.isDebugEnabled()) { - log.debug("Empty area.", toBrowseURL()); + log.debug("Empty area "+wayId+".", toBrowseURL()); } return null; } @@ -1037,7 +1059,7 @@ // QA: check that all ways carry the role "inner" and issue warnings for (Way tempWay : wayList) { String realRole = getRole(tempWay); - if (checkRole.equals(realRole) == false) { + if (checkRole.equals(realRole) == false && "".equals(realRole) == false) { if (tempWay instanceof JoinedWay) { log.warn("Polygon composed of ways", ((JoinedWay) tempWay).getOriginalIds(), "carries role", realRole, "but should carry role", checkRole); @@ -1080,7 +1102,7 @@ } for (int rowIndex = 0; rowIndex < polygonList.size(); rowIndex++) { - JoinedWay potentialOuterRing = polygonList.get(rowIndex); + JoinedWay potentialOuterPolygon = polygonList.get(rowIndex); BitSet containsColumns = containsMatrix.get(rowIndex); BitSet finishedCol = finishedMatrix.get(rowIndex); @@ -1094,28 +1116,27 @@ JoinedWay innerPolygon = polygonList.get(colIndex); - if (potentialOuterRing.getBounds().intersects( - innerPolygon.getBounds()) == false) { + if (potentialOuterPolygon.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 { - boolean contains = contains(potentialOuterRing, - innerPolygon); + boolean contains = contains(potentialOuterPolygon, + innerPolygon); if (contains) { containsColumns.set(colIndex); - // we also know that the inner ring does not contain the - // outer ring + // we also know that the inner polygon does not contain the + // outer polygon // 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 + // additionally we know that the outer polygon contains all + // polygons that are contained by the inner polygon containsColumns.or(containsMatrix.get(colIndex)); finishedCol.or(containsColumns); } @@ -1128,8 +1149,8 @@ if (log.isDebugEnabled()) { long t2 = System.currentTimeMillis(); log.debug("createMatrix for", polygonList.size(), "polygons took", - (t2 - t1), "ms"); - + (t2 - t1), "ms"); + log.debug("Containsmatrix"); for (BitSet b : containsMatrix) { log.debug(b); @@ -1138,41 +1159,102 @@ } /** - * Checks if the ring with ringIndex1 contains the ring with ringIndex2. + * Checks if the polygon with polygonIndex1 contains the ring with polygonIndex2. * - * @return true if ring(ringIndex1) contains ring(ringIndex2) + * @return true if polygon(polygonIndex1) contains polygon(polygonIndex2) */ - private boolean contains(int ringIndex1, int ringIndex2) { - return containsMatrix.get(ringIndex1).get(ringIndex2); + private boolean contains(int polygonIndex1, int polygonIndex2) { + return containsMatrix.get(polygonIndex1).get(polygonIndex2); } /** - * Checks if ring1 contains ring2. + * Checks if polygon1 contains polygon2. * - * @param ring1 + * @param polygon1 * a closed way - * @param ring2 a 2nd closed way - * @return true if ring1 contains ring2 + * @param polygon2 + * a 2nd closed way + * @return true if polygon1 contains polygon2 */ - private boolean contains(JoinedWay ring1, JoinedWay ring2) { - // TODO this is a simple algorithm - // might be improved - - if (ring1.isClosed() == false) { + private boolean contains(JoinedWay polygon1, JoinedWay polygon2) { + if (polygon1.isClosed() == false) { + return false; + } + // check if the bounds of polygon2 are completely inside/enclosed the bounds + // of polygon1 + if (polygon1.getBounds().contains(polygon2.getBounds()) == false) { return false; } - Polygon p = createPolygon(ring1.getPoints()); + Polygon p = createPolygon(polygon1.getPoints()); + // check first if one point of polygon2 is in polygon1 - Coord p0 = ring2.getPoints().get(0); - if (p.contains(p0.getLongitude(), p0.getLatitude()) == false) { - // we have one point that is not in way1 => way1 does not contain - // way2 - return false; + // ignore intersections outside the bounding box + // so it is necessary to check if there is at least one + // point of polygon2 in polygon1 ignoring all points outside the bounding box + boolean onePointContained = false; + boolean allOnLine = true; + for (Coord px : polygon2.getPoints()) { + if (p.contains(px.getLongitude(), px.getLatitude())) { + // there's one point that is in polygon1 and in the bounding + // box => polygon1 may contain polygon2 + onePointContained = true; + if (locatedOnLine(px, polygon1.getPoints()) == false) { + allOnLine = false; + break; + } + } else if (bbox.contains(px)) { + // we have to check if the point is on one line of the polygon1 + + if (locatedOnLine(px, polygon1.getPoints()) == false) { + // there's one point that is not in polygon1 but inside the + // bounding box => polygon1 does not contain polygon2 + allOnLine = false; + return false; + } + } + } + + if (allOnLine) { + onePointContained = false; + // all points of polygon2 lie on lines of polygon1 + // => the middle of each line polygon must NOT lie outside polygon1 + ArrayList middlePoints2 = new ArrayList(polygon2.getPoints().size()); + Coord p1 = null; + for (Coord p2 : polygon2.getPoints()) { + if (p1 != null) { + int mLat = p1.getLatitude()+(int)Math.round((p2.getLatitude()-p1.getLatitude())/2d); + int mLong = p1.getLongitude()+(int)Math.round((p2.getLongitude()-p1.getLongitude())/2d); + Coord pm = new Coord(mLat, mLong); + middlePoints2.add(pm); + } + p1 = p2; + } + + for (Coord px : middlePoints2) { + if (p.contains(px.getLongitude(), px.getLatitude())) { + // there's one point that is in polygon1 and in the bounding + // box => polygon1 may contain polygon2 + onePointContained = true; + break; + } else if (bbox.contains(px)) { + // we have to check if the point is on one line of the polygon1 + + if (locatedOnLine(px, polygon1.getPoints()) == false) { + // there's one point that is not in polygon1 but inside the + // bounding box => polygon1 does not contain polygon2 + return false; + } + } + } } - // TODO check if p0 is on any of the lines of ring2 - Iterator it1 = ring1.getPoints().iterator(); + if (onePointContained == false) { + // no point of polygon2 is in polygon1 => polygon1 does not contain polygon2 + return false; + } + + Iterator it1 = polygon1.getPoints().iterator(); Coord p1_1 = it1.next(); Coord p1_2 = null; @@ -1180,7 +1262,7 @@ p1_2 = p1_1; p1_1 = it1.next(); - if (ring2.linePossiblyIntersectsWay(p1_1, p1_2) == false) { + if (polygon2.linePossiblyIntersectsWay(p1_1, p1_2) == false) { // don't check it - this segment of the outer polygon // definitely does not intersect the way continue; @@ -1192,7 +1274,7 @@ int latMax = Math.max(p1_1.getLatitude(), p1_2.getLatitude()); // check all lines of way1 and way2 for intersections - Iterator it2 = ring2.getPoints().iterator(); + Iterator it2 = polygon2.getPoints().iterator(); Coord p2_1 = it2.next(); Coord p2_2 = null; @@ -1236,28 +1318,32 @@ || (prevLatField == 0 && prevLonField == 0); 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 - + && linesCutEachOther(p1_1, p1_2, p2_1, p2_2); + if (intersects) { - if ((ring1.isClosedArtificially() && it1.hasNext() == false) - || (ring2.isClosedArtificially() && it2.hasNext() == false)) { + if ((polygon1.isClosedArtificially() && it1.hasNext() == false) + || (polygon2.isClosedArtificially() && it2.hasNext() == false)) { // don't care about this intersection - // one of the rings is closed by this mp code and the - // closing way causes the intersection - log.info("Polygon", ring1, "may contain polygon", ring2, + // one of the polygons is closed by this mp code and the + // closing segment causes the intersection + log.info("Polygon", polygon1, "may contain polygon", polygon2, ". Ignoring artificial generated intersection."); + } else if ((bbox.contains(p1_1) == false) + || (bbox.contains(p1_2) == false) + || (bbox.contains(p2_1) == false) + || (bbox.contains(p2_2) == false)) { + // at least one point is outside the bounding box + // we ignore the intersection because the ways may not + // be complete + // due to removals of the tile splitter or osmosis + log.info("Polygon", polygon1, "may contain polygon", polygon2, + ". Ignoring because at least one point is outside the bounding box."); } else { - // store them in the intersection rings set - // the error message will be printed out in the end of + // store them in the intersection polygons set + // the error message will be printed out in the end of // the mp handling - intersectingRings.add(ring1); - intersectingRings.add(ring2); + intersectingPolygons.add(polygon1); + intersectingPolygons.add(polygon2); return false; } } @@ -1268,26 +1354,123 @@ } // don't have any intersection - // => ring1 contains ring2 + // => polygon1 contains polygon2 return true; } - private List getWaysFromRinglist(BitSet selection) { + /** + * Checks if the point p is located on one line of the given points. + * @param p a point + * @param points a list of points; all consecutive points are handled as lines + * @return true if p is located on one line given by points + */ + private boolean locatedOnLine(Coord p, List points) { + Coord cp1 = null; + for (Coord cp2 : points) { + if (p.equals(cp2)) { + return true; + } + + try { + if (cp1 == null) { + // first init + continue; + } + + if (p.getLongitude() < Math.min(cp1.getLongitude(), cp2 + .getLongitude())) { + continue; + } + if (p.getLongitude() > Math.max(cp1.getLongitude(), cp2 + .getLongitude())) { + continue; + } + if (p.getLatitude() < Math.min(cp1.getLatitude(), cp2 + .getLatitude())) { + continue; + } + if (p.getLatitude() > Math.max(cp1.getLatitude(), cp2 + .getLatitude())) { + continue; + } + + double dist = Line2D.ptSegDistSq(cp1.getLongitude(), cp1 + .getLatitude(), cp2.getLongitude(), cp2.getLatitude(), + p.getLongitude(), p.getLatitude()); + + if (dist <= OVERLAP_TOLERANCE_DISTANCE) { + log.debug("Point", p, "is located on line between", cp1, "and", + cp2, ". Distance:", dist); + return true; + } + } finally { + cp1 = cp2; + } + } + return false; + } + + /** + * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa. + * This is a form of intersection check where it is allowed that one line ends on the + * other line or that the two lines overlap. + * @param p1_1 first point of line 1 + * @param p1_2 second point of line 1 + * @param p2_1 first point of line 2 + * @param p2_2 second point of line 2 + * @return true if both lines intersect somewhere in the middle of each other + */ + private boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, + Coord p2_2) { + int width1 = p1_2.getLongitude() - p1_1.getLongitude(); + int width2 = p2_2.getLongitude() - p2_1.getLongitude(); + + int height1 = p1_2.getLatitude() - p1_1.getLatitude(); + int height2 = p2_2.getLatitude() - p2_1.getLatitude(); + + int denominator = ((height2 * width1) - (width2 * height1)); + if (denominator == 0) { + // the lines are parallel + // they might overlap but this is ok for this test + return false; + } + + int x1Mx3 = p1_1.getLongitude() - p2_1.getLongitude(); + int y1My3 = p1_1.getLatitude() - p2_1.getLatitude(); + + double isx = (double)((width2 * y1My3) - (height2 * x1Mx3)) + / denominator; + if (isx < 0 || isx > 1) { + return false; + } + + double isy = (double)((width1 * y1My3) - (height1 * x1Mx3)) + / denominator; + + if (isy < 0 || isy > 1) { + return false; + } + + return false; + } + + private List getWaysFromPolygonList(BitSet selection) { if (selection.isEmpty()) { return Collections.emptyList(); } - List wayList = new ArrayList(selection.cardinality()); - for (int i = selection.nextSetBit(0); i >= 0; i = selection.nextSetBit(i+1)) { - wayList.add(rings.get(i)); + List wayList = new ArrayList(selection + .cardinality()); + for (int i = selection.nextSetBit(0); i >= 0; i = selection.nextSetBit(i + 1)) { + wayList.add(polygons.get(i)); } return wayList; } - + private void logWayURLs(Level level, String preMsg, Way way) { if (log.isLoggable(level)) { if (way instanceof JoinedWay) { if (((JoinedWay) way).getOriginalWays().isEmpty()) { - log.warn("Way",way,"does not contain any original ways"); + log.warn("Way", way, "does not contain any original ways"); } for (Way segment : ((JoinedWay) way).getOriginalWays()) { if (preMsg == null || preMsg.length() == 0) { @@ -1451,7 +1634,7 @@ public String toString() { StringBuilder sb = new StringBuilder(200); sb.append(getId()); - sb.append("["); + sb.append("("); sb.append(getPoints().size()); sb.append("P : ("); boolean first = true; @@ -1471,15 +1654,15 @@ } } - private static class RingStatus { + private static class PolygonStatus { boolean outer; int index; - JoinedWay ring; + JoinedWay polygon; - public RingStatus(boolean outer, int index, JoinedWay ring) { + public PolygonStatus(boolean outer, int index, JoinedWay polygon) { this.outer = outer; this.index = index; - this.ring = ring; + this.polygon = polygon; } } }