Now only lines which endpoints are outside or on the bounding box are closed.

WanMil

As András Kolesár found out the PolygonSplitter did not close all
polygons. There were several more places in the code where polygons were
not closed. I am not really sure if that caused any problems in the map
but definitely the coding was not clean.

The patch fixes the conversion between mkgmap objects (List<Coord>) and
Java2D objects (Polygon, Area). Additionally the StyleConverter ensures
that lines which are assigned with polygonal garmin types are closed by
adding the first coord to its list of points.

Please test and inspect the polygons crossing the tile borders in
particular.

WanMil


Index: src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java	(revision 1862)
+++ src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java	(working copy)
@@ -121,7 +121,7 @@
 			coords.add(co);
 			co = new Coord(bounds.getMaxLat(), bounds.getMinLong());
 			coords.add(co);
-			//coords.add(start);
+			coords.add(start);
 
 			// Now add the background area
 			MapShape background = new MapShape();
Index: src/uk/me/parabola/mkgmap/general/PolygonClipper.java
===================================================================
--- src/uk/me/parabola/mkgmap/general/PolygonClipper.java	(revision 1862)
+++ src/uk/me/parabola/mkgmap/general/PolygonClipper.java	(working copy)
@@ -16,13 +16,11 @@
  */
 package uk.me.parabola.mkgmap.general;
 
-import java.awt.*;
-import java.awt.geom.PathIterator;
-import java.util.ArrayList;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.util.Java2DConverter;
 
 /**
  * Clip a polygon to the given bounding box.  This may result in more than
@@ -43,7 +41,7 @@
 		if (bbox == null)
 			return null;
 
-			// If all the points are inside the box then we just return null
+		// If all the points are inside the box then we just return null
 		// to show that nothing was done and the line can be used.  This
 		// is expected to be the normal case.
 		boolean foundOutside = false;
@@ -56,67 +54,12 @@
 		if (!foundOutside)
 			return null;
 
-		// Convert to a awt polygon
-		Polygon polygon = new Polygon();
-		for (Coord co : coords)
-			polygon.addPoint(co.getLongitude(), co.getLatitude());
-
-		Polygon bounds = new Polygon();
-		bounds.addPoint(bbox.getMinLong(), bbox.getMinLat());
-		bounds.addPoint(bbox.getMinLong(), bbox.getMaxLat());
-		bounds.addPoint(bbox.getMaxLong(), bbox.getMaxLat());
-		bounds.addPoint(bbox.getMaxLong(), bbox.getMinLat());
-		bounds.addPoint(bbox.getMinLong(), bbox.getMinLat());
-
-		java.awt.geom.Area bbarea = new java.awt.geom.Area(bounds);
-		java.awt.geom.Area shape = new java.awt.geom.Area(polygon);
+		java.awt.geom.Area bbarea = Java2DConverter.createBoundsArea(bbox); 
+		java.awt.geom.Area shape = Java2DConverter.createArea(coords);
 
 		shape.intersect(bbarea);
 
-		return areaToShapes(shape);
+		return Java2DConverter.areaToShapes(shape);
 	}
 
-	/**
-	 * Convert the area back into {@link MapShape}s.  It is possible that the
-	 * area is multiple discontiguous polygons, so you may append more than one
-	 * shape to the output list.
-	 *
-	 * @param area The area to be converted.
-	 */
-	private static List<List<Coord>> areaToShapes(java.awt.geom.Area area) {
-		List<List<Coord>> outputs = new ArrayList<List<Coord>>();
-		float[] res = new float[6];
-		PathIterator pit = area.getPathIterator(null);
-
-		List<Coord> coords = null;
-		while (!pit.isDone()) {
-			int type = pit.currentSegment(res);
-
-			Coord co = new Coord(Math.round(res[1]), Math.round(res[0]));
-
-			if (type == PathIterator.SEG_MOVETO) {
-				// We get a move to at the beginning and if this area is actually
-				// discontiguous we may get more than one, each one representing
-				// the start of another polygon in the output.
-				if (coords != null)
-					outputs.add(coords);
-
-				coords = new ArrayList<Coord>();
-				coords.add(co);
-			} else if (type == PathIterator.SEG_LINETO) {
-				// Continuing with the path.
-				assert coords != null;
-				coords.add(co);
-			} else if (type == PathIterator.SEG_CLOSE) {
-				// The end of a polygon
-				assert coords != null;
-				coords.add(co);
-
-				outputs.add(coords);
-				coords = null;
-			}
-			pit.next();
-		}
-		return outputs;
-	}
 }
Index: src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java	(revision 1862)
+++ src/uk/me/parabola/mkgmap/filters/PolygonSplitterBase.java	(working copy)
@@ -16,14 +16,13 @@
  */
 package uk.me.parabola.mkgmap.filters;
 
