I have found that some multipolygons use nested inner polygons.
Example:
* A forest (outer) with a lake (inner) that has an island (inner) which is not a forest. (example: http://www.openstreetmap.org/browse/relation/331402)

By definition of the multipolygon relation (http://wiki.openstreetmap.org/wiki/Relation:multipolygon) one has to create two mps. But I see that it is a nice simplification lots of mappers are using.

Attached patch handles this situation.
The inner and outer roles are now used as follows:
* the outmost polygon must have role=outer (or role=<empty>)
* all outmost polygons with role=inner are not used (a warning is issued)
* all inside polygons with role=inner use their own tagging
* all inside polygons with role=outer use the mp tagging if available or their own tagging if the mp is not tagged * all inside polygons with role=<empty> are categorized automatically as outer/inner depending on the nest level * all inside polygons with role=outer without a direct ambient inner polygon are dropped with a warning message (outer in outer polygon)

Most of the renderers do not support inner in inner polygons but I think it is a very reasonable extension of the multipolygon handling.

This patch also improves the error messages. I haven't seen any more "are not processed due to an unknown reason" messages and I look forward that the community will also not be able to generate any more ... ;-)

WanMil

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<PolygonStatus> getPolygonStatus(BitSet 
outmostPolygons,
-                       boolean outer) {
+                       String defaultRole) {
                ArrayList<PolygonStatus> polygonStatusList = new 
ArrayList<PolygonStatus>();
                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<JoinedWay>();
+               
+               // 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<PolygonStatus> polygonWorkingQueue = new 
LinkedBlockingQueue<PolygonStatus>();
+               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<PolygonStatus> holes = 
getPolygonStatus(holeIndexes,
-                               !currentPolygon.outer);
+                       ArrayList<PolygonStatus> 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<Way> cutOutInnerPolygons(Way outerPolygon, List<Way> 
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<Way> 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.
         * 
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to