[mkgmap] 01/04: Imported Upstream version 0.0.0+svn3706

Bas Couwenberg sebastic at debian.org
Thu Dec 1 17:46:10 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch master
in repository mkgmap.

commit 28f661fe2c2e027a5ccc8c463a62146b0345676b
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Thu Dec 1 18:31:11 2016 +0100

    Imported Upstream version 0.0.0+svn3706
---
 doc/options.txt                                    |   8 ++
 doc/styles/internal-tags.txt                       |   3 +-
 resources/help/en/options                          |   6 +
 resources/mkgmap-version.properties                |   4 +-
 resources/styles/default/inc/water_polygons        |   2 +-
 src/uk/me/parabola/imgfmt/app/Area.java            |  70 +++++++---
 src/uk/me/parabola/mkgmap/build/MapArea.java       | 146 ++++++++++++++++++++-
 src/uk/me/parabola/mkgmap/build/MapBuilder.java    |  28 +++-
 src/uk/me/parabola/mkgmap/build/MapSplitter.java   |  28 ++--
 .../parabola/mkgmap/filters/ShapeMergeFilter.java  |   7 +-
 src/uk/me/parabola/mkgmap/general/LevelInfo.java   |   4 +-
 src/uk/me/parabola/mkgmap/general/MapShape.java    |  19 +++
 .../parabola/mkgmap/osmstyle/StyledConverter.java  |  21 +++
 src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java |   4 +-
 .../mkgmap/osmstyle/function/AreaSizeFunction.java |  23 +++-
 .../mkgmap/reader/osm/MultiPolygonRelation.java    |   4 +
 .../parabola/mkgmap/reader/osm/SeaGenerator.java   |  16 ++-
 src/uk/me/parabola/mkgmap/reader/osm/Way.java      |  15 +++
 .../mkgmap/filters/ShapeMergeFilterTest.java       |   2 +-
 19 files changed, 359 insertions(+), 51 deletions(-)

diff --git a/doc/options.txt b/doc/options.txt
index 0bf5b0b..3c227b7 100644
--- a/doc/options.txt
+++ b/doc/options.txt
@@ -705,3 +705,11 @@ Default is enabled, use --no-poi-address to disable.
 <p>
 ;--verbose
 : 	Makes some operations more verbose. Mostly used with --list-styles.
+<p>
+;--order-by-decreasing-area
+:	Puts area/polygons into the mapfile in decreasing size so
+that smaller features are rendered over larger ones
+(assuming _drawOrder is equal).
+The tag mkgmap:drawLevel can be used to override the
+natural area of a polygon, so forcing changes to the rendering order. 
+
diff --git a/doc/styles/internal-tags.txt b/doc/styles/internal-tags.txt
index 80643f2..67f64e9 100644
--- a/doc/styles/internal-tags.txt
+++ b/doc/styles/internal-tags.txt
@@ -133,6 +133,5 @@ is used to assign the country location.
 | +mkgmap:highest-resolution-only+  | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.
 | +mkgmap:execute_finalize_rules+  | If set to +true+ mkgmap will execute the finalize rules even if no object is created fot the element.
 | +mkgmap:numbers+  | If set to +false+ for a node or way mkgmap will ignore the object in the calculations for the --housenumber option   
+| +mkgmap:drawLevel+  | Set to a number from 1 to 100. Overrides the polygon area that is used by --order-by-decreasing-area. 1..50 are larger than typical polygons and be overwritten by them, 51..100 are smaller and will show. Higher drawLevels will show over lower values.
 |=========================================================
-
-
diff --git a/resources/help/en/options b/resources/help/en/options
index 169f91f..3306ac1 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -699,3 +699,9 @@ Miscellaneous options:
 
 --verbose
 	Makes some operations more verbose. Mostly used with --list-styles.
+
+--order-by-decreasing-area
+	Puts polygons/areas into the mapfile in decreasing size so that
+	smaller features are rendered over larger ones (assuming _drawOrder
+	is equal). The tag mkgmap:drawLevel can be used to override the
+	natural area of a polygon, so forcing changes to the rendering order. 
diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties
index 13c137c..4d2e788 100644
--- a/resources/mkgmap-version.properties
+++ b/resources/mkgmap-version.properties
@@ -1,2 +1,2 @@
-svn.version: 3701
-build.timestamp: 2016-10-30T22:57:00+0000
+svn.version: 3706
+build.timestamp: 2016-11-28T13:14:14+0000
diff --git a/resources/styles/default/inc/water_polygons b/resources/styles/default/inc/water_polygons
index d59f0fd..25d5c0f 100644
--- a/resources/styles/default/inc/water_polygons
+++ b/resources/styles/default/inc/water_polygons
@@ -8,6 +8,6 @@ natural=mud [0x51 resolution 20]
 natural=wetland [0x51 resolution 20]
 natural=water [0x3c resolution 18]
 natural=waterfall | waterway=waterfall [0x47 resolution 21]
