Hi Gerd

I've fixed it and the test case now runs. I've also incorporated your 3
other points.

I've added the following comment to mkgmap/build/MapArea.java

/*
Failed to split!

This happens easily when doing low resolution overview
levels (have a high shift value) and we are insisting that areas
can only be split on boundaries that can be represented exactly
with the relevant shift for the level.  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.  In the end it tries to split a single pixel because
it contains, say, 100s of bits of Sea and Coastline with complex
shapes which it thinks is too much data for a subDivision, but
actually will mostly disappear when we come to write it.  And it
will look meaningless - 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,
filtering and splitting, subDivision splitting etc needs a
re-think and re-organisation.
*/

I could try and explain this more fully in another thread and maybe it
should go on the enhancement list

Regards
Ticker


On Sat, 2016-10-22 at 14:00 +0100, Ticker Berkin wrote:
> Hi Gerd
> 
> I've reproduced the crash.
> 
> I think the problem is that area.getEstimatedSizes() (called from
> MapSplitter.addAreasToList) doesn't reflects what will go into the
> area
> when the option is in effect and so addAreasToList might keep
> recursing.
> 
> I need to think about this more deeply. There is a related issue
> where
> complex shapes are split earlier when there probably is no need
> because
> they will be split later to scattered into lots of subDivisions.
> 
> Concerning the other problems:
> 
> It wasn't dead code - rather a function with side effects - I'll re
> -code it.
> 
> Sorry about Long vs long.
> 
> I notice ShapeMergeFilterTest in the patch. I experimented a bit with
> mergeShapes and was surprised it wasn't doing a bit of merging when
> my
> option was in effect - I was expecting it join bits of the complex
> shapes mentioned earlier.
> 
> I'm away on holiday next week. I should be able to send you something
> in a couple of weeks time.
> 
> Regards
> Ticker
> 
> 
> On Sat, 2016-10-22 at 01:04 -0700, Gerd Petermann wrote:
> > Hi Ticker,
> > 
> > I reviewed  the patch, it looked good but the patched version
> > crashes
> > in a
> > recursion because of an 
> > java.lang.StackOverflowError. I think the problem is in the
> > rounding
> > in Area
> > class.
> > I've uploaded test data that should allow you to reproduce the
> > problem:
> > http://files.mkgmap.org.uk/download/310/test.zip
> > 
> > Besides that I found two small problems:
> > 1) The unit tests did not compile with the patch
> > 2)  MultPpolygonRelation.java contains dead (?) code and uses Long
> > instead
> > of long.
> > Please check my changes in the attached modified patch:
> > 
> > sortAreas_v2.patch
> > <http://gis.19327.n8.nabble.com/file/n5884759/sortAreas_v2.patch>  
> > ;;
> > 
> > Ciao,
> > Gerd
> > 
> > 
> > Ticker Berkin wrote
> > > Hi
> > > 
> > > I have a patch that outputs polygons into the map file in
> > > decreasing
> > > size order, so that, assuming equal _drawOrder, the Garmin device
> > > renders small details over larger areas, much like how they
> > > appear
> > > on
> > > OpenStreetMap.org.
> > > 
> > > As well   as sorting by size, polygons that straddle
> > > subDivisions      
> > > are cut up and placed into their correct subdivision, rather than
> > > being
> > > put into an arbirary one, which, if it isn't the one in focus,
> > > renders
> > > over the focus subdivision.
> > > 
> > > The original, full, area of polygons is tracked   through 
> > > a
> > > ny cutting and clipping so that relative ordering is correct
> > > across
> > > subdivision and map boundaries.
> > > 
> > > All this is controlled by the option --order-by-decreasing-area,
> > > with a
> > > default of false that leaves the behavior of mkgmap unchanged.    
> > > A
> > > s such, I think this patch could be applied to the trunk
> > > development.
> > > 
> > > Regards
> > > Ticker Berkin
> > > mk
> > > 
> > > _______________________________________________
> > > mkgmap-dev mailing list
> > 
> > > mkgmap-dev@.org
> > 
> > > http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev
> > > 
> > > sortAreas.patch (25K)
> > > &lt;
> > > http://gis.19327.n8.nabble.com/attachment/5884038/0/sortAreas.p
> > > atch&gt;
> > 
> > 
> > 
> > 
> > 
> > --
> > View this message in context: 
> > http://gis.19327.n8.nabble.com/patch-to
> > -write-polygons-in-decreasing-order-tp5884038p5884759.html
> > Sent from the Mkgmap Development mailing list archive at
> > Nabble.com.
> > _______________________________________________
> > mkgmap-dev mailing list
> > mkgmap-dev@lists.mkgmap.org.uk
> > http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev
> _______________________________________________
> mkgmap-dev mailing list
> mkgmap-dev@lists.mkgmap.org.uk
> http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev
Index: doc/options.txt
===================================================================
--- doc/options.txt	(revision 3701)
+++ doc/options.txt	(working copy)
@@ -705,3 +705,8 @@
 <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).
