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 <sebas...@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

_______________________________________________
Pkg-grass-devel mailing list
Pkg-grass-devel@lists.alioth.debian.org
http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel

Reply via email to