-natural=sea { add mkgmap:skipSizeFilter=true } [0x32 resolution 10]
+natural=sea { add mkgmap:skipSizeFilter=true; set mkgmap:drawLevel=2 } [0x32 resolution 10]
 
 waterway=riverbank [0x46 resolution 20]
diff --git a/src/uk/me/parabola/imgfmt/app/Area.java b/src/uk/me/parabola/imgfmt/app/Area.java
index 2bc6ac0..fb775fe 100644
--- a/src/uk/me/parabola/imgfmt/app/Area.java
+++ b/src/uk/me/parabola/imgfmt/app/Area.java
@@ -19,6 +19,7 @@ package uk.me.parabola.imgfmt.app;
 import java.util.ArrayList;
 import java.util.List;
 
+import uk.me.parabola.imgfmt.MapFailedException;
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.log.Logger;
 
@@ -104,43 +105,72 @@ public class Area {
 	}
 
 	/**
+	 * Round integer to nearest power of 2.
+	 *
+	 * @param val The number of be rounded.
+	 * @param shift The power of 2.
+	 * @return The rounded number (binary half rounds up).
+	 */
+	private static int roundPof2(int val, int shift) {
+		if (shift <= 0)
+			return val;
+		return (((val >> (shift-1)) + 1) >> 1) << shift;
+	}
+
+	/**
 	 * Split this area up into a number of smaller areas.
 	 *
 	 * @param xsplit The number of pieces to split this area into in the x
 	 * direction.
 	 * @param ysplit The number of pieces to split this area into in the y
 	 * direction.
-	 * @return An area containing xsplit*ysplit areas.
+	 * @param resolutionShift round to this power of 2.
+	 * @return An array containing xsplit*ysplit areas or null if can't split in half.
+	 * @throws MapFailedException if more complex split operation couldn't be honoured.
 	 */