Index: resources/help/en/options
===================================================================
--- resources/help/en/options	(revision 3701)
+++ resources/help/en/options	(working copy)
@@ -699,3 +699,8 @@
 
 --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)
Index: src/uk/me/parabola/imgfmt/app/Area.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/Area.java	(revision 3701)
+++ src/uk/me/parabola/imgfmt/app/Area.java	(working copy)
@@ -103,6 +103,19 @@
 				;
 	}
 
+    	/**
+	 * Round integer to a power of 2
+	 *
+	 * @param val The number of be rounded.
+	 * @param shift The power of 2.
+	 * @return The rounded number (rounds up).
+	 */
+	private 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.
 	 *
@@ -110,37 +123,62 @@
 	 * 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 area containing xsplit*ysplit areas (hopefully).
 	 */
-	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 { // (nAreas < areas.length) // didn't quite split as expected because of initial boundaries
+/*
+			Area[] fewerAreas = new Area[nAreas];
+			for (int i = 0; i < nAreas; ++i)
+				fewerAreas[i] = areas[i];
+			return fewerAreas;
+I don't think this can happen, ie split an area larger tham max into bits then
+have problems with aligned boundaries. Also, will cause problems for the current
+MapArea sprinkle logic if don't return enought pieces, so:
+*/
+			throw new uk.me.parabola.imgfmt.MapFailedException("Area split shift align problems");
+		}
 	}
 
 	/**
Index: src/uk/me/parabola/mkgmap/build/MapArea.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapArea.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/build/MapArea.java	(working copy)
@@ -180,10 +180,41 @@
 	 * @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.  
+	 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.  
 	 * @return An array of the new MapArea's.
 	 */
-	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!
+ * This happens easily when doing low resolution overview levels (have a high
+ * shift value) and we are insisting that areas can only be split on boundaries that can be
+ * represented exactly with the relevant shift for the level.
+ * 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.
+ * In the end it tries to split a single pixel because it contains, say, 100s
+ * of bits of Sea and Coastline with complex shapes which it thinks is too much data
+ * for a subDivision, but actually will mostly disappear when we come to write it.
+ * And it will look meaningless - 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, simplificatin, filtering and splitting,
+ * subDivision splitting etc needs a re-think and re-organisation.
+ */
+			for (MapLine e : this.lines)
+				if (e.getMinResolution() <= areaResolution)
+			    		log.warn("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.warn("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()));
+		    	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 +268,10 @@
 			}
 
 			for (MapShape e : this.shapes) {
+				if (orderByDecreasingArea) { // need to treat shapes consistently, regards 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 +295,7 @@
 				 * them equally to the two areas.  
 				 */
 				useNormalSplit = false;
+				log.warn("useNormalSplit false");
 				continue;
 			} 
 			
@@ -610,6 +646,32 @@
 	}
 
 	/**
+	 * Spit the polygon into areas
+	 *
+	 * @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)
+	{
+		// Convert to a awt area
+		java.awt.geom.Area area = uk.me.parabola.util.Java2DConverter.createArea(e.getPoints());
+
+		for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
+			java.awt.geom.Area clipper = uk.me.parabola.util.Java2DConverter.createBoundsArea(areas[areaIndex].getBounds());
+			clipper.intersect(area);
+			List<List<Coord>> subShapePoints = uk.me.parabola.util.Java2DConverter.areaToShapes(clipper);
+			for (List<Coord> subShape : subShapePoints) {
+			    used[areaIndex] = true;
+			    MapShape s = e.copy();
+			    s.setPoints(subShape);
+			    areas[areaIndex].addShape(s);
+			}
+		}
+	}
+
+
+	/**
 	 * @return true if this area contains any data
 	 */
 	public boolean hasData() {
Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapBuilder.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/build/MapBuilder.java	(working copy)
@@ -147,6 +147,8 @@
 
 	private String licenseFileName;
 
+	private boolean orderByDecreasingArea;
+
 	public MapBuilder() {
 		regionName = null;
 		locationAutofill = Collections.emptySet();
@@ -188,6 +190,7 @@
 			driveOnLeft = true;
 		if ("right".equals(driveOn))
 			driveOnLeft = false;
+		orderByDecreasingArea = props.getProperty("order-by-decreasing-area", false);
 	}
 
 	/**
@@ -685,7 +688,7 @@
 			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) {
@@ -1115,11 +1118,20 @@
 		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 java.util.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);
Index: src/uk/me/parabola/mkgmap/build/MapSplitter.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapSplitter.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/build/MapSplitter.java	(working copy)
@@ -91,20 +91,21 @@
 	 *
 	 * 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 @@
 	 * @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 @@
 						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("Area single pixel shifted 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 @@
 	 * 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 @@
 		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);
 	}
 
 	/**
Index: src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java	(working copy)
@@ -41,9 +41,11 @@
 	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 @@
 			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();
Index: src/uk/me/parabola/mkgmap/general/MapShape.java
===================================================================
--- src/uk/me/parabola/mkgmap/general/MapShape.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/general/MapShape.java	(working copy)
@@ -23,6 +23,7 @@
  */
 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 +33,7 @@
 	MapShape(MapShape s) {
 		super(s);
 		this.osmid = s.osmid;
+		this.fullArea = s.getFullArea();
 	}
 
 	public MapShape copy() {
@@ -50,4 +52,18 @@
 	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<uk.me.parabola.imgfmt.app.Coord> points = this.getPoints();
+			if (points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1)))
+				this.fullArea = uk.me.parabola.mkgmap.filters.ShapeMergeFilter.calcAreaSizeTestVal(points);
+		}
+		return this.fullArea;
+	}
+
 }
Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java	(working copy)
@@ -989,6 +989,7 @@
 		final MapShape shape = new MapShape(way.getId());
 		elementSetup(shape, gt, way);
 		shape.setPoints(way.getPoints());
