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