-	public Area[] split(int xsplit, int ysplit) {
+	public Area[] split(int xsplit, int ysplit, int resolutionShift) {
 		Area[] areas =  new Area[xsplit * ysplit];
 
+		int xstart;
+		int xend;
+		int ystart;
+		int yend;
+		int nAreas = 0;
 
-		int xsize = getWidth() / xsplit;
-		int ysize = getHeight() / ysplit;
-
-		int xextra = getWidth() - xsize * xsplit;
-		int yextra = getHeight() - ysize * ysplit;
-		
+		xstart = minLong;
 		for (int x = 0; x < xsplit; x++) {
-			int xstart = minLong + x * xsize;
-			int xend = xstart + xsize;
 			if (x == xsplit - 1)
-				xend += xextra;
-
+				xend = maxLong;
+			else
+				xend = roundPof2(xstart + (maxLong - xstart) / (xsplit - x),
+						 resolutionShift);
+			ystart = minLat;
 			for (int y = 0; y < ysplit; y++) {
-				int ystart = minLat + y * ysize;
-				int yend = ystart + ysize;
 				if (y == ysplit - 1)
-					yend += yextra;
-				Area a = new Area(ystart, xstart, yend, xend);
-				log.debug(x, y, a);
-				areas[x * ysplit + y] = a;
+					yend = maxLat;
+				else
+					yend = roundPof2(ystart + (maxLat - ystart) / (ysplit - y),
+							 resolutionShift);
+				if (xstart < xend && ystart < yend) {
+					Area a = new Area(ystart, xstart, yend, xend);
+//					log.debug(x, y, a);
+					log.debug("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "to", ystart, xstart, yend, xend);
+					areas[nAreas++] = a;
+				} else
+					log.warn("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "can't", xsplit, ysplit);
+				ystart = yend;
 			}
+			xstart = xend;
 		}
 
-		assert areas.length == xsplit * ysplit;
-		return areas;
+//		assert areas.length == xsplit * ysplit;
+		if (nAreas == areas.length) // no problem
+			return areas;
+// beware - MapSplitter.splitMaxSize requests split of 1/1 if the original area wasn't too big
+		else if (nAreas == 1) // failed to split in half
+			return null;  
+		else
+			throw new MapFailedException("Area split shift align problems");
 	}
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/build/MapArea.java b/src/uk/me/parabola/mkgmap/build/MapArea.java
index df899f4..17014f9 100644
--- a/src/uk/me/parabola/mkgmap/build/MapArea.java
+++ b/src/uk/me/parabola/mkgmap/build/MapArea.java
@@ -20,6 +20,10 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
+import uk.me.parabola.imgfmt.Utils;
+import uk.me.parabola.util.Java2DConverter;
 import uk.me.parabola.imgfmt.app.Area;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.trergn.Overview;
@@ -89,6 +93,8 @@ public class MapArea implements MapDataSource {
 	/** The resolution that this area is at */
 	private final int areaResolution;
 
+	private Long2ObjectOpenHashMap<Coord> areasHashMap;
+
 	/**
 	 * Create a map area from the given map data source.  This map
 	 * area will have the same bounds as the map data source and
@@ -175,15 +181,49 @@ public class MapArea implements MapDataSource {
 	 * Split this area into several pieces. All the map elements are reallocated
 	 * to the appropriate subarea.  Usually this instance would now be thrown
 	 * away and the new sub areas used instead.
+	 * <p>
+	 * if orderByDecreasingArea, the split is forced onto boundaries that can
+	 * be represented exactly with the relevant shift for the level.
+	 * This can cause the split to fail because all the lines/shapes that need
+	 * to be put at this level are here, but represented at the highest resolution
+	 * without any filtering relevant to the resolution and the logic to request
+	 * splitting considers this too much for a subDivision, even though it will
+	 * mostly will disappear when we come to write it and look meaningless -
+	 * the subDivision has been reduced to a single point at its shift level.
+	 * <p>
+	 * The lines/shapes should have been simplified much earlier in the process,
+	 * then they could appear as such in reasonably size subDivision.
+	 * The logic of levels, lines and shape placement, simplification, splitting and
+	 * other filtering, subDivision splitting etc needs a re-think and re-organisation.
 	 *
 	 * @param nx The number of pieces in the x (longitude) direction.
 	 * @param ny The number of pieces in the y direction.
 	 * @param resolution The resolution of the level.
 	 * @param bounds the bounding box that is used to create the areas.  
-	 * @return An array of the new MapArea's.
+	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
+	 * @return An array of the new MapArea's or null if can't split.
 	 */
-	public MapArea[] split(int nx, int ny, int resolution, Area bounds) {
-		Area[] areas = bounds.split(nx, ny);
+	public MapArea[] split(int nx, int ny, int resolution, Area bounds, boolean orderByDecreasingArea) {
+		int resolutionShift = orderByDecreasingArea ? (24 - resolution) : 0;
+		Area[] areas = bounds.split(nx, ny, resolutionShift);
+		if (areas == null) { //  Failed to split!
+			if (log.isDebugEnabled()) { // see what is here
+				for (MapLine e : this.lines)
+					if (e.getMinResolution() <= areaResolution)
+						log.debug("line. locn=", e.getPoints().get(0).toOSMURL(),
+							 " type=", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()),
+							 " name=", e.getName(), " min=", e.getMinResolution(), " max=", e.getMaxResolution());
+				for (MapShape e : this.shapes)
+					if (e.getMinResolution() <= areaResolution)
+						log.debug("shape. locn=", e.getPoints().get(0).toOSMURL(),
+							 " type=", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()),
+							 " name=", e.getName(), " min=", e.getMinResolution(), " max=", e.getMaxResolution(),
+							 " full=", e.getFullArea(),
+							 " calc=", uk.me.parabola.mkgmap.filters.ShapeMergeFilter.calcAreaSizeTestVal(e.getPoints()));
+				// the main culprits are lots of bits of sea and coastline in an overview map (res 12)
+			}
+			return null;
+		}
 		MapArea[] mapAreas = new MapArea[nx * ny];
 		log.info("Splitting area " + bounds + " into " + nx + "x" + ny + " pieces at resolution " + resolution);
 		boolean useNormalSplit = true;
@@ -237,6 +277,10 @@ public class MapArea implements MapDataSource {
 			}
 
 			for (MapShape e : this.shapes) {
+				if (orderByDecreasingArea) { // need to treat shapes consistently, regardless of useNormalSplit
+					splitIntoAreas(mapAreas, e, used);
+					continue;
+				}
 				if (useNormalSplit){
 					areaIndex = pickArea(mapAreas, e, xbase30, ybase30, nx, ny, dx30, dy30);
 					if (e.getBounds().getHeight() > maxHeight || e.getBounds().getWidth() > maxWidth){
@@ -260,6 +304,7 @@ public class MapArea implements MapDataSource {
 				 * them equally to the two areas.  
 				 */
 				useNormalSplit = false;
+				log.warn("useNormalSplit false");
 				continue;
 			} 
 			
@@ -610,6 +655,101 @@ public class MapArea implements MapDataSource {
 	}
 
 	/**
+	 * Spit the polygon into areas
+	 *
+	 * Using .intersect() here is expensive. The code should be changed to
+	 * use a simple rectangle clipping algorithm as in, say, 
+	 * util/ShapeSplitter.java
+	 *
+	 * @param areas The available areas to choose from.
+	 * @param e The map element.
+	 * @param used flag vector to say area has been added to.
+	 */
+	private void splitIntoAreas(MapArea[] areas, MapShape e, boolean[] used)
+	{
+		// quick check if bbox of shape lies fully inside one of the areas
+		Area shapeBounds = e.getBounds();
+
+		// this is worked out at standard precision, along with Area.contains() and so can get
+		// tricky problems as it might not really be fully within the area.
+		// so: pretend the shape is a touch bigger. Will get the optimisation most of the time
+		// and in the boundary cases will fall into the precise code.
+		shapeBounds = new Area(shapeBounds.getMinLat()-2,
+				       shapeBounds.getMinLong()-2,
+				       shapeBounds.getMaxLat()+2,
+				       shapeBounds.getMaxLong()+2);
+		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
+			if (areas[areaIndex].getBounds().contains(shapeBounds)) {
+				used[areaIndex] = true;
+				areas[areaIndex].addShape(e);
+				return;
+			}
+		}
+		// Shape crosses area(s), we have to split it
+
+		// Convert to a awt area
+		List<Coord> coords = e.getPoints();
+		java.awt.geom.Area area = Java2DConverter.createArea(coords);
+		// remember actual coord, so can re-use
+		int origSize = coords.size();
+		Long2ObjectOpenHashMap<Coord> shapeHashMap = new Long2ObjectOpenHashMap<>(origSize);
+		for (int i = 0; i < origSize; ++i) {
+			Coord co = coords.get(i);
+			shapeHashMap.put(Utils.coord2Long(co), co);
+		}
+		if (areasHashMap == null)
+			areasHashMap = new Long2ObjectOpenHashMap<>();
+
+		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
+			java.awt.geom.Area clipper = Java2DConverter.createBoundsArea(areas[areaIndex].getBounds());
+			clipper.intersect(area);
+			List<List<Coord>> subShapePoints = Java2DConverter.areaToShapes(clipper);
+			for (List<Coord> subShape : subShapePoints) {
+				// Use original or share newly created coords on clipped edge.
+				// NB: .intersect()/areaToShapes can output flattened shapes,
+				// normally triangles, in any orientation; check we haven't got one by calc area.
+				long signedAreaSize = 0;
+				int subSize = subShape.size();
+				int c1_highPrecLat = 0, c1_highPrecLon = 0;
+				int c2_highPrecLat, c2_highPrecLon;
+				for (int i = 0; i < subSize; ++i) {
+					Coord co = subShape.get(i);
+					c2_highPrecLat = co.getHighPrecLat();
+					c2_highPrecLon = co.getHighPrecLon();
+					if (i > 0)
+						signedAreaSize += (long)(c2_highPrecLon + c1_highPrecLon) *
+									(c1_highPrecLat - c2_highPrecLat);
+					c1_highPrecLat = c2_highPrecLat;
+					c1_highPrecLon = c2_highPrecLon;
+					long hashVal = Utils.coord2Long(co);
+					Coord replCoord = shapeHashMap.get(hashVal);
+					if (replCoord != null)
+						subShape.set(i, replCoord);
+					else { // not an original coord
+						replCoord = areasHashMap.get(hashVal);
+						if (replCoord != null)
+							subShape.set(i, replCoord);
+						else
+							areasHashMap.put(hashVal, co);
+					}
+				}
+				if (signedAreaSize == 0) {
+					log.warn("splitIntoAreas flat shape. id", e.getOsmid(),
+						 "type", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()), subSize,
+						 "points, at", subShape.get(0).toOSMURL());
+					continue;
+				}
+				MapShape s = e.copy();
+				s.setPoints(subShape);
+				s.setClipped(true);
+				areas[areaIndex].addShape(s);
+				used[areaIndex] = true;
+			}
+		}
+	}
+
+
+	/**
 	 * @return true if this area contains any data
 	 */
 	public boolean hasData() {
diff --git a/src/uk/me/parabola/mkgmap/build/MapBuilder.java b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
index 27e169d..865c46a 100644
--- a/src/uk/me/parabola/mkgmap/build/MapBuilder.java
+++ b/src/uk/me/parabola/mkgmap/build/MapBuilder.java
@@ -21,12 +21,16 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Set;
 
+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
+
 import uk.me.parabola.imgfmt.ExitException;
+import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.imgfmt.app.Exit;
 import uk.me.parabola.imgfmt.app.Label;
@@ -147,6 +151,8 @@ public class MapBuilder implements Configurable {
 
 	private String licenseFileName;
 
+	private boolean orderByDecreasingArea;
+
 	public MapBuilder() {
 		regionName = null;
 		locationAutofill = Collections.emptySet();
@@ -188,6 +194,7 @@ public class MapBuilder implements Configurable {
 			driveOnLeft = true;
 		if ("right".equals(driveOn))
 			driveOnLeft = false;
+		orderByDecreasingArea = props.getProperty("order-by-decreasing-area", false);
 	}
 
 	/**
@@ -685,7 +692,7 @@ public class MapBuilder implements Configurable {
 			for (SourceSubdiv srcDivPair : srcList) {
 
 				MapSplitter splitter = new MapSplitter(srcDivPair.getSource(), zoom);
-				MapArea[] areas = splitter.split();
+				MapArea[] areas = splitter.split(orderByDecreasingArea);
 				log.info("Map region", srcDivPair.getSource().getBounds(), "split into", areas.length, "areas at resolution", zoom.getResolution());
 
 				for (MapArea area : areas) {
@@ -712,16 +719,16 @@ public class MapBuilder implements Configurable {
 	 * @param shapes the list of shapes
 	 */
 	private void prepShapesForMerge(List<MapShape> shapes) {
-		HashMap<Coord,Coord> coordMap = new HashMap<>();
+		Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>();
 		for (MapShape s : shapes){
 			List<Coord> points = s.getPoints();
 			int n = points.size();
 			for (int i = 0; i< n; i++){
 				Coord co = points.get(i);
-				Coord repl = coordMap.get(co);
+				Coord repl = coordMap.get(Utils.coord2Long(co));
 				if (repl == null)
-					coordMap.put(co, co);
-				else 
+					coordMap.put(Utils.coord2Long(co), co);
+				else
 					points.set(i, repl);
 			}
 		}
@@ -1115,11 +1122,20 @@ public class MapBuilder implements Configurable {
 		config.setRoutable(doRoads);
 		
 		if (mergeShapes){
-			ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res);
+			ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea);
 			List<MapShape> mergedShapes = shapeMergeFilter.merge(shapes);
 			shapes = mergedShapes;
 		}
 		
+		if (orderByDecreasingArea && shapes.size() > 1) {
+			// sort so that the shape with the largest area is processed first
+			Collections.sort(shapes, new Comparator<MapShape>() {
+				public int compare(MapShape s1, MapShape s2) {
+					return Long.compare(Math.abs(s2.getFullArea()), Math.abs(s1.getFullArea()));
+				}
+			});
+		}
+
 		preserveHorizontalAndVerticalLines(res, shapes);
 		
 		LayerFilterChain filters = new LayerFilterChain(config);
diff --git a/src/uk/me/parabola/mkgmap/build/MapSplitter.java b/src/uk/me/parabola/mkgmap/build/MapSplitter.java
index b3884d5..96176fc 100644
--- a/src/uk/me/parabola/mkgmap/build/MapSplitter.java
+++ b/src/uk/me/parabola/mkgmap/build/MapSplitter.java
@@ -91,20 +91,21 @@ public class MapSplitter {
 	 *
 	 * This routine is not called recursively.
 	 *
+	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 * @return An array of map areas, each of which is within the size limit
 	 * and the limit on the number of features.
 	 */
-	public MapArea[] split() {
+	public MapArea[] split(boolean orderByDecreasingArea) {
 		log.debug("orig area", mapSource.getBounds());
 
 		MapArea ma = initialArea(mapSource);
-		MapArea[] areas = splitMaxSize(ma);
+		MapArea[] areas = splitMaxSize(ma, orderByDecreasingArea);
 
 		// Now step through each area and see if any have too many map features
 		// in them.  For those that do, we further split them.  This is done
 		// recursively until everything fits.
 		List<MapArea> alist = new ArrayList<>();
-		addAreasToList(areas, alist, 0);
+		addAreasToList(areas, alist, 0, orderByDecreasingArea);
 
 		MapArea[] results = new MapArea[alist.size()];
 		return alist.toArray(results);
@@ -118,8 +119,9 @@ public class MapSplitter {
 	 * @param areas The areas to add to the list (and possibly split up).
 	 * @param alist The list that will finally contain the complete list of
 	 * map areas.
+	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 */
-	private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth) {
+	private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth, boolean orderByDecreasingArea) {
 		int res = zoom.getResolution();
 		for (MapArea area : areas) {
 			Area bounds = area.getBounds();
@@ -164,11 +166,16 @@ public class MapSplitter {
 						log.debug("splitting area", area);
 					MapArea[] sublist;
 					if(bounds.getWidth() > bounds.getHeight())
-						sublist = area.split(2, 1, res, bounds);
+						sublist = area.split(2, 1, res, bounds, orderByDecreasingArea);
 					else
-						sublist = area.split(1, 2, res, bounds);
-					addAreasToList(sublist, alist, depth + 1);
-					continue;
+						sublist = area.split(1, 2, res, bounds, orderByDecreasingArea);
+					if (sublist == null) {
+						log.warn("SubDivision is single point at this resolution so can't split at " +
+							 area.getBounds().getCenter().toOSMURL() + " (probably harmless)");
+					} else {
+						addAreasToList(sublist, alist, depth + 1, orderByDecreasingArea);
+						continue;
+					}
 				} else {
 					log.error("Area too small to split at " + area.getBounds().getCenter().toOSMURL() + " (reduce the density of points, length of lines, etc.)");
 				}
@@ -192,9 +199,10 @@ public class MapSplitter {
 	 * If the area is already small enough then it will be returned unchanged.
 	 *
 	 * @param mapArea The area that needs to be split down.
+	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 * @return An array of map areas.  Each will be below the max size.
 	 */
-	private MapArea[] splitMaxSize(MapArea mapArea) {
+	private MapArea[] splitMaxSize(MapArea mapArea, boolean orderByDecreasingArea) {
 		Area bounds = mapArea.getFullBounds();
 
 		int shift = zoom.getShiftValue();
@@ -214,7 +222,7 @@ public class MapSplitter {
 		if (height > MAX_DIVISION_SIZE)
 			ysplit = height / MAX_DIVISION_SIZE + 1;
 
-		return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds);
+		return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds, orderByDecreasingArea);
 	}
 
 	/**
diff --git a/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java b/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
index caeda7f..74218e3 100644
--- a/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
+++ b/src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
@@ -41,9 +41,11 @@ public class ShapeMergeFilter{
 	private static final Logger log = Logger.getLogger(ShapeMergeFilter.class);
 	private final int resolution;
 	private final ShapeHelper dupShape = new ShapeHelper(new ArrayList<Coord>(0)); 
+	private final boolean orderByDecreasingArea;
 
-	public ShapeMergeFilter(int resolution) {
+	public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) {
 		this.resolution = resolution;
+		this.orderByDecreasingArea = orderByDecreasingArea;
 	}
 
 	public List<MapShape> merge(List<MapShape> shapes) {
@@ -84,6 +86,9 @@ public class ShapeMergeFilter{
 			for (Map<MapShape, List<ShapeHelper>> lowMap : sameTypeList){
 				boolean added = false;
 				for (MapShape ms: lowMap.keySet()){
+					if (orderByDecreasingArea && ms.getFullArea() != shape.getFullArea())
+						// must not merge areas unless derived from same thing
+						continue;
 					// we do not use isSimilar() here, as it compares minRes and maxRes as well
 					String s1 = ms.getName();
 					String s2 = shape.getName();
diff --git a/src/uk/me/parabola/mkgmap/general/LevelInfo.java b/src/uk/me/parabola/mkgmap/general/LevelInfo.java
index 6ea3d0e..d0e7ef0 100644
--- a/src/uk/me/parabola/mkgmap/general/LevelInfo.java
+++ b/src/uk/me/parabola/mkgmap/general/LevelInfo.java
@@ -68,10 +68,10 @@ public class LevelInfo implements Comparable<LevelInfo> {
 			try {
 				int key = Integer.parseInt(keyVal[0]);
 				if (key < 0 || key > 16)
-					throw new ExitException("Error: Level value out of range 0-16: " + s);
+					throw new ExitException("Error: Level value out of range 0-16: " + s + " in levels specification " + levelSpec);
 				int value = Integer.parseInt(keyVal[1]);
 				if (value <= 0 || value > 24)
-					throw new ExitException("Error: Resolution value out of range 0-24: " + s);
+					throw new ExitException("Error: Resolution value out of range 0-24: " + s + " in levels specification " + levelSpec);
 				levels[count] = new LevelInfo(key, value);
 			} catch (NumberFormatException e) {
 				throw new ExitException("Error: Levels specification not all numbers: " + levelSpec + " check " + s);
diff --git a/src/uk/me/parabola/mkgmap/general/MapShape.java b/src/uk/me/parabola/mkgmap/general/MapShape.java
index 54c462f..a3d7b7e 100644
--- a/src/uk/me/parabola/mkgmap/general/MapShape.java
+++ b/src/uk/me/parabola/mkgmap/general/MapShape.java
@@ -15,6 +15,9 @@
  */
 package uk.me.parabola.mkgmap.general;
 
+import uk.me.parabola.imgfmt.app.Coord;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
+
 /**
  * A shape or polygon is just the same as a line really as far as I can tell.
  * There are some things that you cannot do with them semantically.
@@ -23,6 +26,7 @@ package uk.me.parabola.mkgmap.general;
  */
 public class MapShape extends MapLine {// So top code can link objects from here
 	private long osmid; //TODO: remove debug aid
+	private long fullArea = Long.MAX_VALUE; // meaning unset
 	public MapShape() {
 		osmid = 0;
 	}
@@ -32,6 +36,7 @@ public class MapShape extends MapLine {// So top code can link objects from here
 	MapShape(MapShape s) {
 		super(s);
 		this.osmid = s.osmid;
+		this.fullArea = s.getFullArea();
 	}
 
 	public MapShape copy() {
@@ -50,4 +55,18 @@ public class MapShape extends MapLine {// So top code can link objects from here
 	public long getOsmid() {
 		return osmid;
 	}
+
+	public void setFullArea(long fullArea) {
+		this.fullArea = fullArea;
+	}
+
+	public long getFullArea() { // this is unadulterated size, +ve if clockwise
+		if (this.fullArea == Long.MAX_VALUE) {
+			java.util.List<Coord> points = this.getPoints();
+			if (points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1)))
+				this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
+		}
+		return this.fullArea;
+	}
+
 }
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index 08f1cb9..51d9651 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -990,6 +990,26 @@ public class StyledConverter implements OsmConverter {
 		elementSetup(shape, gt, way);
 		shape.setPoints(way.getPoints());
 
+		long areaVal = 0;
+		String tagStringVal = way.getTag(drawLevelTagKey);
+		if (tagStringVal != null) {
+			try {
+				areaVal = Integer.parseInt(tagStringVal);
+				if (areaVal < 1 || areaVal > 100) {
+					log.error("mkgmap:drawLevel must be in range 1..100, not", areaVal);
+					areaVal = 0;
+				} else if (areaVal <= 50)
+					areaVal = Long.MAX_VALUE - areaVal; // 1 => MAX_VALUE-1, 50 => MAX_VALUE-50
+				else
+					areaVal = 101 - areaVal; // 51 => 50, 100 => 1
+			} catch (NumberFormatException e) {
+				log.error("mkgmap:drawLevel invalid integer:", tagStringVal);
+			}
+		}
+		if (areaVal == 0)
+			areaVal = way.getFullArea();
+		shape.setFullArea(areaVal);
+
 		clipper.clipShape(shape, collector);
 	}
 
@@ -1070,6 +1090,7 @@ public class StyledConverter implements OsmConverter {
 	};
 	private static final short highResOnlyTagKey = TagDict.getInstance().xlate("mkgmap:highest-resolution-only");
 	private static final short skipSizeFilterTagKey = TagDict.getInstance().xlate("mkgmap:skipSizeFilter");
+	private static final short drawLevelTagKey = TagDict.getInstance().xlate("mkgmap:drawLevel");
 
 	private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country");
 	private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region");
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java b/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
index 03afc2f..b3083a2 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/TypeReader.java
@@ -102,8 +102,10 @@ public class TypeReader {
 		if (performChecks){
 			boolean fromOverlays = false;
 			List<Integer> usedTypes = null;
-			if (gt.getMaxResolution() < levels[0].getBits()){
+			if (gt.getMaxResolution() < levels[0].getBits() || gt.getMaxResolution() > 24){
 				System.out.println("Warning: Object with max resolution of " + gt.getMaxResolution() + " is ignored. Check levels option and style file "+ ts.getFileName() + ", line " + ts.getLinenumber());
+			} else if (gt.getMinResolution() > 24) {
+				System.out.println("Warning: Object with min resolution of " + gt.getMinResolution() + " is ignored. Check levels option and style file "+ ts.getFileName() + ", line " + ts.getLinenumber());
 			}
 			if (overlays != null && kind == FeatureKind.POLYLINE){
 				usedTypes = overlays.get(gt.getType());
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java b/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
index 5fdef4f..3b4ea0c 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
@@ -4,17 +4,28 @@ import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 import java.util.Locale;
 
+import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.mkgmap.reader.osm.Element;
 import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 
 /**
  * Calculates the area size of a polygon in garmin units ^ 2.
+ *
+ * if orderByDecreasingArea then the area of the polygon has already been
+ * calculated and we use it here.
+ * To be totally consistent, ie no possible difference to mkgmap behaviour when
+ * --order-by-decreasing-area is not set, this flag should be set from the option;
+ * However it is now considered that 'fullArea' is what the user might expect, rather
+ * than various different values for the same original because of clipping and cutting to
+ * expose holes.
+ *
  * @author WanMil
  */
 public class AreaSizeFunction extends CachedFunction {
 
 	private final DecimalFormat nf = new DecimalFormat("0.0#####################", DecimalFormatSymbols.getInstance(Locale.US));
+	private final boolean orderByDecreasingArea = true;
 
 	public AreaSizeFunction() {
 		super(null);
@@ -27,7 +38,17 @@ public class AreaSizeFunction extends CachedFunction {
 			if (w.hasEqualEndPoints() == false) {
 				return "0";
 			}
-			return nf.format(MultiPolygonRelation.calcAreaSize(((Way) el).getPoints()));
+			double areaSize;
+			if (orderByDecreasingArea) {
+				long fullArea = w.getFullArea();
+				if (fullArea == Long.MAX_VALUE)
+					return "0";
+				//  convert from high prec to value in map units
+			 	areaSize = (double) fullArea / (2 * (1<<Coord.DELTA_SHIFT) * (1<<Coord.DELTA_SHIFT));
+				areaSize = Math.abs(areaSize);
+			} else
+				areaSize = MultiPolygonRelation.calcAreaSize(w.getPoints());
+			return nf.format(areaSize);
 		}
 		return null;
 	}
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
index 4d357b9..a5c7ced 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
@@ -846,6 +846,8 @@ public class MultiPolygonRelation extends Relation {
 		
 		int wi = 0;
 		for (Way w : polygons) {
+			w.setFullArea(w.getFullArea()); // trigger setting area before start cutting...
+			// do like this to disguise function with side effects
 			String role = getRole(w);
 			if ("inner".equals(role)) {
 				innerPolygons.set(wi);
@@ -1034,12 +1036,14 @@ public class MultiPolygonRelation extends Relation {
 						outmostPolygonProcessing = false;
 					}
 					
+					long fullArea = currentPolygon.polygon.getFullArea();
 					for (Way mpWay : singularOuterPolygons) {
 						// put the cut out polygons to the
 						// final way map
 						if (log.isDebugEnabled())
 							log.debug(mpWay.getId(),mpWay.toTagString());
 					
+						mpWay.setFullArea(fullArea);
 						// mark this polygons so that only polygon style rules are applied
 						mpWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_POLYGON);
 						mpWay.addTag(MP_CREATED_TAG, "true");
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
index fcbf952..ba958cd 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
@@ -103,7 +103,18 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 	private static final int MIN_LON = Utils.toMapUnit(-180.0);
 	private static final int MAX_LON = Utils.toMapUnit(180.0);
 	private final static Pattern keySplitter = Pattern.compile(Pattern.quote("_"));
-	
+
+	/**
+	 * When order-by-decreasing-area we need all bit of sea to be output consistently.
+	 * Unless _draworder changes things, having seaSize as BIG causes polygons beyond the
+	 * coastline to be shown. To hide these and have the sea show up to the high-tide
+	 * coastline, can set this to be very small instead (or use _draworder).
+	 * <p>
+	 * mkgmap:drawLevel can be used to override this value in the style - the default style has:
+	 * natural=sea { add mkgmap:skipSizeFilter=true; set mkgmap:drawLevel=2 } [0x32 resolution 10]
+	 * which is equivalent to Long.MAX_VALUE-2.
+	 */
+	private static final long seaSize = Long.MAX_VALUE-2; // sea is BIG
 
 	private static final List<Class<? extends LoadableMapDataSource>> precompSeaLoader;
 
@@ -709,6 +720,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 				saver.addWay(w);
 			}
 			for (Way w : seaWays) {
+				w.setFullArea(seaSize);
 				saver.addWay(w);
 			}
 		} else {
@@ -718,6 +730,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 			// first add the complete bounding box as sea
 			Way sea = new Way(FakeIdGenerator.makeFakeId(),bounds.toCoords());
 			sea.addTag("natural", "sea");
+			sea.setFullArea(seaSize);
 			
 			for (Way w : landWays) {
 				saver.addWay(w);
@@ -991,6 +1004,7 @@ public class SeaGenerator extends OsmReadingHooksAdaptor {
 					ne.getLongitude() + 1));
 			sea.addPoint(sea.getPoints().get(0)); // close shape
 			sea.addTag("natural", "sea");
+			sea.setFullArea(seaSize);
 
 			log.info("sea: ", sea);
 			saver.addWay(sea);
diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Way.java b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
index c78d458..c782454 100644
--- a/src/uk/me/parabola/mkgmap/reader/osm/Way.java
+++ b/src/uk/me/parabola/mkgmap/reader/osm/Way.java
@@ -24,6 +24,7 @@ import java.util.List;
 import uk.me.parabola.imgfmt.Utils;
 import uk.me.parabola.imgfmt.app.Coord;
 import uk.me.parabola.log.Logger;
+import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
 
 /**
  * Represent a OSM way in the 0.5 api.  A way consists of an ordered list of
@@ -34,6 +35,7 @@ import uk.me.parabola.log.Logger;
 public class Way extends Element {
 	private static final Logger log = Logger.getLogger(Way.class);
 	private final List<Coord> points;
+	private long fullArea = Long.MAX_VALUE; // meaning unset
 
 	// This will be set if a way is read from an OSM file and the first node is the same node as the last
 	// one in the way. This can be set to true even if there are missing nodes and so the nodes that we
@@ -63,6 +65,7 @@ public class Way extends Element {
 		dup.closedInOSM = this.closedInOSM;
 		dup.complete = this.complete;
 		dup.isViaWay = this.isViaWay;
+		dup.fullArea = this.getFullArea();
 		return dup;
 	}
 
@@ -252,4 +255,16 @@ public class Way extends Element {
 	public void setViaWay(boolean isViaWay) {
 		this.isViaWay = isViaWay;
 	}
+
+	public void setFullArea(long fullArea) {
+		this.fullArea = fullArea;
+	}
+
+	public long getFullArea() { // this is unadulterated size, +ve if clockwise
+		if (this.fullArea == Long.MAX_VALUE && points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1))) {
+			this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
+		}
+		return this.fullArea;
+	}
+
 }
diff --git a/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java b/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
index 636d52e..bbc1955 100644
--- a/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
+++ b/test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
@@ -365,7 +365,7 @@ public class ShapeMergeFilterTest {
 	}
 	
 	void testOneVariant(String testId, MapShape s1, MapShape s2, int expectedNumShapes, int expectedNumPoints){
-		ShapeMergeFilter smf = new ShapeMergeFilter(24);
+		ShapeMergeFilter smf = new ShapeMergeFilter(24, false);
 		List<MapShape> res = smf.merge(Arrays.asList(s1,s2));
 		assertTrue(testId, res != null);
 		assertEquals(testId,expectedNumShapes, res.size() );

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mkgmap.git



More information about the Pkg-grass-devel mailing list