Codebase list mkgmap / ecddb91
Merge tag 'upstream/0.0.0+svn3706' Upstream version 0.0.0+svn3706 Bas Couwenberg 7 years ago
19 changed file(s) with 364 addition(s) and 56 deletion(s). Raw diff Collapse all Expand all
704704 <p>
705705 ;--verbose
706706 : Makes some operations more verbose. Mostly used with --list-styles.
707 <p>
708 ;--order-by-decreasing-area
709 : Puts area/polygons into the mapfile in decreasing size so
710 that smaller features are rendered over larger ones
711 (assuming _drawOrder is equal).
712 The tag mkgmap:drawLevel can be used to override the
713 natural area of a polygon, so forcing changes to the rendering order.
714
132132 | +mkgmap:highest-resolution-only+ | If set to +true+ the object will only be added for the highest resolution configured in the element type definition.
133133 | +mkgmap:execute_finalize_rules+ | If set to +true+ mkgmap will execute the finalize rules even if no object is created fot the element.
134134 | +mkgmap:numbers+ | If set to +false+ for a node or way mkgmap will ignore the object in the calculations for the --housenumber option
135 | +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.
135136 |=========================================================
136
137
698698
699699 --verbose
700700 Makes some operations more verbose. Mostly used with --list-styles.
701
702 --order-by-decreasing-area
703 Puts polygons/areas into the mapfile in decreasing size so that
704 smaller features are rendered over larger ones (assuming _drawOrder
705 is equal). The tag mkgmap:drawLevel can be used to override the
706 natural area of a polygon, so forcing changes to the rendering order.
0 svn.version: 3701
1 build.timestamp: 2016-10-30T22:57:00+0000
0 svn.version: 3706
1 build.timestamp: 2016-11-28T13:14:14+0000
77 natural=wetland [0x51 resolution 20]
88 natural=water [0x3c resolution 18]
99 natural=waterfall | waterway=waterfall [0x47 resolution 21]
10 natural=sea { add mkgmap:skipSizeFilter=true } [0x32 resolution 10]
10 natural=sea { add mkgmap:skipSizeFilter=true; set mkgmap:drawLevel=2 } [0x32 resolution 10]
1111
1212 waterway=riverbank [0x46 resolution 20]
1818 import java.util.ArrayList;
1919 import java.util.List;
2020
21 import uk.me.parabola.imgfmt.MapFailedException;
2122 import uk.me.parabola.imgfmt.Utils;
2223 import uk.me.parabola.log.Logger;
2324
103104 }
104105
105106 /**
107 * Round integer to nearest power of 2.
108 *
109 * @param val The number of be rounded.
110 * @param shift The power of 2.
111 * @return The rounded number (binary half rounds up).
112 */
113 private static int roundPof2(int val, int shift) {
114 if (shift <= 0)
115 return val;
116 return (((val >> (shift-1)) + 1) >> 1) << shift;
117 }
118
119 /**
106120 * Split this area up into a number of smaller areas.
107121 *
108122 * @param xsplit The number of pieces to split this area into in the x
109123 * direction.
110124 * @param ysplit The number of pieces to split this area into in the y
111125 * direction.
112 * @return An area containing xsplit*ysplit areas.
113 */
114 public Area[] split(int xsplit, int ysplit) {
126 * @param resolutionShift round to this power of 2.
127 * @return An array containing xsplit*ysplit areas or null if can't split in half.
128 * @throws MapFailedException if more complex split operation couldn't be honoured.
129 */
130 public Area[] split(int xsplit, int ysplit, int resolutionShift) {
115131 Area[] areas = new Area[xsplit * ysplit];
116132
117
118 int xsize = getWidth() / xsplit;
119 int ysize = getHeight() / ysplit;
120
121 int xextra = getWidth() - xsize * xsplit;
122 int yextra = getHeight() - ysize * ysplit;
123
133 int xstart;
134 int xend;
135 int ystart;
136 int yend;
137 int nAreas = 0;
138
139 xstart = minLong;
124140 for (int x = 0; x < xsplit; x++) {
125 int xstart = minLong + x * xsize;
126 int xend = xstart + xsize;
127141 if (x == xsplit - 1)
128 xend += xextra;
129
142 xend = maxLong;
143 else
144 xend = roundPof2(xstart + (maxLong - xstart) / (xsplit - x),
145 resolutionShift);
146 ystart = minLat;
130147 for (int y = 0; y < ysplit; y++) {
131 int ystart = minLat + y * ysize;
132 int yend = ystart + ysize;
133148 if (y == ysplit - 1)
134 yend += yextra;
135 Area a = new Area(ystart, xstart, yend, xend);
136 log.debug(x, y, a);
137 areas[x * ysplit + y] = a;
149 yend = maxLat;
150 else
151 yend = roundPof2(ystart + (maxLat - ystart) / (ysplit - y),
152 resolutionShift);
153 if (xstart < xend && ystart < yend) {
154 Area a = new Area(ystart, xstart, yend, xend);
155 // log.debug(x, y, a);
156 log.debug("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "to", ystart, xstart, yend, xend);
157 areas[nAreas++] = a;
158 } else
159 log.warn("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "can't", xsplit, ysplit);
160 ystart = yend;
138161 }
162 xstart = xend;
139163 }
140164
141 assert areas.length == xsplit * ysplit;
142 return areas;
165 // assert areas.length == xsplit * ysplit;
166 if (nAreas == areas.length) // no problem
167 return areas;
168 // beware - MapSplitter.splitMaxSize requests split of 1/1 if the original area wasn't too big
169 else if (nAreas == 1) // failed to split in half
170 return null;
171 else
172 throw new MapFailedException("Area split shift align problems");
143173 }
144174
145175 /**
1919 import java.util.Arrays;
2020 import java.util.List;
2121
22 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
23
24 import uk.me.parabola.imgfmt.Utils;
25 import uk.me.parabola.util.Java2DConverter;
2226 import uk.me.parabola.imgfmt.app.Area;
2327 import uk.me.parabola.imgfmt.app.Coord;
2428 import uk.me.parabola.imgfmt.app.trergn.Overview;
8892 /** The resolution that this area is at */
8993 private final int areaResolution;
9094
95 private Long2ObjectOpenHashMap<Coord> areasHashMap;
96
9197 /**
9298 * Create a map area from the given map data source. This map
9399 * area will have the same bounds as the map data source and
174180 * Split this area into several pieces. All the map elements are reallocated
175181 * to the appropriate subarea. Usually this instance would now be thrown
176182 * away and the new sub areas used instead.
183 * <p>
184 * if orderByDecreasingArea, the split is forced onto boundaries that can
185 * be represented exactly with the relevant shift for the level.
186 * This can cause the split to fail because all the lines/shapes that need
187 * to be put at this level are here, but represented at the highest resolution
188 * without any filtering relevant to the resolution and the logic to request
189 * splitting considers this too much for a subDivision, even though it will
190 * mostly will disappear when we come to write it and look meaningless -
191 * the subDivision has been reduced to a single point at its shift level.
192 * <p>
193 * The lines/shapes should have been simplified much earlier in the process,
194 * then they could appear as such in reasonably size subDivision.
195 * The logic of levels, lines and shape placement, simplification, splitting and
196 * other filtering, subDivision splitting etc needs a re-think and re-organisation.
177197 *
178198 * @param nx The number of pieces in the x (longitude) direction.
179199 * @param ny The number of pieces in the y direction.
180200 * @param resolution The resolution of the level.
181201 * @param bounds the bounding box that is used to create the areas.
182 * @return An array of the new MapArea's.
183 */
184 public MapArea[] split(int nx, int ny, int resolution, Area bounds) {
185 Area[] areas = bounds.split(nx, ny);
202 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
203 * @return An array of the new MapArea's or null if can't split.
204 */
205 public MapArea[] split(int nx, int ny, int resolution, Area bounds, boolean orderByDecreasingArea) {
206 int resolutionShift = orderByDecreasingArea ? (24 - resolution) : 0;
207 Area[] areas = bounds.split(nx, ny, resolutionShift);
208 if (areas == null) { // Failed to split!
209 if (log.isDebugEnabled()) { // see what is here
210 for (MapLine e : this.lines)
211 if (e.getMinResolution() <= areaResolution)
212 log.debug("line. locn=", e.getPoints().get(0).toOSMURL(),
213 " type=", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()),
214 " name=", e.getName(), " min=", e.getMinResolution(), " max=", e.getMaxResolution());
215 for (MapShape e : this.shapes)
216 if (e.getMinResolution() <= areaResolution)
217 log.debug("shape. locn=", e.getPoints().get(0).toOSMURL(),
218 " type=", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()),
219 " name=", e.getName(), " min=", e.getMinResolution(), " max=", e.getMaxResolution(),
220 " full=", e.getFullArea(),
221 " calc=", uk.me.parabola.mkgmap.filters.ShapeMergeFilter.calcAreaSizeTestVal(e.getPoints()));
222 // the main culprits are lots of bits of sea and coastline in an overview map (res 12)
223 }
224 return null;
225 }
186226 MapArea[] mapAreas = new MapArea[nx * ny];
187227 log.info("Splitting area " + bounds + " into " + nx + "x" + ny + " pieces at resolution " + resolution);
188228 boolean useNormalSplit = true;
236276 }
237277
238278 for (MapShape e : this.shapes) {
279 if (orderByDecreasingArea) { // need to treat shapes consistently, regardless of useNormalSplit
280 splitIntoAreas(mapAreas, e, used);
281 continue;
282 }
239283 if (useNormalSplit){
240284 areaIndex = pickArea(mapAreas, e, xbase30, ybase30, nx, ny, dx30, dy30);
241285 if (e.getBounds().getHeight() > maxHeight || e.getBounds().getWidth() > maxWidth){
259303 * them equally to the two areas.
260304 */
261305 useNormalSplit = false;
306 log.warn("useNormalSplit false");
262307 continue;
263308 }
264309
609654 }
610655
611656 /**
657 * Spit the polygon into areas
658 *
659 * Using .intersect() here is expensive. The code should be changed to
660 * use a simple rectangle clipping algorithm as in, say,
661 * util/ShapeSplitter.java
662 *
663 * @param areas The available areas to choose from.
664 * @param e The map element.
665 * @param used flag vector to say area has been added to.
666 */
667 private void splitIntoAreas(MapArea[] areas, MapShape e, boolean[] used)
668 {
669 // quick check if bbox of shape lies fully inside one of the areas
670 Area shapeBounds = e.getBounds();
671
672 // this is worked out at standard precision, along with Area.contains() and so can get
673 // tricky problems as it might not really be fully within the area.
674 // so: pretend the shape is a touch bigger. Will get the optimisation most of the time
675 // and in the boundary cases will fall into the precise code.
676 shapeBounds = new Area(shapeBounds.getMinLat()-2,
677 shapeBounds.getMinLong()-2,
678 shapeBounds.getMaxLat()+2,
679 shapeBounds.getMaxLong()+2);
680 for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
681 if (areas[areaIndex].getBounds().contains(shapeBounds)) {
682 used[areaIndex] = true;
683 areas[areaIndex].addShape(e);
684 return;
685 }
686 }
687 // Shape crosses area(s), we have to split it
688
689 // Convert to a awt area
690 List<Coord> coords = e.getPoints();
691 java.awt.geom.Area area = Java2DConverter.createArea(coords);
692 // remember actual coord, so can re-use
693 int origSize = coords.size();
694 Long2ObjectOpenHashMap<Coord> shapeHashMap = new Long2ObjectOpenHashMap<>(origSize);
695 for (int i = 0; i < origSize; ++i) {
696 Coord co = coords.get(i);
697 shapeHashMap.put(Utils.coord2Long(co), co);
698 }
699 if (areasHashMap == null)
700 areasHashMap = new Long2ObjectOpenHashMap<>();
701
702 for (int areaIndex = 0; areaIndex < areas.length; ++areaIndex) {
703 java.awt.geom.Area clipper = Java2DConverter.createBoundsArea(areas[areaIndex].getBounds());
704 clipper.intersect(area);
705 List<List<Coord>> subShapePoints = Java2DConverter.areaToShapes(clipper);
706 for (List<Coord> subShape : subShapePoints) {
707 // Use original or share newly created coords on clipped edge.
708 // NB: .intersect()/areaToShapes can output flattened shapes,
709 // normally triangles, in any orientation; check we haven't got one by calc area.
710 long signedAreaSize = 0;
711 int subSize = subShape.size();
712 int c1_highPrecLat = 0, c1_highPrecLon = 0;
713 int c2_highPrecLat, c2_highPrecLon;
714 for (int i = 0; i < subSize; ++i) {
715 Coord co = subShape.get(i);
716 c2_highPrecLat = co.getHighPrecLat();
717 c2_highPrecLon = co.getHighPrecLon();
718 if (i > 0)
719 signedAreaSize += (long)(c2_highPrecLon + c1_highPrecLon) *
720 (c1_highPrecLat - c2_highPrecLat);
721 c1_highPrecLat = c2_highPrecLat;
722 c1_highPrecLon = c2_highPrecLon;
723 long hashVal = Utils.coord2Long(co);
724 Coord replCoord = shapeHashMap.get(hashVal);
725 if (replCoord != null)
726 subShape.set(i, replCoord);
727 else { // not an original coord
728 replCoord = areasHashMap.get(hashVal);
729 if (replCoord != null)
730 subShape.set(i, replCoord);
731 else
732 areasHashMap.put(hashVal, co);
733 }
734 }
735 if (signedAreaSize == 0) {
736 log.warn("splitIntoAreas flat shape. id", e.getOsmid(),
737 "type", uk.me.parabola.mkgmap.reader.osm.GType.formatType(e.getType()), subSize,
738 "points, at", subShape.get(0).toOSMURL());
739 continue;
740 }
741 MapShape s = e.copy();
742 s.setPoints(subShape);
743 s.setClipped(true);
744 areas[areaIndex].addShape(s);
745 used[areaIndex] = true;
746 }
747 }
748 }
749
750
751 /**
612752 * @return true if this area contains any data
613753 */
614754 public boolean hasData() {
2020 import java.util.ArrayList;
2121 import java.util.Arrays;
2222 import java.util.Collections;
23 import java.util.Comparator;
2324 import java.util.HashMap;
2425 import java.util.IdentityHashMap;
2526 import java.util.List;
2627 import java.util.Set;
2728
29 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
30
2831 import uk.me.parabola.imgfmt.ExitException;
32 import uk.me.parabola.imgfmt.Utils;
2933 import uk.me.parabola.imgfmt.app.Coord;
3034 import uk.me.parabola.imgfmt.app.Exit;
3135 import uk.me.parabola.imgfmt.app.Label;
146150
147151 private String licenseFileName;
148152
153 private boolean orderByDecreasingArea;
154
149155 public MapBuilder() {
150156 regionName = null;
151157 locationAutofill = Collections.emptySet();
187193 driveOnLeft = true;
188194 if ("right".equals(driveOn))
189195 driveOnLeft = false;
196 orderByDecreasingArea = props.getProperty("order-by-decreasing-area", false);
190197 }
191198
192199 /**
684691 for (SourceSubdiv srcDivPair : srcList) {
685692
686693 MapSplitter splitter = new MapSplitter(srcDivPair.getSource(), zoom);
687 MapArea[] areas = splitter.split();
694 MapArea[] areas = splitter.split(orderByDecreasingArea);
688695 log.info("Map region", srcDivPair.getSource().getBounds(), "split into", areas.length, "areas at resolution", zoom.getResolution());
689696
690697 for (MapArea area : areas) {
711718 * @param shapes the list of shapes
712719 */
713720 private void prepShapesForMerge(List<MapShape> shapes) {
714 HashMap<Coord,Coord> coordMap = new HashMap<>();
721 Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>();
715722 for (MapShape s : shapes){
716723 List<Coord> points = s.getPoints();
717724 int n = points.size();
718725 for (int i = 0; i< n; i++){
719726 Coord co = points.get(i);
720 Coord repl = coordMap.get(co);
727 Coord repl = coordMap.get(Utils.coord2Long(co));
721728 if (repl == null)
722 coordMap.put(co, co);
723 else
729 coordMap.put(Utils.coord2Long(co), co);
730 else
724731 points.set(i, repl);
725732 }
726733 }
11141121 config.setRoutable(doRoads);
11151122
11161123 if (mergeShapes){
1117 ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res);
1124 ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea);
11181125 List<MapShape> mergedShapes = shapeMergeFilter.merge(shapes);
11191126 shapes = mergedShapes;
11201127 }
11211128
1129 if (orderByDecreasingArea && shapes.size() > 1) {
1130 // sort so that the shape with the largest area is processed first
1131 Collections.sort(shapes, new Comparator<MapShape>() {
1132 public int compare(MapShape s1, MapShape s2) {
1133 return Long.compare(Math.abs(s2.getFullArea()), Math.abs(s1.getFullArea()));
1134 }
1135 });
1136 }
1137
11221138 preserveHorizontalAndVerticalLines(res, shapes);
11231139
11241140 LayerFilterChain filters = new LayerFilterChain(config);
9090 *
9191 * This routine is not called recursively.
9292 *
93 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
9394 * @return An array of map areas, each of which is within the size limit
9495 * and the limit on the number of features.
9596 */
96 public MapArea[] split() {
97 public MapArea[] split(boolean orderByDecreasingArea) {
9798 log.debug("orig area", mapSource.getBounds());
9899
99100 MapArea ma = initialArea(mapSource);
100 MapArea[] areas = splitMaxSize(ma);
101 MapArea[] areas = splitMaxSize(ma, orderByDecreasingArea);
101102
102103 // Now step through each area and see if any have too many map features
103104 // in them. For those that do, we further split them. This is done
104105 // recursively until everything fits.
105106 List<MapArea> alist = new ArrayList<>();
106 addAreasToList(areas, alist, 0);
107 addAreasToList(areas, alist, 0, orderByDecreasingArea);
107108
108109 MapArea[] results = new MapArea[alist.size()];
109110 return alist.toArray(results);
117118 * @param areas The areas to add to the list (and possibly split up).
118119 * @param alist The list that will finally contain the complete list of
119120 * map areas.
120 */
121 private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth) {
121 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
122 */
123 private void addAreasToList(MapArea[] areas, List<MapArea> alist, int depth, boolean orderByDecreasingArea) {
122124 int res = zoom.getResolution();
123125 for (MapArea area : areas) {
124126 Area bounds = area.getBounds();
163165 log.debug("splitting area", area);
164166 MapArea[] sublist;
165167 if(bounds.getWidth() > bounds.getHeight())
166 sublist = area.split(2, 1, res, bounds);
168 sublist = area.split(2, 1, res, bounds, orderByDecreasingArea);
167169 else
168 sublist = area.split(1, 2, res, bounds);
169 addAreasToList(sublist, alist, depth + 1);
170 continue;
170 sublist = area.split(1, 2, res, bounds, orderByDecreasingArea);
171 if (sublist == null) {
172 log.warn("SubDivision is single point at this resolution so can't split at " +
173 area.getBounds().getCenter().toOSMURL() + " (probably harmless)");
174 } else {
175 addAreasToList(sublist, alist, depth + 1, orderByDecreasingArea);
176 continue;
177 }
171178 } else {
172179 log.error("Area too small to split at " + area.getBounds().getCenter().toOSMURL() + " (reduce the density of points, length of lines, etc.)");
173180 }
191198 * If the area is already small enough then it will be returned unchanged.
192199 *
193200 * @param mapArea The area that needs to be split down.
201 * @param orderByDecreasingArea aligns subareas as powerOf2 and splits polygons into the subareas.
194202 * @return An array of map areas. Each will be below the max size.
195203 */
196 private MapArea[] splitMaxSize(MapArea mapArea) {
204 private MapArea[] splitMaxSize(MapArea mapArea, boolean orderByDecreasingArea) {
197205 Area bounds = mapArea.getFullBounds();
198206
199207 int shift = zoom.getShiftValue();
213221 if (height > MAX_DIVISION_SIZE)
214222 ysplit = height / MAX_DIVISION_SIZE + 1;
215223
216 return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds);
224 return mapArea.split(xsplit, ysplit, zoom.getResolution(), bounds, orderByDecreasingArea);
217225 }
218226
219227 /**
4040 private static final Logger log = Logger.getLogger(ShapeMergeFilter.class);
4141 private final int resolution;
4242 private final ShapeHelper dupShape = new ShapeHelper(new ArrayList<Coord>(0));
43
44 public ShapeMergeFilter(int resolution) {
43 private final boolean orderByDecreasingArea;
44
45 public ShapeMergeFilter(int resolution, boolean orderByDecreasingArea) {
4546 this.resolution = resolution;
47 this.orderByDecreasingArea = orderByDecreasingArea;
4648 }
4749
4850 public List<MapShape> merge(List<MapShape> shapes) {
8385 for (Map<MapShape, List<ShapeHelper>> lowMap : sameTypeList){
8486 boolean added = false;
8587 for (MapShape ms: lowMap.keySet()){
88 if (orderByDecreasingArea && ms.getFullArea() != shape.getFullArea())
89 // must not merge areas unless derived from same thing
90 continue;
8691 // we do not use isSimilar() here, as it compares minRes and maxRes as well
8792 String s1 = ms.getName();
8893 String s2 = shape.getName();
6767 try {
6868 int key = Integer.parseInt(keyVal[0]);
6969 if (key < 0 || key > 16)
70 throw new ExitException("Error: Level value out of range 0-16: " + s);
70 throw new ExitException("Error: Level value out of range 0-16: " + s + " in levels specification " + levelSpec);
7171 int value = Integer.parseInt(keyVal[1]);
7272 if (value <= 0 || value > 24)
73 throw new ExitException("Error: Resolution value out of range 0-24: " + s);
73 throw new ExitException("Error: Resolution value out of range 0-24: " + s + " in levels specification " + levelSpec);
7474 levels[count] = new LevelInfo(key, value);
7575 } catch (NumberFormatException e) {
7676 throw new ExitException("Error: Levels specification not all numbers: " + levelSpec + " check " + s);
1414 */
1515 package uk.me.parabola.mkgmap.general;
1616
17 import uk.me.parabola.imgfmt.app.Coord;
18 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
19
1720 /**
1821 * A shape or polygon is just the same as a line really as far as I can tell.
1922 * There are some things that you cannot do with them semantically.
2225 */
2326 public class MapShape extends MapLine {// So top code can link objects from here
2427 private long osmid; //TODO: remove debug aid
28 private long fullArea = Long.MAX_VALUE; // meaning unset
2529 public MapShape() {
2630 osmid = 0;
2731 }
3135 MapShape(MapShape s) {
3236 super(s);
3337 this.osmid = s.osmid;
38 this.fullArea = s.getFullArea();
3439 }
3540
3641 public MapShape copy() {
4954 public long getOsmid() {
5055 return osmid;
5156 }
57
58 public void setFullArea(long fullArea) {
59 this.fullArea = fullArea;
60 }
61
62 public long getFullArea() { // this is unadulterated size, +ve if clockwise
63 if (this.fullArea == Long.MAX_VALUE) {
64 java.util.List<Coord> points = this.getPoints();
65 if (points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1)))
66 this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
67 }
68 return this.fullArea;
69 }
70
5271 }
989989 elementSetup(shape, gt, way);
990990 shape.setPoints(way.getPoints());
991991
992 long areaVal = 0;
993 String tagStringVal = way.getTag(drawLevelTagKey);
994 if (tagStringVal != null) {
995 try {
996 areaVal = Integer.parseInt(tagStringVal);
997 if (areaVal < 1 || areaVal > 100) {
998 log.error("mkgmap:drawLevel must be in range 1..100, not", areaVal);
999 areaVal = 0;
1000 } else if (areaVal <= 50)
1001 areaVal = Long.MAX_VALUE - areaVal; // 1 => MAX_VALUE-1, 50 => MAX_VALUE-50
1002 else
1003 areaVal = 101 - areaVal; // 51 => 50, 100 => 1
1004 } catch (NumberFormatException e) {
1005 log.error("mkgmap:drawLevel invalid integer:", tagStringVal);
1006 }
1007 }
1008 if (areaVal == 0)
1009 areaVal = way.getFullArea();
1010 shape.setFullArea(areaVal);
1011
9921012 clipper.clipShape(shape, collector);
9931013 }
9941014
10691089 };
10701090 private static final short highResOnlyTagKey = TagDict.getInstance().xlate("mkgmap:highest-resolution-only");
10711091 private static final short skipSizeFilterTagKey = TagDict.getInstance().xlate("mkgmap:skipSizeFilter");
1092 private static final short drawLevelTagKey = TagDict.getInstance().xlate("mkgmap:drawLevel");
10721093
10731094 private static final short countryTagKey = TagDict.getInstance().xlate("mkgmap:country");
10741095 private static final short regionTagKey = TagDict.getInstance().xlate("mkgmap:region");
101101 if (performChecks){
102102 boolean fromOverlays = false;
103103 List<Integer> usedTypes = null;
104 if (gt.getMaxResolution() < levels[0].getBits()){
104 if (gt.getMaxResolution() < levels[0].getBits() || gt.getMaxResolution() > 24){
105105 System.out.println("Warning: Object with max resolution of " + gt.getMaxResolution() + " is ignored. Check levels option and style file "+ ts.getFileName() + ", line " + ts.getLinenumber());
106 } else if (gt.getMinResolution() > 24) {
107 System.out.println("Warning: Object with min resolution of " + gt.getMinResolution() + " is ignored. Check levels option and style file "+ ts.getFileName() + ", line " + ts.getLinenumber());
106108 }
107109 if (overlays != null && kind == FeatureKind.POLYLINE){
108110 usedTypes = overlays.get(gt.getType());
33 import java.text.DecimalFormatSymbols;
44 import java.util.Locale;
55
6 import uk.me.parabola.imgfmt.app.Coord;
67 import uk.me.parabola.mkgmap.reader.osm.Element;
78 import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
89 import uk.me.parabola.mkgmap.reader.osm.Way;
910
1011 /**
1112 * Calculates the area size of a polygon in garmin units ^ 2.
13 *
14 * if orderByDecreasingArea then the area of the polygon has already been
15 * calculated and we use it here.
16 * To be totally consistent, ie no possible difference to mkgmap behaviour when
17 * --order-by-decreasing-area is not set, this flag should be set from the option;
18 * However it is now considered that 'fullArea' is what the user might expect, rather
19 * than various different values for the same original because of clipping and cutting to
20 * expose holes.
21 *
1222 * @author WanMil
1323 */
1424 public class AreaSizeFunction extends CachedFunction {
1525
1626 private final DecimalFormat nf = new DecimalFormat("0.0#####################", DecimalFormatSymbols.getInstance(Locale.US));
27 private final boolean orderByDecreasingArea = true;
1728
1829 public AreaSizeFunction() {
1930 super(null);
2637 if (w.hasEqualEndPoints() == false) {
2738 return "0";
2839 }
29 return nf.format(MultiPolygonRelation.calcAreaSize(((Way) el).getPoints()));
40 double areaSize;
41 if (orderByDecreasingArea) {
42 long fullArea = w.getFullArea();
43 if (fullArea == Long.MAX_VALUE)
44 return "0";
45 // convert from high prec to value in map units
46 areaSize = (double) fullArea / (2 * (1<<Coord.DELTA_SHIFT) * (1<<Coord.DELTA_SHIFT));
47 areaSize = Math.abs(areaSize);
48 } else
49 areaSize = MultiPolygonRelation.calcAreaSize(w.getPoints());
50 return nf.format(areaSize);
3051 }
3152 return null;
3253 }
845845
846846 int wi = 0;
847847 for (Way w : polygons) {
848 w.setFullArea(w.getFullArea()); // trigger setting area before start cutting...
849 // do like this to disguise function with side effects
848850 String role = getRole(w);
849851 if ("inner".equals(role)) {
850852 innerPolygons.set(wi);
10331035 outmostPolygonProcessing = false;
10341036 }
10351037
1038 long fullArea = currentPolygon.polygon.getFullArea();
10361039 for (Way mpWay : singularOuterPolygons) {
10371040 // put the cut out polygons to the
10381041 // final way map
10391042 if (log.isDebugEnabled())
10401043 log.debug(mpWay.getId(),mpWay.toTagString());
10411044
1045 mpWay.setFullArea(fullArea);
10421046 // mark this polygons so that only polygon style rules are applied
10431047 mpWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_POLYGON);
10441048 mpWay.addTag(MP_CREATED_TAG, "true");
102102 private static final int MIN_LON = Utils.toMapUnit(-180.0);
103103 private static final int MAX_LON = Utils.toMapUnit(180.0);
104104 private final static Pattern keySplitter = Pattern.compile(Pattern.quote("_"));
105
105
106 /**
107 * When order-by-decreasing-area we need all bit of sea to be output consistently.
108 * Unless _draworder changes things, having seaSize as BIG causes polygons beyond the
109 * coastline to be shown. To hide these and have the sea show up to the high-tide
110 * coastline, can set this to be very small instead (or use _draworder).
111 * <p>
112 * mkgmap:drawLevel can be used to override this value in the style - the default style has:
113 * natural=sea { add mkgmap:skipSizeFilter=true; set mkgmap:drawLevel=2 } [0x32 resolution 10]
114 * which is equivalent to Long.MAX_VALUE-2.
115 */
116 private static final long seaSize = Long.MAX_VALUE-2; // sea is BIG
106117
107118 private static final List<Class<? extends LoadableMapDataSource>> precompSeaLoader;
108119
708719 saver.addWay(w);
709720 }
710721 for (Way w : seaWays) {
722 w.setFullArea(seaSize);
711723 saver.addWay(w);
712724 }
713725 } else {
717729 // first add the complete bounding box as sea
718730 Way sea = new Way(FakeIdGenerator.makeFakeId(),bounds.toCoords());
719731 sea.addTag("natural", "sea");
732 sea.setFullArea(seaSize);
720733
721734 for (Way w : landWays) {
722735 saver.addWay(w);
9901003 ne.getLongitude() + 1));
9911004 sea.addPoint(sea.getPoints().get(0)); // close shape
9921005 sea.addTag("natural", "sea");
1006 sea.setFullArea(seaSize);
9931007
9941008 log.info("sea: ", sea);
9951009 saver.addWay(sea);
2323 import uk.me.parabola.imgfmt.Utils;
2424 import uk.me.parabola.imgfmt.app.Coord;
2525 import uk.me.parabola.log.Logger;
26 import uk.me.parabola.mkgmap.filters.ShapeMergeFilter;
2627
2728 /**
2829 * Represent a OSM way in the 0.5 api. A way consists of an ordered list of
3334 public class Way extends Element {
3435 private static final Logger log = Logger.getLogger(Way.class);
3536 private final List<Coord> points;
37 private long fullArea = Long.MAX_VALUE; // meaning unset
3638
3739 // This will be set if a way is read from an OSM file and the first node is the same node as the last
3840 // one in the way. This can be set to true even if there are missing nodes and so the nodes that we
6264 dup.closedInOSM = this.closedInOSM;
6365 dup.complete = this.complete;
6466 dup.isViaWay = this.isViaWay;
67 dup.fullArea = this.getFullArea();
6568 return dup;
6669 }
6770
251254 public void setViaWay(boolean isViaWay) {
252255 this.isViaWay = isViaWay;
253256 }
257
258 public void setFullArea(long fullArea) {
259 this.fullArea = fullArea;
260 }
261
262 public long getFullArea() { // this is unadulterated size, +ve if clockwise
263 if (this.fullArea == Long.MAX_VALUE && points.size() >= 4 && points.get(0).highPrecEquals(points.get(points.size()-1))) {
264 this.fullArea = ShapeMergeFilter.calcAreaSizeTestVal(points);
265 }
266 return this.fullArea;
267 }
268
254269 }
364364 }
365365
366366 void testOneVariant(String testId, MapShape s1, MapShape s2, int expectedNumShapes, int expectedNumPoints){
367 ShapeMergeFilter smf = new ShapeMergeFilter(24);
367 ShapeMergeFilter smf = new ShapeMergeFilter(24, false);
368368 List<MapShape> res = smf.merge(Arrays.asList(s1,s2));
369369 assertTrue(testId, res != null);
370370 assertEquals(testId,expectedNumShapes, res.size() );