Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java =================================================================== --- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (revision 1593) +++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (working copy) @@ -1,6 +1,7 @@ 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; @@ -479,14 +480,19 @@ } private ArrayList getPolygonStatus(BitSet outmostPolygons, - boolean outer) { + String defaultRole) { 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)); + String role = getRole(polygon); + // if the role is not explicitly set use the default role + if (role == null || "".equals(role)) { + role = defaultRole; + } + polygonStatusList.add(new PolygonStatus("outer".equals(role), polyIndex, polygon)); } return polygonStatusList; } @@ -511,16 +517,17 @@ } } + // join all single ways to polygons, try to close ways and remove non closed ways polygons = joinWays(allWays); closeWays(polygons); removeUnclosedWays(polygons); - // now we have closed ways == polygons only + // now only closed ways are left => polygons only // check if we have at least one polygon left if (polygons.isEmpty()) { // do nothing - log.warn("Multipolygon " + toBrowseURL() + log.info("Multipolygon " + toBrowseURL() + " does not contain a closed polygon."); cleanup(); return; @@ -538,21 +545,29 @@ // the intersectingPolygons marks all intersecting/overlapping polygons intersectingPolygons = new HashSet(); + + // check which polygons lie inside which other polygon createContainsMatrix(polygons); + // unfinishedPolygons marks which polygons are not yet processed BitSet unfinishedPolygons = new BitSet(polygons.size()); unfinishedPolygons.set(0, polygons.size()); // create bitsets which polygons belong to the outer and to the inner role BitSet innerPolygons = new BitSet(); + BitSet taggedInnerPolygons = new BitSet(); BitSet outerPolygons = new BitSet(); + BitSet taggedOuterPolygons = new BitSet(); + int wi = 0; for (Way w : polygons) { String role = getRole(w); if ("inner".equals(role)) { innerPolygons.set(wi); + taggedInnerPolygons.set(wi); } else if ("outer".equals(role)) { outerPolygons.set(wi); + taggedOuterPolygons.set(wi); } else { // unknown role => it could be both innerPolygons.set(wi); @@ -563,26 +578,38 @@ if (outerPolygons.isEmpty()) { log.warn("Multipolygon", toBrowseURL(), - "does not contain any way tagged with role=outer."); + "does not contain any way tagged with role=outer or empty role."); cleanup(); return; } Queue polygonWorkingQueue = new LinkedBlockingQueue(); + BitSet outerNestedOuterPolygons = new BitSet(); - 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 + BitSet outmostPolygons ; + BitSet outmostInnerPolygons = new BitSet(); + boolean outmostInnerFound = false; + do { + outmostInnerFound = false; + outmostPolygons = findOutmostPolygons(unfinishedPolygons); - // // 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 { - polygonWorkingQueue.addAll(getPolygonStatus(outmostPolygons, true)); + if (outmostPolygons.intersects(taggedInnerPolygons)) { + outmostInnerPolygons.or(outmostPolygons); + outmostInnerPolygons.and(taggedInnerPolygons); + + if (log.isDebugEnabled()) + log.debug("wrong inner polygons: " + outmostInnerPolygons); + // do not process polygons tagged with role=inner but which are + // not + // contained by any other polygon + unfinishedPolygons.andNot(outmostInnerPolygons); + outmostPolygons.andNot(outmostInnerPolygons); + outmostInnerFound = true; + } + } while (outmostInnerFound); + + if (outmostPolygons.isEmpty() == false) { + polygonWorkingQueue.addAll(getPolygonStatus(outmostPolygons, "outer")); } while (polygonWorkingQueue.isEmpty() == false) { @@ -590,11 +617,6 @@ // 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(currentPolygon.polygon.getOriginalWays(), - (currentPolygon.outer ? "outer" : "inner")); - // this polygon is now processed and should not be used by any // further step unfinishedPolygons.clear(currentPolygon.index); @@ -609,13 +631,36 @@ // get the holes // 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)); + boolean holesOk = true; + BitSet holeIndexes; + do { + holeIndexes = findOutmostPolygons(polygonContains); + holesOk = true; + if (currentPolygon.outer) { + // for role=outer only role=inner is allowed + if (holeIndexes.intersects(taggedOuterPolygons)) { + BitSet addOuterNestedPolygons = new BitSet(); + addOuterNestedPolygons.or(holeIndexes); + addOuterNestedPolygons.and(taggedOuterPolygons); + outerNestedOuterPolygons.or(addOuterNestedPolygons); + holeIndexes.andNot(addOuterNestedPolygons); + // do not process them + unfinishedPolygons.andNot(addOuterNestedPolygons); + polygonContains.andNot(addOuterNestedPolygons); + + // recalculate the holes again to get all inner polygons + // in the nested outer polygons + holesOk = false; + } + } else { + // for role=inner both role=inner and role=outer are allowed + } + } while (holesOk == false); - ArrayList holes = getPolygonStatus(holeIndexes, - !currentPolygon.outer); + ArrayList holes = getPolygonStatus(holeIndexes, + (currentPolygon.outer ? "inner" : "outer")); - // these polygons must all be checked for inner polygons + // these polygons must all be checked for holes polygonWorkingQueue.addAll(holes); // check if the polygon has tags and therefore should be processed @@ -666,11 +711,13 @@ } } } - - if (log.isLoggable(Level.WARNING) && unfinishedPolygons.isEmpty() == false) { + + if (log.isLoggable(Level.WARNING) && (outmostInnerPolygons.cardinality()+unfinishedPolygons.cardinality()+outerNestedOuterPolygons.cardinality() >= 1)) { log.warn("Multipolygon", toBrowseURL(), "contains errors."); runIntersectionCheck(unfinishedPolygons); + runOutmostInnerPolygonCheck(outmostInnerPolygons); + runOuterInOuterPolygonCheck(outerNestedOuterPolygons); runWrongInnerPolygonCheck(unfinishedPolygons, innerPolygons); // we have at least one ring that could not be processed @@ -718,6 +765,27 @@ } } + + private void runOuterInOuterPolygonCheck(BitSet outerInOuterPolygons) { + // just print out warnings + // the check has been done before + for (int wiIndex = outerInOuterPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = outerInOuterPolygons + .nextSetBit(wiIndex + 1)) { + Way outerWay = polygons.get(wiIndex); + log.warn("Polygon", outerWay, "carries role outer but lies inside an outer polygon. Potentially its role should be inner."); + } + } + + private void runOutmostInnerPolygonCheck(BitSet outmostInnerPolygons) { + // just print out warnings + // the check has been done before + for (int wiIndex = outmostInnerPolygons.nextSetBit(0); wiIndex >= 0; wiIndex = outmostInnerPolygons + .nextSetBit(wiIndex + 1)) { + Way innerWay = polygons.get(wiIndex); + log.warn("Polygon", innerWay, "carries role", getRole(innerWay), "but is not inside any other polygon. Potentially it does not belong to this multipolygon."); + } + } + private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons, BitSet innerPolygons) { // find all unfinished inner rings that are not contained by any @@ -778,6 +846,10 @@ * polygons */ private List cutOutInnerPolygons(Way outerPolygon, List innerPolygons) { + if (innerPolygons.isEmpty()) { + return Collections.singletonList((Way)new JoinedWay(outerPolygon)); + } + // we use the java.awt.geom.Area class because it's a quick // implementation of what we need @@ -1049,29 +1121,6 @@ } /** - * 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 checkRole - */ - private void checkRoles(List wayList, String checkRole) { - // 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 && "".equals(realRole) == false) { - if (tempWay instanceof JoinedWay) { - log.warn("Polygon composed of ways", ((JoinedWay) tempWay).getOriginalIds(), "carries role", realRole, - "but should carry role", checkRole); - } else { - log.warn("Way", tempWay.getId(), "carries role", realRole, - "but should carry role", checkRole); - } - } - } - } - - /** * Creates a matrix which polygon contains which polygon. A polygon does not * contain itself. *