+		shape.setFullArea(way.getFullArea());
 
 		clipper.clipShape(shape, collector);
 	}
Index: src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java	(working copy)
@@ -15,6 +15,10 @@
 public class AreaSizeFunction extends CachedFunction {
 
 	private final DecimalFormat nf = new DecimalFormat("0.0#####################", DecimalFormatSymbols.getInstance(Locale.US));
+	private final boolean orderByDecreasingArea = true;
+	// To be totally consistent, ie no possible difference to mkgmap behaviour when --order-by-decreasing-area is not set, 
+	// this should be set from the option; however I believe 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.     
 
 	public AreaSizeFunction() {
 		super(null);
@@ -27,7 +31,18 @@
 			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<<uk.me.parabola.imgfmt.app.Coord.DELTA_SHIFT) *
+							           (1<<uk.me.parabola.imgfmt.app.Coord.DELTA_SHIFT));
+				areaSize = Math.abs(areaSize);
+			} else
+				areaSize = MultiPolygonRelation.calcAreaSize(w.getPoints());
+			return nf.format(areaSize);
 		}
 		return null;
 	}
Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java	(working copy)
@@ -846,6 +846,8 @@
 		
 		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,6 +1036,7 @@
 						outmostPolygonProcessing = false;
 					}
 					
+					long fullArea = currentPolygon.polygon.getFullArea();
 					for (Way mpWay : singularOuterPolygons) {
 						// put the cut out polygons to the
 						// final way map
@@ -1040,6 +1043,7 @@
 						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");
Index: src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java	(working copy)
@@ -103,8 +103,17 @@
 	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 sea 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).
+	 * Maybe this could be a mkgmap:variable specified in the style where default has:
+natural=sea { add mkgmap:skipSizeFilter=true } [0x32 resolution 10]
+	 */
+	private static final long seaSize = Long.MAX_VALUE-2; // sea is BIG
+
 	private static final List<Class<? extends LoadableMapDataSource>> precompSeaLoader;
 
 	static {
@@ -709,6 +718,7 @@
 				saver.addWay(w);
 			}
 			for (Way w : seaWays) {
+	    			w.setFullArea(seaSize);
 				saver.addWay(w);
 			}
 		} else {
@@ -718,6 +728,7 @@
 			// 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 +1002,7 @@
 					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);
Index: src/uk/me/parabola/mkgmap/reader/osm/Way.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Way.java	(revision 3701)
+++ src/uk/me/parabola/mkgmap/reader/osm/Way.java	(working copy)
@@ -34,6 +34,7 @@
 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 +64,7 @@
 		dup.closedInOSM = this.closedInOSM;
 		dup.complete = this.complete;
 		dup.isViaWay = this.isViaWay;
+		dup.fullArea = this.getFullArea();
 		return dup;
 	}
 
@@ -252,4 +254,16 @@
 	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 = uk.me.parabola.mkgmap.filters.ShapeMergeFilter.calcAreaSizeTestVal(points);
+		}
+		return this.fullArea;
+	}
+
 }
_______________________________________________
mkgmap-dev mailing list
mkgmap-dev@lists.mkgmap.org.uk
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to