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