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


There have been different opinions about this patch.

There is one controversal point:
Should mkgmap allow nested inner polygons?

  From my point of view: YES. Because it does not harm in any way. The
nested inner polygons I analysed were all reasonable. Ok, they are not
100% compliant to the specification but this is common to lots of others
things in (anarchic) OSM.

At the moment I see two possible changes:
1. Issue a warning for any nested inner polygon but process it anyhow.
2. Reject nested inner polygons (and issue a warning). What a pity but
they are not 100% compliant.

Please let me know what's the opinion of mkgmap community and committers
because I want the improvements of error messages to be committed.

WanMil

The patch improves the handling of roles and warning messages in multipolygon. - A reasonable warning message for nested outer and nested inner polygons is given
- nested inner polygons are processed anyhow

WanMil

Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java      
(revision 1598)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java      
(working copy)
@@ -497,14 +497,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;
        }
@@ -513,6 +518,7 @@
         * Process the ways in this relation. Joins way with the role "outer" 
Adds
         * ways with the role "inner" to the way with the role "outer"
         */
+       @Override
        public void processElements() {
                log.info("Processing multipolygon", toBrowseURL());
 
@@ -533,17 +539,18 @@
                bboxArea = new Area(new Rectangle(bbox.getMinLong(), bbox
                        .getMinLat(), bbox.getMaxLong() - bbox.getMinLong(),
                        bbox.getMaxLat() - bbox.getMinLat()));
-               
+
+               // 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;
@@ -561,21 +568,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);
@@ -586,26 +601,39 @@
 
                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 nestedOuterPolygons = new BitSet();
+               BitSet nestedInnerPolygons = 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) {
@@ -613,11 +641,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);
@@ -632,13 +655,44 @@
                        // 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);
+                                               
nestedOuterPolygons.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 is supported
+                                       // although inner in inner is not 
officially allowed
+                                       if 
(holeIndexes.intersects(taggedInnerPolygons)) {
+                                               // process inner in inner but 
issue a warning later
+                                               BitSet addInnerNestedPolygons = 
new BitSet();
+                                               
addInnerNestedPolygons.or(holeIndexes);
+                                               
addInnerNestedPolygons.and(taggedInnerPolygons);
+                                               
nestedInnerPolygons.or(addInnerNestedPolygons);
+                                       }
+                               }
+                       } 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
@@ -646,20 +700,15 @@
                                        || 
hasPolygonTags(currentPolygon.polygon);
 
                        if (processPolygon) {
-                               List<Way> singularOuterPolygons;
-                               if (holes.isEmpty()) {
-                                       singularOuterPolygons = Collections
-                                                       .singletonList((Way) 
new JoinedWay(currentPolygon.polygon));
-                               } else {
-                                       List<Way> innerWays = new 
ArrayList<Way>(holes.size());
-                                       for (PolygonStatus polygonHoleStatus : 
holes) {
-                                               
innerWays.add(polygonHoleStatus.polygon);
-                                       }
 
-                                       singularOuterPolygons = 
cutOutInnerPolygons(currentPolygon.polygon,
-                                               innerWays);
+                               List<Way> innerWays = new 
ArrayList<Way>(holes.size());
+                               for (PolygonStatus polygonHoleStatus : holes) {
+                                       
innerWays.add(polygonHoleStatus.polygon);
                                }
-                               
+
+                               List<Way> singularOuterPolygons = 
cutOutInnerPolygons(
+                                       currentPolygon.polygon, innerWays);
+
                                if 
(currentPolygon.polygon.getOriginalWays().size() == 1) {
                                        // the original way was a closed 
polygon which
                                        // has been replaced by the new cutted 
polygon
@@ -694,11 +743,15 @@
                                }
                        }
                }
-
-               if (log.isLoggable(Level.WARNING) && 
unfinishedPolygons.isEmpty() == false) {
+               
+               if (log.isLoggable(Level.WARNING) && 
+                               
(outmostInnerPolygons.cardinality()+unfinishedPolygons.cardinality()+nestedOuterPolygons.cardinality()+nestedInnerPolygons.cardinality()
 >= 1)) {
                        log.warn("Multipolygon", toBrowseURL(), "contains 
errors.");
 
                        runIntersectionCheck(unfinishedPolygons);
+                       runOutmostInnerPolygonCheck(outmostInnerPolygons);
+                       runNestedOuterPolygonCheck(nestedOuterPolygons);
+                       runNestedInnerPolygonCheck(nestedInnerPolygons);
                        runWrongInnerPolygonCheck(unfinishedPolygons, 
innerPolygons);
 
                        // we have at least one polygon that could not be 
processed
@@ -745,6 +798,36 @@
                }
        }
 
+       private void runNestedOuterPolygonCheck(BitSet nestedOuterPolygons) {
+               // just print out warnings
+               // the check has been done before
+               for (int wiIndex = nestedOuterPolygons.nextSetBit(0); wiIndex 
>= 0; wiIndex = nestedOuterPolygons
+                               .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 runNestedInnerPolygonCheck(BitSet nestedInnerPolygons) {
+               // just print out warnings
+               // the check has been done before
+               for (int wiIndex = nestedInnerPolygons.nextSetBit(0); wiIndex 
>= 0; wiIndex = nestedInnerPolygons
+                               .nextSetBit(wiIndex + 1)) {
+                       Way innerWay = polygons.get(wiIndex);
+                       log.warn("Polygon",     innerWay, "carries role", 
getRole(innerWay), "but lies inside an inner polygon. Potentially its role 
should be outer.");
+               }
+       }       
+       
+       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 polygons that are not contained by 
any
@@ -858,7 +941,7 @@
 
                // use the java.awt.geom.Area class because it's a quick
                // implementation of what's needed
-
+               
                // this list contains all non overlapping and singular areas
                // of the outerPolygon
                Queue<AreaCutData> areasToCut = new LinkedList<AreaCutData>();
@@ -1176,29 +1259,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.
         * 
@@ -1649,6 +1709,7 @@
                        updateBounds(point);
                }
 
+               @Override
                public void addPoint(Coord point) {
                        super.addPoint(point);
                        updateBounds(point);
@@ -1749,14 +1810,7 @@
                        }
                }
 
-               public List<Long> getOriginalIds() {
-                       ArrayList<Long> idList = new 
ArrayList<Long>(getOriginalWays().size());
-                       for (Way w : getOriginalWays()) {
-                               idList.add(w.getId());
-                       }
-                       return idList;
-               }
-
+               @Override
                public String toString() {
                        StringBuilder sb = new StringBuilder(200);
                        sb.append(getId());
@@ -1864,6 +1918,7 @@
                        return this.areas.size();
                }
 
+               @Override
                public int compareTo(CutPoint o) {
                        if (this == o) {
                                return 0;
@@ -1876,6 +1931,7 @@
                        return 
(stopPoint-startPoint)-(o.stopPoint-o.startPoint); 
                }
 
+               @Override
                public String toString() {
                        return axis +" "+getNumberOfAreas()+" "+startPoint+" 
"+stopPoint+" "+getCutPoint();
                }
@@ -1928,6 +1984,7 @@
                        this.axis = axis;
                }
 
+               @Override
                public int compare(Area o1, Area o2) {
                        if (o1 == o2) {
                                return 0;
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to