Compared to v3 (posted by Carlos in thread "Wrong multipolygon warnings") some unused debug messages have been removed.

The patch enables the multipolygon code to process multipolygons with overlapping lines.

WanMil
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<Long, String> roleMap = new HashMap<Long, String>();
 
        private ArrayList<BitSet> containsMatrix;
-       private ArrayList<JoinedWay> rings;
-       private Set<JoinedWay> intersectingRings;
+       private ArrayList<JoinedWay> polygons;
+       private Set<JoinedWay> 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.<br/>
+        * 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 
<var>candidates</var>
+        * Finds all polygons that are not contained by any other polygons and 
that match
+        * to the given role. All polygons with index given by 
<var>candidates</var>
         * 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<RingStatus> getRingStatus(BitSet outmostRings,
+       private ArrayList<PolygonStatus> getPolygonStatus(BitSet 
outmostPolygons,
                        boolean outer) {
-               ArrayList<RingStatus> ringStatusList = new 
ArrayList<RingStatus>();
-               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<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));
                }
-               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<JoinedWay>();
-               createContainsMatrix(rings);
+
+               // the intersectingPolygons marks all intersecting/overlapping 
polygons
+               intersectingPolygons = new HashSet<JoinedWay>();
+               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<RingStatus> ringWorkingQueue = new 
LinkedBlockingQueue<RingStatus>();
 
-               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<PolygonStatus> polygonWorkingQueue = new 
LinkedBlockingQueue<PolygonStatus>();
+
+               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<RingStatus> holes = getRingStatus(holeIndexes,
-                                       !currentRing.outer);
+                       ArrayList<PolygonStatus> 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<Way> innerWays = new 
ArrayList<Way>(holes.size());
-                               for (RingStatus holeRingStatus : holes) {
-                                       innerWays.add(holeRingStatus.ring);
+                               for (PolygonStatus polygonHoleStatus : holes) {
+                                       
innerWays.add(polygonHoleStatus.polygon);
                                }
 
-                               List<Way> singularOuterPolygons = 
cutOutInnerRings(
-                                               currentRing.ring, innerWays);
+                               List<Way> 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<JoinedWay> lostWays = 
getWaysFromRinglist(unfinishedRings);
+                       List<JoinedWay> 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<Way> cutOutInnerRings(Way outerRing, List<Way> innerRings) 
{
+       private List<Way> cutOutInnerPolygons(Way outerPolygon, List<Way> 
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<Area> outerAreas = new ArrayList<Area>();
 
-               // 1st create an Area object of the outerRing and put it to the 
list
-               List<Area> oa = createAreas(outerRing);
+               // 1st create an Area object of the outerPolygon and put it to 
the list
+               List<Area> 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<Area> innerAreas = new ArrayList<Area>();
-               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<Area> outerAfterThisStep = new ArrayList<Area>();
                        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<Way> cutOuterRing = new ArrayList<Way>(outerAreas.size());
+               List<Way> cutOuterPolygon = new 
ArrayList<Way>(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<Area> 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<Coord> middlePoints2 = new 
ArrayList<Coord>(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<Coord> it1 = ring1.getPoints().iterator();
+               if (onePointContained == false) {
+                       // no point of polygon2 is in polygon1 => polygon1 does 
not contain polygon2
+                       return false;
+               }
+               
+               Iterator<Coord> 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<Coord> it2 = ring2.getPoints().iterator();
+                       Iterator<Coord> 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<JoinedWay> 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<Coord> 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<JoinedWay> getWaysFromPolygonList(BitSet selection) {
                if (selection.isEmpty()) {
                        return Collections.emptyList();
                }
-               List<JoinedWay> wayList = new 
ArrayList<JoinedWay>(selection.cardinality());
-               for (int i = selection.nextSetBit(0); i >= 0; i = 
selection.nextSetBit(i+1)) {
-                       wayList.add(rings.get(i));
+               List<JoinedWay> wayList = new ArrayList<JoinedWay>(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;
                }
        }
 }
_______________________________________________
mkgmap-dev mailing list
mkgmap-dev@lists.mkgmap.org.uk
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to