v2 - now checks to see if an anti-island is surrounded by water or land.

If surrounded by water, it is converted back into an island (tagged
with the land tag) and a warning message is issued.

Tested with the GB map, it detected a handful of anti-islands that
should have been islands but their points were in the wrong order
(backwards).

---------------

This patch extends the sea generation stuff to cope with anti-islands
(water inside, land outside and, presumably, where the anti-treasure
is hidden). Anti-islands are tagged natural=water (you can't use
natural=sea because it will be beneath the surrounding land polygon).

Note that if your map contains any islands whose direction is incorrect,
they will now be filled with water. To help find such islands, a
warning message is output when an anti-island is discovered. The upside
is that if your map contains anti-islands whose direction is correct,
they will now become visible.

Mark
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Way.java b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
index a8812bf..a4d8680 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Way.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
@@ -16,6 +16,7 @@
  */
 package uk.me.parabola.mkgmap.reader.osm;
 
+import java.awt.Polygon;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -141,4 +142,37 @@ public class Way extends Element {
 	public String kind() {
 		return "way";
 	}
+
+	// returns true if the way is a closed polygon with a clockwise
+	// direction
+	public boolean clockwise() {
+
+		if(points.size() < 3 || !points.get(0).equals(points.get(points.size() - 1)))
+			return false;
+
+		long area = 0;
+		Coord p1 = points.get(0);
+		for(int i = 1; i < points.size(); ++i) {
+			Coord p2 = points.get(i);
+			area += ((long)p1.getLongitude() * p2.getLatitude() - 
+					 (long)p2.getLongitude() * p1.getLatitude());
+			p1 = p2;
+		}
+
+		// this test looks to be inverted but gives the expected result!
+		return area < 0;
+	}
+
+	// simplistic check to see if this way "contains" another - for
+	// speed, all we do is check that all of the other way's points
+	// are inside this way's polygon
+	public boolean containsPointsOf(Way other) {
+		Polygon thisPoly = new Polygon();
+		for(Coord p : points)
+			thisPoly.addPoint(p.getLongitude(), p.getLatitude());
+		for(Coord p : other.points)
+			if(!thisPoly.contains(p.getLongitude(), p.getLatitude()))
+				return false;
+		return true;
+	}
 }
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
index 7cffaed..6a85d5a 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java
@@ -1152,6 +1152,7 @@ class Osm5XmlHandler extends DefaultHandler {
 		while (it.hasNext()) {
 			Way w = it.next();
 			if (w.isClosed()) {
+				log.info("adding island " + w);
 				islands.add(w);
 				it.remove();
 			}
@@ -1168,27 +1169,8 @@ class Osm5XmlHandler extends DefaultHandler {
 			}
 		}
 
-		// create a "inner" way for each island
-		for (Way w : islands) {
-			log.info("adding island " + w);
-			if(generateSeaUsingMP)
-				seaRelation.addElement("inner", w);
-			else {
-				if(!FakeIdGenerator.isFakeId(w.getId())) {
-					Way w1 = new Way(FakeIdGenerator.makeFakeId());
-					w1.getPoints().addAll(w.getPoints());
-					// only copy the name tags
-					for(String tag : w)
-						if(tag.equals("name") || tag.endsWith(":name"))
-							w1.addTag(tag, w.getTag(tag));
-					w = w1;
-				}
-				w.addTag(landTag[0], landTag[1]);
-				wayMap.put(w.getId(), w);
-			}
-		}
-
 		boolean generateSeaBackground = true;
+		boolean coastlineIntersectsBoundary = false;
 
 		// the remaining shoreline segments should intersect the boundary
 		// find the intersection points and store them in a SortedMap
@@ -1273,6 +1255,7 @@ class Osm5XmlHandler extends DefaultHandler {
 					log.debug("hits (second try): ", hStart, hEnd);
 					hitMap.put(hStart, w);
 					hitMap.put(hEnd, null);
+					coastlineIntersectsBoundary = true;
 				}
 				else {
 					// show the coastline even though we can't produce
@@ -1285,40 +1268,9 @@ class Osm5XmlHandler extends DefaultHandler {
 				log.debug("hits: ", hStart, hEnd);
 				hitMap.put(hStart, w);
 				hitMap.put(hEnd, null);
+				coastlineIntersectsBoundary = true;
 			}
 		}
-		if (generateSeaBackground) {
-			seaId = FakeIdGenerator.makeFakeId();
-			sea = new Way(seaId);
-			if (generateSeaUsingMP) {
-				// the sea background area must be a little bigger than all
-				// inner land areas. this is a workaround for a mp shortcoming:
-				// mp is not able to combine outer and inner if they intersect
-				// or have overlaying lines
-				// the added area will be clipped later by the style generator
-				sea.addPoint(new Coord(nw.getLatitude() - 1,
-						nw.getLongitude() - 1));
-				sea.addPoint(new Coord(sw.getLatitude() + 1,
-						sw.getLongitude() - 1));
-				sea.addPoint(new Coord(se.getLatitude() + 1,
-						se.getLongitude() + 1));
-				sea.addPoint(new Coord(ne.getLatitude() - 1,
-						ne.getLongitude() + 1));
-				sea.addPoint(new Coord(nw.getLatitude() - 1,
-						nw.getLongitude() - 1));
-			} else {
-				sea.addPoint(nw);
-				sea.addPoint(sw);
-				sea.addPoint(se);
-				sea.addPoint(ne);
-				sea.addPoint(nw);
-			}
-			sea.addTag("natural", "sea");
-			log.info("sea: ", sea);
-			wayMap.put(seaId, sea);
-			if(generateSeaUsingMP)
-				seaRelation.addElement("outer", sea);
-		}
 
 		// now construct inner ways from these segments
 		NavigableSet<EdgeHit> hits = (NavigableSet<EdgeHit>) hitMap.keySet();
@@ -1379,23 +1331,108 @@ class Osm5XmlHandler extends DefaultHandler {
 			if (!w.isClosed())
 				w.getPoints().add(w.getPoints().get(0));
 			log.info("adding non-island landmass, hits.size()=" + hits.size());
-			//w.addTag("highway", "motorway");
-			if(generateSeaUsingMP)
+			islands.add(w);
+		}
+
+		List<Way> antiIslands = new ArrayList<Way>();
+
+		for (Way w : islands) {
+
+			if(generateSeaUsingMP) {
+				// create a "inner" way for each island
 				seaRelation.addElement("inner", w);
+			}
 			else {
 				if(!FakeIdGenerator.isFakeId(w.getId())) {
 					Way w1 = new Way(FakeIdGenerator.makeFakeId());
 					w1.getPoints().addAll(w.getPoints());
+					// only copy the name tags
 					for(String tag : w)
 						if(tag.equals("name") || tag.endsWith(":name"))
 							w1.addTag(tag, w.getTag(tag));
 					w = w1;
 				}
-				w.addTag(landTag[0], landTag[1]);
+				// determine where the water is
+				if(w.clockwise()) {
+					// water on the inside of the poly, it's an
+					// "anti-island" so tag with natural=water (to
+					// make it visible above the land)
+					w.addTag("natural", "water");
+					antiIslands.add(w);
+				}
+				else {
+					// water on the outside of the poly, it's an
+					// island so tag it as land
+					w.addTag(landTag[0], landTag[1]);
+				}
+
 				wayMap.put(w.getId(), w);
 			}
 		}
 
+		islands.removeAll(antiIslands);
+
+		if(!coastlineIntersectsBoundary && antiIslands.size() > 0) {
+			// the coast doesn't reach the boundary and the tile
+			// contains "anti-islands" so assume what we have is land
+			// with a sea in the middle
+			generateSeaBackground = false;
+		}
+
+		if (generateSeaBackground) {
+
+			// the background is sea so all anti-islands should be
+			// contained by land otherwise they won't be visible
+
+			for(Way ai : antiIslands) {
+				boolean containedByLand = false;
+				for(Way i : islands) {
+					if(i.containsPointsOf(ai)) {
+						containedByLand = true;
+						break;
+					}
+				}
+				if(!containedByLand) {
+					// found an anti-island that is not contained by
+					// land so convert it back into an island
+					ai.deleteTag("natural");
+					ai.addTag(landTag[0], landTag[1]);
+					log.warn("Converting anti-island starting at " + ai.getPoints().get(0).toOSMURL() + " into an island as it is surrounded by water");
+				}
+			}
+
+			seaId = FakeIdGenerator.makeFakeId();
+			sea = new Way(seaId);
+			if (generateSeaUsingMP) {
+				// the sea background area must be a little bigger than all
+				// inner land areas. this is a workaround for a mp shortcoming:
+				// mp is not able to combine outer and inner if they intersect
+				// or have overlaying lines
+				// the added area will be clipped later by the style generator
+				sea.addPoint(new Coord(nw.getLatitude() - 1,
+						nw.getLongitude() - 1));
+				sea.addPoint(new Coord(sw.getLatitude() + 1,
+						sw.getLongitude() - 1));
+				sea.addPoint(new Coord(se.getLatitude() + 1,
+						se.getLongitude() + 1));
+				sea.addPoint(new Coord(ne.getLatitude() - 1,
+						ne.getLongitude() + 1));
+				sea.addPoint(new Coord(nw.getLatitude() - 1,
+						nw.getLongitude() - 1));
+			} else {
+				sea.addPoint(nw);
+				sea.addPoint(sw);
+				sea.addPoint(se);
+				sea.addPoint(ne);
+				sea.addPoint(nw);
+			}
+			sea.addTag("natural", "sea");
+			log.info("sea: ", sea);
+			wayMap.put(seaId, sea);
+			if(generateSeaUsingMP)
+				seaRelation.addElement("outer", sea);
+		}
+
 		if(generateSeaUsingMP) {
 			seaRelation = new MultiPolygonRelation(seaRelation, wayMap);
 			relationMap.put(multiId, seaRelation);
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to