-import java.awt.*;
+import java.awt.Rectangle;
 import java.awt.geom.Area;
-import java.awt.geom.PathIterator;
-import java.util.ArrayList;
 import java.util.List;
 
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.general.MapShape;
+import uk.me.parabola.util.Java2DConverter;
 
 /**
  * @author Steve Ratcliffe
@@ -38,14 +37,11 @@
 	 */
 	protected void split(MapShape shape, List<MapShape> outputs) {
 
-		// Convert to a awt polygon
-		Polygon polygon = new Polygon();
-		for (Coord co : shape.getPoints()) {
-			polygon.addPoint(co.getLongitude(), co.getLatitude());
-		}
+		// Convert to a awt area
+		Area a1 = Java2DConverter.createArea(shape.getPoints());
 
 		// Get the bounds of this polygon
-		Rectangle bounds = polygon.getBounds();
+		Rectangle bounds = a1.getBounds();
 
 		if (bounds.isEmpty())
 			return;  // Drop it
@@ -66,7 +62,6 @@
 		// Now find the intersection of these two boxes with the original
 		// polygon.  This will make two new areas, and each area will be one
 		// (or more) polygons.
-		Area a1 = new Area(polygon);
 		Area a2 = (Area) a1.clone();
 		a1.intersect(new Area(r1));
 		a2.intersect(new Area(r2));
@@ -86,42 +81,12 @@
 	 * @param outputs Used to hold output shapes.
 	 */
 	private void areaToShapes(MapShape origShape, Area area, List<MapShape> outputs) {
-		float[] res = new float[6];
-		PathIterator pit = area.getPathIterator(null);
-
-		List<Coord> coords = null;
-		while (!pit.isDone()) {
-			int type = pit.currentSegment(res);
-
-			//System.out.println("T" + type + " " + res[0] + "," + res[1] + " " + res[2] + "," + res[3] + " " + res[4] + "," + res[5]);
-			Coord co = new Coord(Math.round(res[1]), Math.round(res[0]));
-
-			if (type == PathIterator.SEG_MOVETO) {
-				// We get a moveto at the beginning and if this area is actually
-				// discontiguous we may get more than one, each one representing
-				// the start of another polygon in the output.
-				if (coords != null) {
-					MapShape s2 = origShape.copy();
-					s2.setPoints(coords);
-					outputs.add(s2);
-				}
-				coords = new ArrayList<Coord>();
-				coords.add(co);
-			} else if (type == PathIterator.SEG_LINETO) {
-				// Continuing with the path.
-				assert coords != null;
-				coords.add(co);
-			} else if (type == PathIterator.SEG_CLOSE) {
-				// The end of a polygon
-				assert coords != null;
-				coords.add(co);
-
-				MapShape s2 = origShape.copy();
-				s2.setPoints(coords);
-				outputs.add(s2);
-				coords = null;
-			}
-			pit.next();
+		List<List<Coord>> subShapePoints = Java2DConverter.areaToShapes(area);
+		
+		for (List<Coord> subShape : subShapePoints) {
+			MapShape s = origShape.copy();
+			s.setPoints(subShape);
+			outputs.add(s);
 		}
 	}
 }
Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(revision 1862)
+++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(working copy)
@@ -517,6 +517,28 @@
 	}
 
 	private void addShape(Way way, GType gt) {
+		if (way.isClosed()==false) {
+			if (bbox.insideBoundary(way.getPoints().get(0)) || 
+				bbox.insideBoundary(way.getPoints().get(way.getPoints().size()-1))) {
+				log.warn("Unclosed way",way.toBrowseURL(),"should be converted as shape but the start or end point lies inside the bbox.. Skip it.");
+				return;
+			}
+			if (bbox.allInsideBoundary(way.getPoints())) {
+				// the way does not touch or intersects the bbox
+				// do not close it
+				log.warn("Unclosed way",way.toBrowseURL(),"should be converted as shape but lies completely in the bbox. Skip it.");
+				return;
+			}
+
+			log.warn("Add unclosed way as shape. Close it automatically.",way.toBrowseURL());
+			// it is handled as shape so the way should be closed
+			// reasons for this are:
+			// * OSM data failures
+			// * the way cuts the tile bounds and the splitter did not close it
+			// * the way should be a line but the style has a polygon rule only
+			way.addPoint(way.getPoints().get(0));
+		}
+		
 		final MapShape shape = new MapShape();
 		elementSetup(shape, gt, way);
 		shape.setPoints(way.getPoints());
Index: src/uk/me/parabola/util/Java2DConverter.java
===================================================================
--- src/uk/me/parabola/util/Java2DConverter.java	(revision 1862)
+++ src/uk/me/parabola/util/Java2DConverter.java	(working copy)
@@ -11,19 +11,50 @@
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
 
+/**
+ * This is a tool class that provides static methods to convert between
+ * mkgmap objects and Java2D objects. The Java2D objects provide some 
+ * optimized polygon algorithms that are quite useful so that it makes
+ * sense to perform the conversion.
+ *  
+ * @author WanMil
+ */
 public class Java2DConverter {
-	private static final Logger log = Logger
-	.getLogger(Java2DConverter.class);
+	private static final Logger log = Logger.getLogger(Java2DConverter.class);
 
-	public static Area createArea(List<Coord> points) {
-		return new Area(createPolygon(points));
+	/**
+	 * Creates a Java2D {@link Area} object from the given mkgmap rectangular
+	 * {@link uk.me.parabola.imgfmt.app.Area} object.
+	 * 
+	 * @param bbox a rectangular bounding box
+	 * @return the converted Java2D area
+	 */
+	public static Area createBoundsArea(uk.me.parabola.imgfmt.app.Area bbox) {
+		Polygon bboxPoints = new Polygon();
+		bboxPoints.addPoint(bbox.getMinLong(), bbox.getMinLat());
+		bboxPoints.addPoint(bbox.getMinLong(), bbox.getMaxLat());
+		bboxPoints.addPoint(bbox.getMaxLong(), bbox.getMaxLat());
+		bboxPoints.addPoint(bbox.getMaxLong(), bbox.getMinLat());
+		bboxPoints.addPoint(bbox.getMinLong(), bbox.getMinLat());
+		
+		return new Area(bboxPoints);
+	}
+	
+	/**
+	 * Creates a Java2D {@link Area} object from a polygon given as a list of
+	 * {@link Coord} objects. This list should describe a closed polygon.
+	 * 
+	 * @param polygonPoints a list of points that describe a closed polygon
+	 * @return the converted Java2D area
+	 */
+	public static Area createArea(List<Coord> polygonPoints) {
+		return new Area(createPolygon(polygonPoints));
 	}
 	
 	/**
 	 * Create a polygon from a list of points.
 	 * 
-	 * @param points
-	 *            list of points
+	 * @param points list of points
 	 * @return the polygon
 	 */
 	public static Polygon createPolygon(List<Coord> points) {
@@ -38,8 +69,7 @@
 	 * Convert an area that may contains multiple areas to a list of singular
 	 * areas
 	 * 
-	 * @param area
-	 *            an area
+	 * @param area an area
 	 * @return list of singular areas
 	 */
 	public static List<Area> areaToSingularAreas(Area area) {
@@ -101,8 +131,7 @@
 	 * Convert an area to an mkgmap way. The caller must ensure that the area is singular.
 	 * Otherwise only the first part of the area is converted.
 	 * 
-	 * @param area
-	 *            the area
+	 * @param area the area
 	 * @return a new mkgmap way
 	 */
 	public static List<Coord> singularAreaToPoints(Area area) {
@@ -141,4 +170,55 @@
 		return points;
 	}
 	
+	/**
+	 * Convert the area back into a list of polygons each represented by a list of coords. 
+	 * It is possible that the area contains multiple discontiguous polygons, so you may 
+	 * append more than one shape to the output list.<br/>
+	 * <b>Attention:</b> The outline of holes in the area are converted as a polygon too. There
+	 * is no way to check which returned polygon describes the outer shape and which one describes
+	 * the hole. So it is better not to use this method with holes. 
+	 *
+	 * @param area The area to be converted.
+	 * @return a list of closed polygons
+	 */
+	public static List<List<Coord>> areaToShapes(java.awt.geom.Area area) {
+		List<List<Coord>> outputs = new ArrayList<List<Coord>>(4);
+		float[] res = new float[6];
+		PathIterator pit = area.getPathIterator(null);
+
+		List<Coord> coords = null;
+		while (!pit.isDone()) {
+			int type = pit.currentSegment(res);
+
+			Coord co = new Coord(Math.round(res[1]), Math.round(res[0]));
+
+			if (type == PathIterator.SEG_MOVETO) {
+				// We get a move to at the beginning and if this area is actually
+				// discontiguous we may get more than one, each one representing
+				// the start of another polygon in the output.
+				if (coords != null) {
+					// this should not happen because each polygon is ended
+					// with a SEG_CLOSE
+					outputs.add(coords);
+				}
+
+				coords = new ArrayList<Coord>();
+				coords.add(co);
+			} else if (type == PathIterator.SEG_LINETO) {
+				// Continuing with the path.
+				assert coords != null;
+				coords.add(co);
+			} else if (type == PathIterator.SEG_CLOSE) {
+				// The end of a polygon
+				assert coords != null;
+				// close the polygon
+				coords.add(coords.get(0));
+				
+				outputs.add(coords);
+				coords = null;
+			}
+			pit.next();
+		}
+		return outputs;
+	}
 }
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to