Hi Gerd
I think the reason why the buildings look different is because, without
--order-by-decreasing-area, they get merged into a single area and then
a filter (DouglasPeucker), finding the intermediate points are within
the tolerance of a straight line, removes all but the 4 corners of the
complete block. With --order-by, they are output individually as
accurately as possible, but, because we are at/beyond at the limit of
accuracy, rounding effects become obvious.
You mentioned GPSMapEdit not showing the smaller shape on top of the
larger. Using it to look at your test case of buildings in Bremen, and
the difference between with/without the option, I think it might apply
the size rule itself - drawing smaller shapes on top of larger. The
example I found was Penny supermarket @ 53.06650,8.79501 straddling a
number of buildings. With the buildings merged, it is smaller and hence
shows. With them separate, the buildings are smaller and they show.
Does this fit with your observations? Which examples were you looking
at? www.openstreetmap.org doesn't show Penny either.
I didn't use Util.roundUp because I wanted something to round to the
closest power of 2 and it rounds up to the next power of 2. I've
improved the javaDoc text for roundPof2.
I've made the other changes and attach a patch.
Regards
Ticker
On Fri, 2016-11-11 at 11:37 +0000, Ticker Berkin wrote:
> Hi Gerd
>
> I'll check on the rounding, fix the warn>debug and add the pre-test
> to
> the splitting into areas.
>
> I've just loaded GPSMapEdit and see what you mean about the long
> lines
> of buildings - they are wavy!. I'll work out why it is happening.
>
> Regards
> Ticker
>
> On Fri, 2016-11-11 at 01:25 -0700, Gerd Petermann wrote:
> > Hi Ticker,
> >
> > sorry, still some problems:
> > 1) I think I found an error in method Area.roundPof2(int val, int
> > shift)
> > I wanted to find out why you did not use Util.roundUp(int val, int
> > shift).
> >
> > For val=-9567 and shift=4 I see
> > -9568 from your routine and
> > -9552 from the other
> > So it seems your routine doesn't round up as the javadoc says.
> > So either the doc or the result is wrong.
> >
> > 2) Please check the new log messages. The severity level warning
> > should not
> > be used for debug messages.
> > A warning message should be understandable for the end user.
> >
> > 3) Reg. performance:
> > I think most of the area.intersect calls are not needed when you
> > add
> > this
> > block at the beginning of
> > splitIntoAreas():
> > // quick check if bbox of shape lies fully inside one
> > of the areas
> > for (int areaIndex = 0; areaIndex < areas.length;
> > ++areaIndex) {
> > if
> > (areas[areaIndex].getBounds().contains(e.getBounds())) {
> > used[areaIndex] = true;
> > areas[areaIndex].addShape(e);
> > return;
> > }
> > }
> > // Shape crosses area(s), we have to split it
> > // Convert to a awt area
> > ...
> >
> > ciao,
> > Gerd
> >
> >
> >
> >
> >
> > --
> > View this message in context:
> > http://gis.19327.n8.nabble.com/patch-to
> > -write-polygons-in-decreasing-order-tp5884038p5885742.html
> > Sent from the Mkgmap Development mailing list archive at
> > Nabble.com.
> > _______________________________________________
> > mkgmap-dev mailing list
> > [email protected]
> > http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev
> _______________________________________________
> mkgmap-dev mailing list
> [email protected]
> http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-devIndex: doc/options.txt
===================================================================
--- doc/options.txt (revision 3701)
+++ doc/options.txt (working copy)
@@ -705,3 +705,8 @@
<p>
;--verbose
: Makes some operations more verbose. Mostly used with --list-styles.
+<p>
+;--order-by-decreasing-area
+: Puts area/polygons into the mapfile in decreasing size so
+that smaller features are rendered over larger ones
+(assuming _drawOrder is equal).
Index: resources/help/en/options
===================================================================
--- resources/help/en/options (revision 3701)
+++ resources/help/en/options (working copy)
@@ -699,3 +699,8 @@
--verbose
Makes some operations more verbose. Mostly used with --list-styles.
+
+--order-by-decreasing-area
+ Puts polygons/areas into the mapfile in decreasing size so that
+ smaller features are rendered over larger ones (assuming _drawOrder
+ is equal)
Index: src/uk/me/parabola/imgfmt/app/Area.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/Area.java (revision 3701)
+++ src/uk/me/parabola/imgfmt/app/Area.java (working copy)
@@ -19,6 +19,7 @@
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,6 +105,19 @@
}
/**
+ * 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 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
@@ -110,37 +124,53 @@
* 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");
}
/**
Index: src/uk/me/parabola/mkgmap/build/MapArea.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapArea.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/build/MapArea.java (working copy)
@@ -20,6 +20,7 @@
import java.util.Arrays;
import java.util.List;
+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;
@@ -175,15 +176,49 @@
* 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 +272,10 @@
}
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 +299,7 @@
* them equally to the two areas.
*/
useNormalSplit = false;
+ log.warn("useNormalSplit false");
continue;
}
@@ -610,6 +650,46 @@
}
/**
+ * Spit the polygon into areas
+ *
+ * Using .intersect() here is expensive. The code should be changed to
+ * use a simple rectangle clipping altorithm 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();
+ 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
+ java.awt.geom.Area area = Java2DConverter.createArea(e.getPoints());
+
+ 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) {
+ used[areaIndex] = true;
+ MapShape s = e.copy();
+ s.setPoints(subShape);
+ areas[areaIndex].addShape(s);
+ }
+ }
+ }
+
+
+ /**
* @return true if this area contains any data
*/
public boolean hasData() {
Index: src/uk/me/parabola/mkgmap/build/MapBuilder.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapBuilder.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/build/MapBuilder.java (working copy)
@@ -21,12 +21,16 @@
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 @@
private String licenseFileName;
+ private boolean orderByDecreasingArea;
+
public MapBuilder() {
regionName = null;
locationAutofill = Collections.emptySet();
@@ -188,6 +194,7 @@
driveOnLeft = true;
if ("right".equals(driveOn))
driveOnLeft = false;
+ orderByDecreasingArea = props.getProperty("order-by-decreasing-area", false);
}
/**
@@ -685,7 +692,7 @@
for (SourceSubdiv srcDivPair : srcList) {
MapSplitter splitter = new MapSplitter(srcDivPair.getSource(), zoom);
- MapArea[] areas = splitter.split();
+ MapArea[] areas = splitter.split(orderByDecreasingArea);
log.info("Map region", srcDivPair.getSource().getBounds(), "split into", areas.length, "areas at resolution", zoom.getResolution());
for (MapArea area : areas) {
@@ -712,16 +719,16 @@
* @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,23 @@
config.setRoutable(doRoads);
if (mergeShapes){
- ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res);
+ if (orderByDecreasingArea) // splitIntoAreas destroyed the shared coord, so redo
+ prepShapesForMerge(shapes);
+
+ 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);
Index: src/uk/me/parabola/mkgmap/build/MapSplitter.java
===================================================================
--- src/uk/me/parabola/mkgmap/build/MapSplitter.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/build/MapSplitter.java (working copy)
@@ -91,20 +91,21 @@
*
* This routine is not called recursively.
*
+ * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
* @return An array of map areas, each of which is within the size limit
* and the limit on the number of features.
*/
- public MapArea[] split() {
+ public MapArea[] split(boolean orderByDecreasingArea) {
log.debug("orig area", mapSource.getBounds());
MapArea ma = initialArea(mapSource);
- MapArea[] areas = splitMaxSize(ma);
+ MapArea[] areas = splitMaxSize(ma, orderByDecreasingArea);
// Now step through each area and see if any have too many map features
// in them. For those that do, we further split them. This is done
// recursively until everything fits.
List<MapArea> alist = new ArrayList<>();
- addAreasToList(areas, alist, 0);
+ addAreasToList(areas, alist, 0, orderByDecreasingArea);
MapArea[] results = new MapArea[alist.size()];
return alist.toArray(results);
@@ -118,8 +119,9 @@
* @param areas The areas to add to the list (and possibly split up).
* @param alist The list that will finally contain the complete list of
* map areas.
+ * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
*/
- private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth) {
+ private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth, boolean orderByDecreasingArea) {
int res = zoom.getResolution();
for (MapArea area : areas) {
Area bounds = area.getBounds();
@@ -164,11 +166,16 @@
log.debug("splitting area", area);
MapArea[] sublist;
if(bounds.getWidth() > bounds.getHeight())
- sublist = area.split(2, 1, res, bounds);
+ sublist = area.split(2, 1, res, bounds, orderByDecreasingArea);
else
- sublist = area.split(1, 2, res, bounds);
- addAreasToList(sublist, alist, depth + 1);
- continue;
+ sublist = area.split(1, 2, res, bounds, orderByDecreasingArea);
+ if (sublist == null) {
+ log.warn("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 @@
* If the area is already small enough then it will be returned unchanged.
*
* @param mapArea The area that needs to be split down.
+ * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
* @return An array of map areas. Each will be below the max size.
*/
- private MapArea[] splitMaxSize(MapArea mapArea) {
+ private MapArea[] splitMaxSize(MapArea mapArea, boolean orderByDecreasingArea) {
Area bounds = mapArea.getFullBounds();
int shift = zoom.getShiftValue();
@@ -214,7 +222,7 @@
if (height > MAX_DIVISION_SIZE)
ysplit = height / MAX_DIVISION_SIZE + 1;
- return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds);
+ return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds, orderByDecreasingArea);
}
/**
Index: src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java
===================================================================
--- src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/filters/ShapeMergeFilter.java (working copy)
@@ -41,9 +41,11 @@
private static final Logger log = Logger.getLogger(ShapeMergeFilter.class);
private final int resolution;
private final ShapeHelper dupShape = new ShapeHelper(new ArrayList<Coord>(0));
+ private final boolean orderByDecreasingArea;
- public ShapeMergeFilter(int resolution) {
+ public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) {
this.resolution = resolution;
+ this.orderByDecreasingArea = orderByDecreasingArea;
}
public List<MapShape> merge(List<MapShape> shapes) {
@@ -84,6 +86,9 @@
for (Map<MapShape, List<ShapeHelper>> lowMap : sameTypeList){
boolean added = false;
for (MapShape ms: lowMap.keySet()){
+ if (orderByDecreasingArea && ms.getFullArea() != shape.getFullArea())
+ // must not merge areas unless derived from same thing
+ continue;
// we do not use isSimilar() here, as it compares minRes and maxRes as well
String s1 = ms.getName();
String s2 = shape.getName();
Index: src/uk/me/parabola/mkgmap/general/MapShape.java
===================================================================
--- src/uk/me/parabola/mkgmap/general/MapShape.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/general/MapShape.java (working copy)
@@ -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 @@
*/
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 @@
MapShape(MapShape s) {
super(s);
this.osmid = s.osmid;
+ this.fullArea = s.getFullArea();
}
public MapShape copy() {
@@ -50,4 +55,18 @@
public long getOsmid() {
return osmid;
}
+
+ public void setFullArea(long fullArea) {
+ this.fullArea = fullArea;
+ }
+
+ public long getFullArea() { // this is unadulterated size, +ve if clockwise
+ if (this.fullArea == Long.MAX_VALUE) {
+ java.util.List<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;
+ }
+
}
Index: src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java (working copy)
@@ -989,6 +989,7 @@
final MapShape shape = new MapShape(way.getId());
elementSetup(shape, gt, way);
shape.setPoints(way.getPoints());
+ shape.setFullArea(way.getFullArea());
clipper.clipShape(shape, collector);
}
Index: src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/osmstyle/function/AreaSizeFunction.java (working copy)
@@ -4,6 +4,7 @@
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;
@@ -10,11 +11,21 @@
/**
* 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 @@
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;
}
Index: src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/reader/osm/MultiPolygonRelation.java (working copy)
@@ -846,6 +846,8 @@
int wi = 0;
for (Way w : polygons) {
+ w.setFullArea(w.getFullArea()); // trigger setting area before start cutting...
+ // do like this to disguise function with side effects
String role = getRole(w);
if ("inner".equals(role)) {
innerPolygons.set(wi);
@@ -1034,6 +1036,7 @@
outmostPolygonProcessing = false;
}
+ long fullArea = currentPolygon.polygon.getFullArea();
for (Way mpWay : singularOuterPolygons) {
// put the cut out polygons to the
// final way map
@@ -1040,6 +1043,7 @@
if (log.isDebugEnabled())
log.debug(mpWay.getId(),mpWay.toTagString());
+ mpWay.setFullArea(fullArea);
// mark this polygons so that only polygon style rules are applied
mpWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_POLYGON);
mpWay.addTag(MP_CREATED_TAG, "true");
Index: src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/reader/osm/SeaGenerator.java (working copy)
@@ -103,8 +103,19 @@
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>
+ * Maybe this could be a mkgmap:variable specified in the style something like:
+ * natural=sea {add mkgmap:skipSizeFilter=true;set mkgmap:drawLevel=6} [0x32 resolution 10]
+ * and mkgmap:drawLevel is suitably scaled and used as the fullArea.
+ */
+ private static final long seaSize = Long.MAX_VALUE-2; // sea is BIG
+
private static final List<Class<? extends LoadableMapDataSource>> precompSeaLoader;
static {
@@ -709,6 +720,7 @@
saver.addWay(w);
}
for (Way w : seaWays) {
+ w.setFullArea(seaSize);
saver.addWay(w);
}
} else {
@@ -718,6 +730,7 @@
// first add the complete bounding box as sea
Way sea = new Way(FakeIdGenerator.makeFakeId(),bounds.toCoords());
sea.addTag("natural", "sea");
+ sea.setFullArea(seaSize);
for (Way w : landWays) {
saver.addWay(w);
@@ -991,6 +1004,7 @@
ne.getLongitude() + 1));
sea.addPoint(sea.getPoints().get(0)); // close shape
sea.addTag("natural", "sea");
+ sea.setFullArea(seaSize);
log.info("sea: ", sea);
saver.addWay(sea);
Index: src/uk/me/parabola/mkgmap/reader/osm/Way.java
===================================================================
--- src/uk/me/parabola/mkgmap/reader/osm/Way.java (revision 3701)
+++ src/uk/me/parabola/mkgmap/reader/osm/Way.java (working copy)
@@ -24,6 +24,7 @@
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 @@
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 @@
dup.closedInOSM = this.closedInOSM;
dup.complete = this.complete;
dup.isViaWay = this.isViaWay;
+ dup.fullArea = this.getFullArea();
return dup;
}
@@ -252,4 +255,16 @@
public void setViaWay(boolean isViaWay) {
this.isViaWay = isViaWay;
}
+
+ public void setFullArea(long fullArea) {
+ this.fullArea = fullArea;
+ }
+
+ public long getFullArea() { // this is unadulterated size, +ve if clockwise
+ if (this.fullArea == Long.MAX_VALUE && points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1))) {
+ this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
+ }
+ return this.fullArea;
+ }
+
}
Index: test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java
===================================================================
--- test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java (revision 3701)
+++ test/uk/me/parabola/mkgmap/filters/ShapeMergeFilterTest.java (working copy)
@@ -365,7 +365,7 @@
}
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() );
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev