[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