Codebase list mkgmap / 9262cc2
New upstream version 0.0.0+svn4380 Bas Couwenberg 4 years ago
147 changed file(s) with 3370 addition(s) and 3536 deletion(s). Raw diff Collapse all Expand all
1212 Using
1313 =====
1414
15 This program requires Java 1.6 or above to run.
15 This program requires Java 1.8 or above to run.
1616
1717 Producing a map is simple. Save OpenStreetMap data from JOSM
1818 or by any other method to a file and copy it to the mkgmap
5959
6060 to obtain an up to date and complete listing of options.
6161
62 Also consider examples/sample.cfg as a starting point for your options package
63
6264 Processing more than one file at a time
6365 ---------------------------------------
6466
400400 <copy todir="${dist}/examples">
401401 <fileset dir="resources">
402402 <include name="roadNameConfig.txt"/>
403 <include name="sample.cfg"/>
403404 <include name="known-hgt.txt"/>
404405 <include name="installer/**"/>
405406 <include name="styles/default/**"/>
574574 ;--check-roundabout-flares
575575 : Check that roundabout flare roads point in the correct
576576 direction, are one-way and don't extend too far.
577 <p>
578 ;--check-routing-island-len=INTEGER
579 : Routing islands are small road networks which are not connected to other
580 roads. A typical case is a footway that is not connected to the main road
581 network, or a small set of ways on the inner courtyard of a large building
582 : These islands can cause problems if you try to calculate a route and the GPS
583 selects a point on the island as a start or end. It will fail to calculate the
584 route even if a major road is only a few steps away. If this option is
585 specified, then mkgmap will detect these islands. If the value is set to zero,
586 mkgmap will simply report the islands (you will need to set
587 uk.me.parabola.imgfmt.app.net.RoadNetwork.level=INFO to activate logging of
588 the message). If the value is greater than zero, mkgmap will mark islands with
589 a total length less than the specified value in metres as not routable.
590 Reasonable values are 500 or higher. The default is for the check to not take
591 place. If any of the roads forming the island touches a tile boundary or a
592 country border the island is ignored, as it may be connected to other roads in
593 a different tile.
594 : See also option --add-boundary-nodes-at-admin-boundaries.
595 : This option seems to cause routing problems in BaseCamp.
577596 <p>
578597 ;--max-flare-length-ratio=NUM
579598 : When checking flare roads, ignore roads whose length is
6464 | +mkgmap:dir-check+ | Set to +false+ to tell mkgmap to ignore the way when checking roundabouts for direction | 'check-roundabouts'
6565 | +mkgmap:no-dir-check+ | Set to +true+ to tell mkgmap to ignore the way when checking roundabouts for direction | 'check-roundabouts'
6666 | +mkgmap:synthesised+ | Set to +true+ to tell mkgmap that this is an additional way created using the continue statement in an action block and that it should be excluded from checks | 'check-roundabouts', 'check-roundabout-flares'
67
67 | +mkgmap:set_unconnected_type+ | Set to +none+ to remove unconected roads from the map once that all roads are known, or to a valid line type if you want a normal line instead of a road. Works also with overlay lines for the same way. |
68 | +mkgmap:set_semi_connected_type+ | Like mkgmap:set_unconnected_type, but matches for roads with exactly one connection to other roads |
69
70
6871 |=========================================================
6972
7073
566566 Check that roundabout flare roads point in the correct direction,
567567 are one-way and don't extend too far.
568568
569 --check-routing-island-len=INTEGER
570 Routing islands are small road networks which are not connected to other
571 roads. A typical case is a footway that is not connected to the main
572 road network, or a small set of ways on the inner courtyard of a large
573 building
574 These islands can cause problems if you try to calculate a route and the GPS
575 selects a point on the island as a start or end. It will fail to calculate
576 the route even if a major road is only a few steps away. If this option is
577 specified, then mkgmap will detect these islands. If the value is set to
578 zero, mkgmap will simply report the islands (you will need to set
579 uk.me.parabola.imgfmt.app.net.RoadNetwork.level=INFO to activate logging of
580 the message). If the value is greater than zero, mkgmap will mark islands
581 with a total length less than the specified value in metres as not routable.
582 Reasonable values are 500 or higher.
583 The default is for the check to not take place.
584 If any of the roads forming the island touches a tile boundary or
585 a country border the island is ignored, as it may be connected to other
586 roads in a different tile.
587 See also option --add-boundary-nodes-at-admin-boundaries.
588 This option seems to cause routing problems in BaseCamp.
589
569590 --max-flare-length-ratio=NUM
570591 When checking flare roads, ignore roads whose length is
571592 greater than NUM (an integer) times the distance between the
0 svn.version: 4323
1 build.timestamp: 2019-10-26T09:00:42+0100
0 svn.version: 4380
1 build.timestamp: 2019-11-28T14:08:04+0000
0 # Sample mkgmap option file to create a map for a GARMIN GPS and compatible PC Programs
1 #
2 # The options given here are a reasonable set for generating a routable map, but you
3 # will probably need to copy this file into your working environment and adapt it by
4 # fixing paths, adjusting options etc.
5 #
6 # Generally, where the default value for an option as acceptable, it isn't specified here.
7 # Please see the standard help information for details of what the options do.
8 #
9 # Typical command line invocation, after using the splitter to generate a set of tiles:
10 #> java -jar mkgmap-rel/mkgmap.jar -c mkgmap-rel/examples/sample.cfg -c template.args mkgmap-rel/examples/typ-files/mkgmap.txt
11 #
12 gmapsupp
13 gmapi
14 route
15 index
16 bounds=bounds.zip
17 precomp-sea=sea.zip
18 location-autofill=is_in,nearest
19 housenumbers
20 max-jobs
21 drive-on=detect
22 add-pois-to-areas
23 link-pois-to-ways
24 process-destination
25 process-exits
26 code-page=1252
27 check-routing-island-len=700
28 remove-ovm-work-files
29 #
30 # Other common options you may wish to consider:
31 #
32 #style-file=mkgmap-rel/examples/styles/default
33 # The above is the default style, change and enable this to use your own style
34 #road-name-config=mkgmap-rel/examples/roadNameConfig.txt
35 #split-name-index
36 #make-opposite-cycleways
37 #order-by-decreasing-area
38 #name-tag-list=name:en,int_name,name,place_name,loc_name
44
55 characters
66
7 =0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad=05bd=200e=200f,0001,0002,0003,0004,0005,0006,0007 ; 05b0 ; 05b1 ; 05b2 ; 05b3 ; 05b4 ; 05b5 ; 05b6 ; 05b7 ; 05b8 ; 05b9 ; 05bb ; 05c2 ; 05c1 ; 05bc ; 05bf
7 =0008=000e=000f=0010=0011=0012=0013=0014=0015=0016=0017=0018=0019=001a=001b=001c=001d=001e=001f=007f=00ad=05bd=200e=200f,0001,0002,0003,0004,0005,0006,0007
8 < 05b0 ; 05b1 ; 05b2 ; 05b3 ; 05b4 ; 05b5 ; 05b6 ; 05b7 ; 05b8 ; 05b9 ; 05bb ; 05c2 ; 05c1 ; 05bc ; 05bf
89 < 0009
910 < 000a
1011 < 000b
3131 */
3232 public class Area {
3333 private static final Logger log = Logger.getLogger(Area.class);
34 public final static Area PLANET = new Area(-90.0, -180.0, 90.0, 180.0);
34 public static final Area PLANET = new Area(-90.0, -180.0, 90.0, 180.0);
3535
3636 private final int minLat;
3737 private final int minLong;
168168 * @throws MapFailedException if more complex split operation couldn't be honoured.
169169 */
170170 public Area[] split(int xsplit, int ysplit, int resolutionShift) {
171 Area[] areas = new Area[xsplit * ysplit];
171 Area[] areas = new Area[xsplit * ysplit];
172172
173173 int xstart;
174174 int xend;
181181 if (x == xsplit - 1)
182182 xend = maxLong;
183183 else
184 xend = roundPof2(xstart + (maxLong - xstart) / (xsplit - x),
185 resolutionShift);
184 xend = roundPof2(xstart + (maxLong - xstart) / (xsplit - x), resolutionShift);
186185 ystart = minLat;
187186 for (int y = 0; y < ysplit; y++) {
188187 if (y == ysplit - 1)
189188 yend = maxLat;
190189 else
191 yend = roundPof2(ystart + (maxLat - ystart) / (ysplit - y),
192 resolutionShift);
190 yend = roundPof2(ystart + (maxLat - ystart) / (ysplit - y), resolutionShift);
193191 if (xstart < xend && ystart < yend) {
194192 Area a = new Area(ystart, xstart, yend, xend);
195 // log.debug(x, y, a);
196 log.debug("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "to", ystart, xstart, yend, xend);
193 log.debug("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "to", ystart,
194 xstart, yend, xend);
197195 areas[nAreas++] = a;
198 } else
199 log.warn("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "can't", xsplit, ysplit);
196 } else {
197 log.warn("Area.split", minLat, minLong, maxLat, maxLong, "res", resolutionShift, "can't", xsplit,
198 ysplit);
199 }
200200 ystart = yend;
201201 }
202202 xstart = xend;
317317 }
318318
319319 public boolean equals(Object o) {
320 if (this == o) return true;
321 if (o == null || getClass() != o.getClass()) return false;
320 if (this == o)
321 return true;
322 if (o == null || getClass() != o.getClass())
323 return false;
322324
323325 Area area = (Area) o;
324
325 if (maxLat != area.maxLat) return false;
326 if (maxLong != area.maxLong) return false;
327 if (minLat != area.minLat) return false;
328 if (minLong != area.minLong) return false;
329
330 return true;
326 return maxLat == area.maxLat && maxLong == area.maxLong && minLat == area.minLat && minLong == area.minLong;
331327 }
332328
333329 public int hashCode() {
342338 * @return list of coords that form the rectangle
343339 */
344340 public List<Coord> toCoords(){
345 List<Coord> coords = new ArrayList<Coord>(5);
346 Coord start = new Coord(minLat, minLong);
347 coords.add(start);
348 Coord co = new Coord(minLat, maxLong);
349 coords.add(co);
350 co = new Coord(maxLat, maxLong);
351 coords.add(co);
352 co = new Coord(maxLat, minLong);
353 coords.add(co);
354 coords.add(start);
341 List<Coord> coords = new ArrayList<>(5);
342 coords.add(new Coord(minLat, minLong));
343 coords.add(new Coord(minLat, maxLong));
344 coords.add(new Coord(maxLat, maxLong));
345 coords.add(new Coord(maxLat, minLong));
346 coords.add(coords.get(0));
355347 return coords;
356348 }
357349
3636 * @author Steve Ratcliffe
3737 */
3838 public class Coord implements Comparable<Coord> {
39 private final static short ON_BOUNDARY_MASK = 0x0001; // bit in flags is true if point lies on a boundary
40 private final static short PRESERVED_MASK = 0x0002; // bit in flags is true if point should not be filtered out
41 private final static short REPLACED_MASK = 0x0004; // bit in flags is true if point was replaced
42 private final static short ADDED_BY_CLIPPER_MASK = 0x0008; // bit in flags is true if point was added by clipper
43 private final static short FIXME_NODE_MASK = 0x0010; // bit in flags is true if a node with this coords has a fixme tag
44 private final static short REMOVE_MASK = 0x0020; // bit in flags is true if this point should be removed
45 private final static short VIA_NODE_MASK = 0x0040; // bit in flags is true if a node with this coords is the via node of a RestrictionRelation
46
47 private final static short PART_OF_BAD_ANGLE = 0x0080; // bit in flags is true if point should be treated as a node
48 private final static short PART_OF_SHAPE2 = 0x0100; // use only in ShapeMerger
49 private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
50 private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
51 private final static short ADDED_HOUSENUMBER_NODE = 0x0800; // node was added for house numbers
52 private final static short ON_COUNTRY_BORDER = 0x1000; // node is on a country border
53
54 private final static int HIGH_PREC_BITS = 30;
55 public final static int DELTA_SHIFT = HIGH_PREC_BITS - 24;
56 private final static int MAX_DELTA = 1 << (DELTA_SHIFT - 2); // max delta abs value that is considered okay
57 private final static long FACTOR_HP = 1L << HIGH_PREC_BITS;
58
59 public final static double R = 6378137.0; // Radius of earth at equator as defined by WGS84
60 public final static double U = R * 2 * Math.PI; // circumference of earth at equator (WGS84)
61 public final static double MEAN_EARTH_RADIUS = 6371000; // earth is a flattened sphere
39 private static final short ON_BOUNDARY_MASK = 0x0001; // bit in flags is true if point lies on a boundary
40 private static final short PRESERVED_MASK = 0x0002; // bit in flags is true if point should not be filtered out
41 private static final short REPLACED_MASK = 0x0004; // bit in flags is true if point was replaced
42 private static final short ADDED_BY_CLIPPER_MASK = 0x0008; // bit in flags is true if point was added by clipper
43 private static final short FIXME_NODE_MASK = 0x0010; // bit in flags is true if a node with this coords has a fixme tag
44 private static final short REMOVE_MASK = 0x0020; // bit in flags is true if this point should be removed
45 private static final short VIA_NODE_MASK = 0x0040; // bit in flags is true if a node with this coords is the via node of a RestrictionRelation
46
47 private static final short PART_OF_BAD_ANGLE = 0x0080; // bit in flags is true if point should be treated as a node
48 private static final short PART_OF_SHAPE2 = 0x0100; // use only in ShapeMerger
49 private static final short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
50 private static final short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
51 private static final short ADDED_HOUSENUMBER_NODE = 0x0800; // node was added for house numbers
52 private static final short ON_COUNTRY_BORDER = 0x1000; // node is on a country border
53
54 private static final int HIGH_PREC_BITS = 30;
55 public static final int DELTA_SHIFT = HIGH_PREC_BITS - 24;
56 private static final int MAX_DELTA = 1 << (DELTA_SHIFT - 2); // max delta abs value that is considered okay
57 private static final long FACTOR_HP = 1L << HIGH_PREC_BITS;
58
59 public static final double R = 6378137.0; // Radius of earth at equator as defined by WGS84
60 public static final double U = R * 2 * Math.PI; // circumference of earth at equator (WGS84)
61 public static final double MEAN_EARTH_RADIUS = 6371000; // earth is a flattened sphere
6262
6363 private final int latitude;
6464 private final int longitude;
9393 this.lonDelta = (byte) ((this.longitude << DELTA_SHIFT) - lonHighPrec);
9494
9595 // verify math
96 assert (this.latitude << DELTA_SHIFT) - latDelta == latHighPrec;
97 assert (this.longitude << DELTA_SHIFT) - lonDelta == lonHighPrec;
96 assert (this.latitude << DELTA_SHIFT) - (int) latDelta == latHighPrec;
97 assert (this.longitude << DELTA_SHIFT) - (int) lonDelta == lonHighPrec;
9898 }
9999
100100 private Coord (int lat, int lon, byte latDelta, byte lonDelta){
403403 * Compares the coordinates that are displayed in the map
404404 */
405405 public boolean equals(Object obj) {
406 if (obj == null || !(obj instanceof Coord))
406 if (!(obj instanceof Coord))
407407 return false;
408408 Coord other = (Coord) obj;
409409 return latitude == other.latitude && longitude == other.longitude;
485485 double sinMidLat = Math.sin((lat1-lat2)/2);
486486 double sinMidLon = Math.sin((lon1-lon2)/2);
487487 double dRad = 2*Math.asin(Math.sqrt(sinMidLat*sinMidLat + Math.cos(lat1)*Math.cos(lat2)*sinMidLon*sinMidLon));
488 double distance= dRad * R;
489 return distance;
488 return dRad * R; // the distance
490489 }
491490
492491 /**
512511
513512 // distance is pythagoras on 'stretched' Mercator projection
514513 double distRad = Math.sqrt(dLat*dLat + q*q*dLon*dLon); // angular distance in radians
515 double dist = distRad * R;
516
517 return dist;
518
514 return distRad * R; // the distance
519515 }
520516
521517 /**
651647 /* Factor for conversion to radians using HIGH_PREC_BITS bits
652648 * (Math.PI / 180) * (360.0 / (1 << HIGH_PREC_BITS))
653649 */
654 final static double HIGH_PREC_RAD_FACTOR = 2 * Math.PI / FACTOR_HP;
650 static final double HIGH_PREC_RAD_FACTOR = 2 * Math.PI / FACTOR_HP;
655651
656652 /**
657653 * Convert to radians using high precision
666662 * @return Latitude as signed HIGH_PREC_BITS bit integer
667663 */
668664 public int getHighPrecLat() {
669 return (latitude << DELTA_SHIFT) - latDelta;
665 return (latitude << DELTA_SHIFT) - (int) latDelta;
670666 }
671667
672668 /**
673669 * @return Longitude as signed HIGH_PREC_BITS bit integer
674670 */
675671 public int getHighPrecLon() {
676 return (longitude << DELTA_SHIFT) - lonDelta;
672 return (longitude << DELTA_SHIFT) - (int) lonDelta;
677673 }
678674
679675 /**
5959 }
6060
6161 void write(ImgFileWriter writer) {
62 //writer.put3()
6362 if (pointRef) {
64 // System.err.println("City point = " + (int)pointIndex + " div = " + subdivision.getNumber());
6563 writer.put1u(pointIndex);
6664 writer.put2u(subdivision.getNumber());
6765 } else {
105103 }
106104
107105 public String getName() {
108 if (label == null)
109 return "";
110 return label.getText();
106 return label == null ? "" : label.getText();
111107 }
112108
113109 public int getLblOffset() {
114 if (label == null)
115 return 0;
116 return label.getOffset();
110 return label == null ? 0 : label.getOffset();
117111 }
118112
119113 public String toString() {
123117 if (subdivision != null)
124118 result += " " + subdivision.getNumber() + "/" + pointIndex;
125119 if(country != null)
126 result += " in country " + (0 + country.getIndex());
120 result += " in country " + country.getIndex();
127121 if(region != null)
128 result += " in region " + (0 + region.getIndex());
122 result += " in region " + region.getIndex();
129123
130124 return result;
131125 }
158152 }
159153
160154 public int getCountryNumber() {
161 return country != null ? country.getIndex() : 0;
155 return country == null ? 0 : country.getIndex();
162156 }
163157
164158 public Label getLabel() {
2626 public class Country {
2727 // The country number. This is not recorded in the file
2828 private int index;
29 private Label label;
29 private final Label label;
3030
31 public Country(int index) {
32 this.index = (char) index;
31 public Country(int index, Label label) {
32 this.index = index;
33 this.label = label;
3334 }
3435
3536 void write(ImgFileWriter writer) {
4041 return index;
4142 }
4243
43 public void setLabel(Label label) {
44 this.label = label;
45 }
46
4744 public Label getLabel() {
4845 return label;
4946 }
5656 private final PlacesFile places = new PlacesFile();
5757 private Sort sort;
5858
59 // Shift value for the label offset.
60 private final int offsetMultiplier = 1;
59 // Shift value for the label offset, we always use 1 here.
60 private static final int OFFSET_MULTIPLIER = 1;
6161
6262 public LBLFile(ImgChannel chan, Sort sort) {
6363 this.sort = sort;
6464 lblHeader.setSort(sort);
65 lblHeader.setOffsetMultiplier(offsetMultiplier);
65 lblHeader.setOffsetMultiplier(OFFSET_MULTIPLIER);
6666 setHeader(lblHeader);
6767
6868 setWriter(new BufferedImgFileWriter(chan));
6969
70 position(LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength());
70 position((long) LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength());
7171
7272 // The zero offset is for no label.
7373 getWriter().put1u(0);
155155 */
156156 private void alignForNext() {
157157 // Align ready for next label
158 while ((getCurrentLabelOffset() & ((1 << offsetMultiplier) - 1)) != 0)
158 while ((getCurrentLabelOffset() & ((1 << OFFSET_MULTIPLIER) - 1)) != 0)
159159 getWriter().put1u(0);
160160 }
161161
162162 private int getNextLabelOffset() {
163 return getCurrentLabelOffset() >> offsetMultiplier;
163 return getCurrentLabelOffset() >> OFFSET_MULTIPLIER;
164164 }
165165
166166 private int getCurrentLabelOffset() {
142142 Label label = fetchLabel(offset);
143143
144144 if (label != null) {
145 Country country = new Country(index);
146 country.setLabel(label);
145 Country country = new Country(index, label);
147146 countries.add(country);
148147 }
149148 index++;
170169 int offset = reader.get3u();
171170 Label label = fetchLabel(offset);
172171 if (label != null) {
173 Region region = new Region(countries.get(country));
172 Region region = new Region(countries.get(country), label);
174173 region.setIndex(index);
175 region.setLabel(label);
176174
177175 regions.add(region);
178176 }
244242 int start = header.getLabelStart();
245243 int size = header.getLabelSize();
246244
247 reader.position(start + mult);
245 reader.position((long) start + mult);
248246 int labelOffset = mult;
249247
250248 for (int off = mult; off <= size; off++) {
312310 while (reader.position() < end) {
313311 int lblOffset = reader.get3u();
314312
315 Zip zip = new Zip();
316 zip.setLabel(fetchLabel(lblOffset));
313 Zip zip = new Zip(fetchLabel(lblOffset));
317314 zip.setIndex(zipIndex);
318315
319316 zips.put(zip.getIndex(), zip);
3434 this.name = name;
3535 this.poiIndex = poiIndex;
3636 this.group = group;
37 this.subType = subType & 0xff;
37 this.subType = subType;
3838 }
3939
4040 void write(ImgFileWriter writer) {
1919 import java.util.LinkedHashMap;
2020 import java.util.List;
2121 import java.util.Map;
22 import java.util.Map.Entry;
2223 import java.util.Random;
24 import java.util.TreeMap;
2325
2426 import uk.me.parabola.imgfmt.app.Exit;
2527 import uk.me.parabola.imgfmt.app.ImgFileWriter;
3335 * This is really part of the LBLFile. We split out all the parts of the file
3436 * that are to do with location to here.
3537 */
36 @SuppressWarnings({"unchecked", "rawtypes"})
3738 public class PlacesFile {
3839 public static final int MIN_INDEXED_POI_TYPE = 0x29;
3940 public static final int MAX_INDEXED_POI_TYPE = 0x30;
5253 private final List<Highway> highways = new ArrayList<>();
5354 private final List<ExitFacility> exitFacilities = new ArrayList<>();
5455 private final List<POIRecord> pois = new ArrayList<>();
55 private final List[] poiIndex = new ArrayList[256];
56 private final TreeMap<Integer, List<POIIndex>> poiIndex = new TreeMap<>();
5657
5758 private LBLFile lblFile;
5859 private PlacesHeader placeHeader;
7576 }
7677
7778 void write(ImgFileWriter writer) {
78 for (Country c : countryList)
79 c.write(writer);
79 countryList.forEach(c -> c.write(writer));
8080 placeHeader.endCountries(writer.position());
8181
82 for (Region region : regionList)
83 region.write(writer);
82 regionList.forEach(r -> r.write(writer));
8483 placeHeader.endRegions(writer.position());
8584
86 for (City sc : cityList)
87 sc.write(writer);
85 cityList.forEach(sc -> sc.write(writer));
8886
8987 placeHeader.endCity(writer.position());
9088
91 for (List<POIIndex> pil : poiIndex) {
92 if(pil != null) {
93 // sort entries by POI name
94 List<SortKey<POIIndex>> sorted = new ArrayList<>();
95 for (POIIndex index : pil) {
96 SortKey<POIIndex> sortKey = sort.createSortKey(index, index.getName());
97 sorted.add(sortKey);
98 }
99 sorted.sort(null);
100
101 for (SortKey<POIIndex> key : sorted) {
102 key.getObject().write(writer);
103 }
89 for (List<POIIndex> pil : poiIndex.values()) {
90 // sort entries by POI name
91 List<SortKey<POIIndex>> sorted = new ArrayList<>();
92 for (POIIndex index : pil) {
93 SortKey<POIIndex> sortKey = sort.createSortKey(index, index.getName());
94 sorted.add(sortKey);
95 }
96 sorted.sort(null);
97
98 for (SortKey<POIIndex> key : sorted) {
99 key.getObject().write(writer);
104100 }
105101 }
106102 placeHeader.endPOIIndex(writer.position());
113109 placeHeader.endPOI(writer.position());
114110
115111 int numPoiIndexEntries = 0;
116 for (int i = 0; i < 256; ++i) {
117 if(poiIndex[i] != null) {
118 writer.put1u(i);
119 writer.put3u(numPoiIndexEntries + 1);
120 numPoiIndexEntries += poiIndex[i].size();
121 }
112 for (Entry<Integer, List<POIIndex>> entry : poiIndex.entrySet()) {
113 int i = entry.getKey();
114 writer.put1u(i);
115 writer.put3u(numPoiIndexEntries + 1);
116 numPoiIndexEntries += entry.getValue().size();
122117 }
123118 placeHeader.endPOITypeIndex(writer.position());
124119
125 for (Zip z : zipList)
126 z.write(writer);
120 zipList.forEach(z -> z.write(writer));
127121 placeHeader.endZip(writer.position());
128122
129123 int extraHighwayDataOffset = 0;
134128 }
135129 placeHeader.endHighway(writer.position());
136130
137 for (ExitFacility ef : exitFacilities)
138 ef.write(writer);
131 exitFacilities.forEach(ef -> ef.write(writer));
139132 placeHeader.endExitFacility(writer.position());
140133
141 for (Highway h : highways)
142 h.write(writer, true);
134 highways.forEach(h -> h.write(writer, true));
143135 placeHeader.endHighwayData(writer.position());
144136 }
145137
146138 Country createCountry(String name, String abbr) {
147
148139 String s = abbr != null ? name + (char)0x1d + abbr : name;
149
150 Country c = countries.get(s);
151
152 if(c == null) {
153 c = new Country(countries.size()+1);
154
155 Label l = lblFile.newLabel(s);
156 c.setLabel(l);
157 countries.put(s, c);
158 }
159 return c;
140 return countries.computeIfAbsent(s, k -> new Country(countries.size() + 1, lblFile.newLabel(s)));
160141 }
161142
162143 Region createRegion(Country country, String name, String abbr) {
163
164 String s = abbr != null ? name + (char)0x1d + abbr : name;
165
144 String s = abbr != null ? name + (char) 0x1d + abbr : name;
166145 String uniqueRegionName = s.toUpperCase() + "_C" + country.getLabel().getOffset();
167
168 Region r = regions.get(uniqueRegionName);
169
170 if(r == null) {
171 r = new Region(country);
172 Label l = lblFile.newLabel(s);
173 r.setLabel(l);
174 regionList.add(r);
175 regions.put(uniqueRegionName, r);
176 }
177 return r;
178 }
179
146 return regions.computeIfAbsent(uniqueRegionName, k -> new Region(country, lblFile.newLabel(s)));
147 }
148
149 /**
150 * Create city without region
151 * @param country the country
152 * @param name the name of the city
153 * @param unique set to true if you needed a new city object
154 * @return the city object
155 */
180156 City createCity(Country country, String name, boolean unique) {
181
182157 String uniqueCityName = name.toUpperCase() + "_C" + country.getLabel().getOffset();
183158
184159 // if unique is true, make sure that the name really is unique
185 if(unique && cities.get(uniqueCityName) != null) {
186 do {
187 // add random suffix
188 uniqueCityName += "_" + new Random().nextInt(0x10000);
189 } while(cities.get(uniqueCityName) != null);
160 if (unique) {
161 uniqueCityName = createUniqueCityName(uniqueCityName);
190162 }
191163
192164 City c = null;
196168 if (c == null) {
197169 c = new City(country);
198170
199 Label l = lblFile.newLabel(name);
200 c.setLabel(l);
171 c.setLabel(lblFile.newLabel(name));
201172
202173 cityList.add(c);
203174 cities.put(uniqueCityName, c);
207178 return c;
208179 }
209180
181 /**
182 * Create city with region
183 * @param region the region
184 * @param name the name of the city
185 * @param unique set to true if you needed a new city object
186 * @return the city object
187 */
210188 City createCity(Region region, String name, boolean unique) {
211189
212190 String uniqueCityName = name.toUpperCase() + "_R" + region.getLabel().getOffset();
213191
214 // if unique is true, make sure that the name really is unique
215 if (unique && cities.get(uniqueCityName) != null) {
216 do {
217 // add semi-random suffix.
218 uniqueCityName += "_" + random.nextInt(0x10000);
219 } while(cities.get(uniqueCityName) != null);
192 if (unique) {
193 uniqueCityName = createUniqueCityName(uniqueCityName);
220194 }
221195
222196 City c = null;
223 if(!unique)
197 if(!unique) {
224198 c = cities.get(uniqueCityName);
199 }
225200
226201 if(c == null) {
227202 c = new City(region);
228203
229 Label l = lblFile.newLabel(name);
230 c.setLabel(l);
204 c.setLabel(lblFile.newLabel(name));
231205
232206 cityList.add(c);
233207 cities.put(uniqueCityName, c);
237211 return c;
238212 }
239213
214 private String createUniqueCityName(String name) {
215 String uniqueCityName = name;
216 while (cities.get(uniqueCityName) != null) {
217 // add semi-random suffix.
218 uniqueCityName += "_" + random.nextInt(0x10000);
219 }
220 return uniqueCityName;
221 }
222
240223 Zip createZip(String code) {
241 Zip z = postalCodes.get(code);
242
243 if(z == null) {
244 z = new Zip();
245
246 Label l = lblFile.newLabel(code);
247 z.setLabel(l);
248
249 zipList.add(z);
250 postalCodes.put(code, z);
251 }
252 return z;
224 return postalCodes.computeIfAbsent(code, k -> new Zip(lblFile.newLabel(code)));
253225 }
254226
255227 Highway createHighway(Region region, String name) {
256228 Highway h = new Highway(region, highways.size()+1);
257229
258 Label l = lblFile.newLabel(name);
259 h.setLabel(l);
230 h.setLabel(lblFile.newLabel(name));
260231
261232 highways.add(h);
262233 return h;
271242
272243 POIRecord createPOI(String name) {
273244 assert !poisClosed;
274 // TODO...
275245 POIRecord p = new POIRecord();
276246
277 Label l = lblFile.newLabel(name);
278 p.setLabel(l);
279
247 p.setLabel(lblFile.newLabel(name));
280248 pois.add(p);
281249
282250 return p;
284252
285253 POIRecord createExitPOI(String name, Exit exit) {
286254 assert !poisClosed;
287 // TODO...
288255 POIRecord p = new POIRecord();
289256
290 Label l = lblFile.newLabel(name);
291 p.setLabel(l);
292
257 p.setLabel(lblFile.newLabel(name));
293258 p.setExit(exit);
294
295259 pois.add(p);
296260
297261 return p;
303267 if (t < MIN_INDEXED_POI_TYPE || t > MAX_INDEXED_POI_TYPE)
304268 return;
305269
306 POIIndex pi = new POIIndex(name, index, group, type);
307 if(poiIndex[t] == null)
308 poiIndex[t] = new ArrayList<POIIndex>();
309 poiIndex[t].add(pi);
270 POIIndex pi = new POIIndex(name, index, group, type & 0xff);
271 poiIndex.computeIfAbsent(t, k -> new ArrayList<>()).add(pi);
310272 }
311273
312274 void allPOIsDone() {
329291 }
330292
331293 /**
332 * I don't know that you have to sort these (after all most tiles will
294 * I don't know that you have to sort these (after almost all tiles will
333295 * only be in one country or at least a very small number).
334296 *
335297 * But why not?
356318 */
357319 private void sortRegions() {
358320 List<SortKey<Region>> keys = new ArrayList<>();
359 for (Region r : regionList) {
321 for (Region r : regions.values()) {
360322 SortKey<Region> key = sort.createSortKey(r, r.getLabel(), r.getCountry().getIndex());
361323 keys.add(key);
362324 }
2727 private int index;
2828
2929 private final Country country;
30 private Label label;
30 private final Label label;
3131
32 public Region(Country country) {
32 public Region(Country country, Label label) {
3333 this.country = country;
34 this.label = label;
3435 }
3536
3637 public void write(ImgFileWriter writer) {
5152 this.index = index;
5253 }
5354
54 public void setLabel(Label label) {
55 this.label = label;
56 }
57
5855 public Label getLabel() {
5956 return label;
6057 }
2727 // The index is not stored in the file, you just use the index of it in
2828 // the section.
2929 private int index;
30 private Label label;
30 private final Label label;
31
32 public Zip(Label label) {
33 this.label = label;
34 }
3135
3236 public void write(ImgFileWriter writer) {
3337 writer.put3u(label.getOffset());
3741 return label;
3842 }
3943
40 public void setLabel(Label label) {
41 this.label = label;
42 }
43
4444 public int getIndex() {
4545 return index;
4646 }
5656 merge(list,start,len);
5757 } else {
5858 // sort one chunk
59 // System.out.println("sorting list of roads. positions " + start + " to " + (start + len - 1));
6059 Map<String, byte[]> cache = new HashMap<>();
6160 List<SortKey<T>> keys = new ArrayList<>(len);
6261
6362 for (int i = start; i < start + len; i++) {
6463 keys.add(makeKey(list.get(i), sort, cache));
6564 }
66 cache = null;
65 cache = null; // release memory
6766 keys.sort(null);
6867
6968 for (int i = 0; i < keys.size(); i++){
7170 T r = sk.getObject();
7271 list.set(start+i, r);
7372 }
74 return;
7573 }
7674 }
7775
7876
7977 private void merge(List<T> list, int start, int len) {
80 // System.out.println("merging positions " + start + " to " + (start + len - 1));
8178 int pos1 = start;
8279 int pos2 = start + len / 2;
8380 int stop1 = start + len / 2;
8885 SortKey<T> sk1 = null;
8986 SortKey<T> sk2 = null;
9087 while (pos1 < stop1 && pos2 < stop2) {
91 if (fetch1 && pos1 < stop1) {
88 if (fetch1) {
9289 sk1 = makeKey(list.get(pos1), sort, null);
9390 fetch1 = false;
9491 }
95 if (fetch2 && pos2 < stop2) {
92 if (fetch2) {
9693 sk2 = makeKey(list.get(pos2), sort, null);
9794 fetch2 = false;
9895 }
309309 mdr10.setNumberOfPois(mdr11.getNumberOfPois());
310310 mdr12.setIndex(mdr11.getIndex());
311311 mdr19.setPois(mdr11.getPois());
312 if (forDevice & !isMulti) {
312 if (forDevice && !isMulti) {
313313 mdr17.addPois(mdr11.getPois());
314314 }
315315 mdr11.release();
336336 writeSection(writer, 5, mdr5);
337337 mdr25.sortCities(mdr5.getCities());
338338 mdr27.sortCities(mdr5.getCities());
339 if (forDevice & !isMulti) {
339 if (forDevice && !isMulti) {
340340 mdr17.addCities(mdr5.getSortedCities());
341341 }
342342 mdr5.release();
350350 mdr21.release();
351351
352352 mdr22.buildFromStreets(mdr7.getStreets());
353 if (forDevice & !isMulti) {
353 if (forDevice && !isMulti) {
354354 mdr17.addStreets(mdr7.getSortedStreets());
355355 }
356356
357357 mdr7.release();
358358 writeSection(writer, 22, mdr22);
359 if (forDevice & !isMulti) {
359 if (forDevice && !isMulti) {
360360 mdr17.addStreetsByCountry(mdr22.getStreets());
361361 }
362362 mdr22.release();
1111 */
1212 package uk.me.parabola.imgfmt.app.mdr;
1313
14 import uk.me.parabola.imgfmt.ReadFailedException;
1514 import uk.me.parabola.imgfmt.app.CommonHeader;
1615 import uk.me.parabola.imgfmt.app.ImgFileReader;
1716 import uk.me.parabola.imgfmt.app.ImgFileWriter;
4847 sections[1].setPosition(getHeaderLength());
4948 }
5049
51 protected void readFileHeader(ImgFileReader reader) throws ReadFailedException {
50 protected void readFileHeader(ImgFileReader reader) {
5251 throw new UnsupportedOperationException("not implemented yet");
5352 }
5453
5656 */
5757 public void addMap(int mapNumber, int index) {
5858 assert index > 0;
59 Mdr1Record rec = new Mdr1Record(mapNumber, getConfig());
59 Mdr1Record rec = new Mdr1Record(mapNumber);
6060 rec.setMapIndex(index);
6161 maps.add(rec);
6262
9595 }
9696
9797 public void setStartPosition(int sectionNumber) {
98 if (isForDevice())
99 return;
100
101 for (Mdr1Record mi : maps)
102 mi.getMdrMapIndex().startSection(sectionNumber);
98 // nothing to do
10399 }
104100
105101 public void setEndPosition(int sectionNumber) {
5454 if (group == 0)
5555 return;
5656 if (group == 1)
57 t.setSubtype(fullType);
57 t.setSubtype(MdrUtils.getTypeFromFullType(fullType)); // cities
5858 else {
5959 t.setSubtype(MdrUtils.getSubtypeFromFullType(fullType));
6060 }
126126 this.numberOfPois = numberOfPois;
127127 }
128128
129 @Override
129130 protected void releaseMemory() {
130131 poiTypes = null;
131132 }
2828 private Mdr11Record mdr11ref;
2929
3030 public int compareTo(Mdr10Record o) {
31 if (mdr11ref.getRecordNumber() == o.mdr11ref.getRecordNumber())
32 return 0;
33 else if (mdr11ref.getRecordNumber() < o.mdr11ref.getRecordNumber())
34 return -1;
35 else
36 return 1;
31 return Integer.compare(mdr11ref.getRecordNumber(), o.mdr11ref.getRecordNumber());
3732 }
3833
3934 public Mdr11Record getMdr11ref() {
5555 * The POI index contains individual references to POI by subdiv and index, so they are not
5656 * de-duplicated in the index in the same way that streets and cities are.
5757 */
58 @Override
5859 protected void preWriteImpl() {
5960 pois.trimToSize();
6061 Sort sort = getConfig().getSort();
6667 return sort.createSortKey(r, r.getName(), r.getMapIndex(), cache);
6768 }
6869 };
69 // System.out.println("sorting " + pois.size() + " pois by name");
7070 sorter.sort(pois);
7171 for (Mdr11Record poi : pois) {
7272 mdr10.addPoiType(poi);
8787 if (poi.isCity())
8888 putRegionIndex(writer, poi.getRegionIndex());
8989 else
90 putCityIndex(writer, poi.getCityIndex(), true);
90 putCityIndex(writer, poi.getCityIndex());
9191 if (hasStrings)
9292 putStringOffset(writer, poi.getStrOffset());
9393 }
144144 rec--;
145145 }
146146
147 Mdr12Record indexRecord = new Mdr12Record();
148 indexRecord.setPrefix(prefix);
149 indexRecord.setRecordNumber(rec);
150 list.add(indexRecord);
147 list.add(new Mdr12Record(prefix, rec));
151148 }
152149 return list;
153150 }
167164 this.mdr10 = mdr10;
168165 }
169166
167 @Override
170168 public void releaseMemory() {
171169 pois = null;
172170 mdr10 = null;
2222 super(config);
2323 }
2424
25 @Override
2526 protected int associatedSize() {
2627 return getSizes().getPoiSize();
2728 }
1717 */
1818 public class Mdr12Record extends Mdr8Record {
1919 // This is exactly the same as mdr8
20 public Mdr12Record(char[] prefix, int recordNumber) {
21 super(prefix, recordNumber);
22 }
2023 }
2222 * @author Steve Ratcliffe
2323 */
2424 public class Mdr13 extends MdrSection implements HasHeaderFlags {
25 private final List<Mdr13Record> regions = new ArrayList<Mdr13Record>();
25 private final List<Mdr13Record> regions = new ArrayList<>();
2626
2727 public Mdr13(MdrConfig config) {
2828 setConfig(config);
2828 * We sort first by map id and then by region id.
2929 */
3030 public int compareTo(Mdr13Record o) {
31 int v1 = (getMapIndex()<<16) + regionIndex;
32 int v2 = (o.getMapIndex()<<16) + o.regionIndex;
33 if (v1 < v2)
34 return -1;
35 else if (v1 > v2)
36 return 1;
37 else
38 return 0;
31 int d = Integer.compare(getMapIndex(), o.getMapIndex());
32 if (d == 0)
33 d = Integer.compare(regionIndex, o.regionIndex);
34 return d;
3935 }
4036
4137 public int getRegionIndex() {
2222 * @author Steve Ratcliffe
2323 */
2424 public class Mdr14 extends MdrSection implements HasHeaderFlags {
25 private final List<Mdr14Record> countries = new ArrayList<Mdr14Record>();
25 private final List<Mdr14Record> countries = new ArrayList<>();
2626
2727 public Mdr14(MdrConfig config) {
2828 setConfig(config);
2828 * it wasn't.
2929 */
3030 public int compareTo(Mdr14Record o) {
31 int v1 = (getMapIndex()<<16) + countryIndex;
32 int v2 = (o.getMapIndex()<<16) + o.countryIndex;
33 if (v1 < v2)
34 return -1;
35 else if (v1 > v2)
36 return 1;
37 else
38 return 0;
31 int d = Integer.compare(getMapIndex(), o.getMapIndex());
32 if (d == 0)
33 d = Integer.compare(countryIndex, o.countryIndex);
34 return d;
3935 }
4036
4137 public int getCountryIndex() {
4040 private final OutputStream stringFile;
4141 private int nextOffset;
4242
43 private Map<String, Integer> strings = new HashMap<String, Integer>();
43 private Map<String, Integer> strings = new HashMap<>();
4444 private final Charset charset;
4545 private final File tempFile;
4646
9393 * Close the temporary file, and release the string table which is no longer
9494 * needed.
9595 */
96 @Override
9697 public void releaseMemory() {
9798 strings = null;
9899 try {
103104 }
104105
105106 public void writeSectData(ImgFileWriter writer) {
106 FileInputStream stream = null;
107 try {
108 stream = new FileInputStream(tempFile);
107 try (FileInputStream stream = new FileInputStream(tempFile)) {
109108 FileChannel channel = stream.getChannel();
110109 ByteBuffer buf = ByteBuffer.allocate(32 * 1024);
111110 while (channel.read(buf) > 0) {
115114 }
116115 } catch (IOException e) {
117116 throw new ExitException("Could not write string section of index");
118 } finally {
119 Utils.closeFile(stream);
120117 }
121118 }
122119
130127 * offset possible. We are taking the total size of the string section
131128 * for this.
132129 */
130 @Override
133131 public int getSizeForRecord() {
134132 return Utils.numberToPointerSize(nextOffset);
135133 }
4848 * have a header with the length and the record size and prefix length of the
4949 * records in the subsection.
5050 */
51 private void writeSubSect(ImgFileWriter writer, PrefixIndex index) {
51 private static void writeSubSect(ImgFileWriter writer, PrefixIndex index) {
5252 index.preWrite();
5353 int len = index.getItemSize() * index.getNumberOfItems() + 2;
5454 if (len == 2)
7676 index.writeSectData(writer);
7777 }
7878
79 @Override
7980 protected void releaseMemory() {
8081 streets = null;
8182 cities = null;
2424 * @author Steve Ratcliffe
2525 */
2626 public class Mdr18 extends MdrSection implements HasHeaderFlags {
27 private List<Mdr18Record> poiTypes = new ArrayList<Mdr18Record>();
27 private List<Mdr18Record> poiTypes = new ArrayList<>();
2828
2929 public Mdr18(MdrConfig config) {
3030 setConfig(config);
3333 this.subWriter.position(subHeader.getHeaderLen());
3434 }
3535
36 public void startSection(int n) {
37 }
38
3936 public void endSection(int n) {
4037 int sn = sectionToSubsection(n);
4138 if (sn != 0)
4643 subWriter.putNu(pointerSize, recordNumber);
4744 }
4845
49 private int sectionToSubsection(int n) {
46 private static int sectionToSubsection(int n) {
5047 int sn;
5148 switch (n) {
5249 case 11: sn = 1; break;
1919 private Mdr1MapIndex mdrMapIndex;
2020 private int indexOffset;
2121
22 public Mdr1Record(int mapNumber, MdrConfig config) {
22 public Mdr1Record(int mapNumber) {
2323 this.mapNumber = mapNumber;
2424 }
2525
5050 if (n == 2)
5151 writer.put4(section.getPosition());
5252 else {
53 //section.writeSectionInfo(writer);
5453 writer.put4(section.getPosition());
5554 int size = section.getSize();
5655 if (size == 0)
2424 * @author Steve Ratcliffe
2525 */
2626 public class Mdr25 extends MdrSection {
27 private final List<Mdr5Record> cities = new ArrayList<Mdr5Record>();
27 private final List<Mdr5Record> cities = new ArrayList<>();
2828
2929 public Mdr25(MdrConfig config) {
3030 setConfig(config);
3737 public void sortCities(List<Mdr5Record> list) {
3838 Sort sort = getConfig().getSort();
3939
40 List<SortKey<Mdr5Record>> keys = new ArrayList<SortKey<Mdr5Record>>();
40 List<SortKey<Mdr5Record>> keys = new ArrayList<>();
4141 for (Mdr5Record c : list) {
4242 SortKey<Mdr5Record> key = sort.createSortKey(c, c.getMdrCountry().getName(), c.getGlobalCityIndex());
4343 keys.add(key);
2424 * @author Steve Ratcliffe
2525 */
2626 public class Mdr26 extends MdrSection {
27 private final List<Mdr28Record> index = new ArrayList<Mdr28Record>();
27 private final List<Mdr28Record> index = new ArrayList<>();
2828
2929 public Mdr26(MdrConfig config) {
3030 setConfig(config);
3333 public void sortMdr28(List<Mdr28Record> in) {
3434 Sort sort = getConfig().getSort();
3535
36 List<SortKey<Mdr28Record>> sortList = new ArrayList<SortKey<Mdr28Record>>();
36 List<SortKey<Mdr28Record>> sortList = new ArrayList<>();
3737 int record = 0;
3838 for (Mdr28Record mdr28 : in) {
3939 SortKey<Mdr28Record> key = sort.createSortKey(mdr28, mdr28.getMdr14().getName(), ++record);
2424 * @author Steve Ratcliffe
2525 */
2626 public class Mdr27 extends MdrSection {
27 private final List<Mdr5Record> cities = new ArrayList<Mdr5Record>();
27 private final List<Mdr5Record> cities = new ArrayList<>();
2828
2929 public Mdr27(MdrConfig config) {
3030 setConfig(config);
3737 public void sortCities(List<Mdr5Record> list) {
3838 Sort sort = getConfig().getSort();
3939
40 List<SortKey<Mdr5Record>> keys = new ArrayList<SortKey<Mdr5Record>>();
40 List<SortKey<Mdr5Record>> keys = new ArrayList<>();
4141 for (Mdr5Record c : list) {
4242 Mdr13Record mdrRegion = c.getMdrRegion();
4343 if (mdrRegion != null) {
2626 * @author Steve Ratcliffe
2727 */
2828 public class Mdr28 extends MdrSection implements HasHeaderFlags {
29 private final List<Mdr28Record> index = new ArrayList<Mdr28Record>();
29 private final List<Mdr28Record> index = new ArrayList<>();
3030
3131 public Mdr28(MdrConfig config) {
3232 setConfig(config);
1818 *
1919 * @author Steve Ratcliffe
2020 */
21 public class Mdr28Record extends ConfigBase {
21 public class Mdr28Record {
2222 private int index;
2323 private String name;
2424 private int mdr21;
5858 }
5959 }
6060
61 @Override
6162 protected void preWriteImpl() {
6263 if (!index.isEmpty()) {
6364 for (Mdr29Record r : index) {
4343 boolean writeLabel = (magic & HAS_LABEL) != 0; // A guess
4444 boolean writeNameOffset = (magic & HAS_NAME_OFFSET) != 0; // A guess, but less so
4545 int partialInfoSize = ((magic >> 3) & 0x7);
46 // int partialBShift = ((magic >> 6) & 0xf);
47 // int partialBMask = (1 << partialBShift) - 1;
4846
4947 int recordNumber = 0;
5048 for (Mdr7Record street : streets) {
6765
6866 if (partialInfoSize > 0) {
6967 int trailingFlags = ((rr & 1) == 0) ? 1 : 0;
70 // trailingFlags |= s.getB() << 1;
71 // trailingFlags |= s.getS() << (1 + partialBShift);
7268 writer.putNu(partialInfoSize, trailingFlags);
7369 }
7470 } else {
118114 return streets.size();
119115 }
120116
117 @Override
121118 protected void releaseMemory() {
122119 streets = null;
123120 }
2020
2121 /**
2222 * POI types. A simple list of the types that are used?
23 * If you have this section, then the ability to select POI categories
23 * If you don't have this section, then the ability to select POI categories
2424 * goes away.
2525 *
2626 * @author Steve Ratcliffe
2727 */
2828 public class Mdr4 extends MdrSection implements HasHeaderFlags {
29 private final Set<Mdr4Record> poiTypes = new HashSet<Mdr4Record>();
29 private final Set<Mdr4Record> poiTypes = new HashSet<>();
3030
3131 public Mdr4(MdrConfig config) {
3232 setConfig(config);
3434
3535
3636 public void writeSectData(ImgFileWriter writer) {
37 List<Mdr4Record> list = new ArrayList<Mdr4Record>(poiTypes);
37 List<Mdr4Record> list = new ArrayList<>(poiTypes);
3838 list.sort(null);
3939
4040 for (Mdr4Record r : list) {
4949 }
5050
5151 public void addType(int type) {
52 Mdr4Record r = new Mdr4Record();
53 if (type <= 0xff)
54 r.setType(type);
55 else {
56 r.setType((type >> 8) & 0xff);
57 r.setSubtype(type & 0xff);
58 }
59 r.setUnknown(0);
60
61 poiTypes.add(r);
52 poiTypes.add(new Mdr4Record((type >> 8) & 0xff, type & 0xff));
6253 }
6354
6455 /**
1616 * The records in MDR 4 are a list of poi types with an unknown byte.
1717 */
1818 public class Mdr4Record implements Comparable<Mdr4Record> {
19 private int type;
20 private int subtype;
19 private final int type;
20 private final int subtype;
2121 private int unknown;
2222
23 public Mdr4Record(int type, int subtype) {
24 this.type = type;
25 this.subtype = subtype;
26 }
27
2328 public int compareTo(Mdr4Record o) {
24 int t1 = ((type<<8) + subtype) & 0xffff;
25 int t2 = ((o.type<<8) + o.subtype) & 0xffff;
26 if (t1 == t2)
27 return 0;
28 else if (t1 < t2)
29 return -1;
30 else
31 return 1;
29 int t1 = ((type << 8) + subtype) & 0xffff;
30 int t2 = ((o.type << 8) + o.subtype) & 0xffff;
31 return Integer.compare(t1, t2);
3232 }
3333
3434 public boolean equals(Object o) {
3838 Mdr4Record that = (Mdr4Record) o;
3939
4040 if (subtype != that.subtype) return false;
41 if (type != that.type) return false;
42
43 return true;
41 return (type == that.type);
4442 }
4543
4644
5452 return type;
5553 }
5654
57 public void setType(int type) {
58 this.type = type;
59 }
60
6155 public int getSubtype() {
6256 return subtype;
63 }
64
65 public void setSubtype(int subtype) {
66 this.subtype = subtype;
6757 }
6858
6959 public int getUnknown() {
5151 /**
5252 * Called after all cities to sort and number them.
5353 */
54 @Override
5455 public void preWriteImpl() {
5556 localCitySize = Utils.numberToPointerSize(maxCityIndex + 1);
5657
281282 return val;
282283 }
283284
285 @Override
284286 protected void releaseMemory() {
285287 allCities = null;
286288 cities = null;
139139 st.setOutNameOffset((byte) outOffset);
140140 st.setPrefixOffset((byte) prefix);
141141 st.setSuffixOffset((byte) suffix);
142 //System.out.println(st.getName() + ": add partial " + st.getPartialName());
143142 if (!exclNames.contains(st.getPartialName()))
144143 storeMdr7(st);
145144
201200 * This is a performance critical part of the index creation process
202201 * as it requires a lot of heap to store the sort keys.
203202 */
203 @Override
204204 protected void preWriteImpl() {
205205
206206 LargeListSorter<Mdr7Record> partialSorter = new LargeListSorter<Mdr7Record>(sort) {
232232
233233 allStreets.trimToSize();
234234 streets.trimToSize();
235 return;
236235 }
237236
238237 /**
355354 return magic;
356355 }
357356
357 @Override
358358 protected void releaseMemory() {
359359 allStreets = null;
360360 streets = null;
168168 return false;
169169 if (suffixOffset != other.suffixOffset)
170170 return false;
171 if (city != other.getCity())
172 return false;
173 return true;
171 return (city == other.getCity());
174172 }
175173
176174 /**
183181 if (last == null)
184182 return 0;
185183 int res = 0;
186 String lastPartial = last.getPartialName();
187 String partial = getPartialName();
188 int cmp = collator.compare(lastPartial, partial);
184 int cmp = collator.compare(last.getPartialName(), getPartialName());
189185 if (cmp == 0)
190186 res = 1;
191187 res |= checkFullRepeat(last, collator);
202198 if (last == null)
203199 return 0;
204200 int res = 0;
205 String lastName = last.getName();
206 String name = getName();
207 int cmp = collator.compare(lastName, name);
201 int cmp = collator.compare(last.getName(), getName());
208202 if (cmp == 0)
209203 res |= 2;
210204 return res;
2424 public class Mdr8 extends MdrSection implements HasHeaderFlags {
2525 private static final int STRING_WIDTH = 4;
2626
27 private List<Mdr8Record> index = new ArrayList<Mdr8Record>();
27 private List<Mdr8Record> index = new ArrayList<>();
2828
2929 public Mdr8(MdrConfig config) {
3030 setConfig(config);
1616 *
1717 * @author Steve Ratcliffe
1818 */
19 public class Mdr8Record extends ConfigBase {
20 private char[] prefix;
21 private int recordNumber;
19 public class Mdr8Record {
20 private final char[] prefix;
21 private final int recordNumber;
22
23
24 public Mdr8Record(char[] prefix, int recordNumber) {
25 this.prefix = prefix;
26 this.recordNumber = recordNumber;
27 }
2228
2329 public char[] getPrefix() {
2430 return prefix;
2531 }
2632
27 public void setPrefix(char[] prefix) {
28 this.prefix = prefix;
29 }
30
3133 public int getRecordNumber() {
3234 return recordNumber;
3335 }
34
35 public void setRecordNumber(int recordNumber) {
36 this.recordNumber = recordNumber;
37 }
3836 }
2424 * @author Steve Ratcliffe
2525 */
2626 public class Mdr9 extends MdrSection implements HasHeaderFlags {
27 private final Map<Integer, Integer> index = new LinkedHashMap<Integer, Integer>();
27 private final Map<Integer, Integer> index = new LinkedHashMap<>();
2828
2929 public Mdr9(MdrConfig config) {
3030 setConfig(config);
3333 * @author Steve Ratcliffe
3434 */
3535 public class MdrConfig {
36 //private static final int DEFAULT_HEADER_LEN = 286;
3736 private static final int DEFAULT_HEADER_LEN = 568;
3837
3938 private boolean writable;
129128 }
130129
131130 public void setMdr7Excl(String exclList) {
132 mdr7Excl = StringToSet(exclList);
131 mdr7Excl = stringToSet(exclList);
133132 }
134133
135134 public Set<String> getMdr7Del() {
137136 }
138137
139138 public void setMdr7Del(String delList) {
140 mdr7Del = StringToSet(delList);
139 mdr7Del = stringToSet(delList);
141140 }
142141
143 private Set<String> StringToSet (String opt) {
142 private static Set<String> stringToSet (String opt) {
144143 Set<String> set;
145144
146145 if (opt == null)
151150 if (opt.endsWith("'") || opt.endsWith("\""))
152151 opt = opt.substring(0, opt.length() - 1);
153152 List<String> list = Arrays.asList(opt.split(","));
154 set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
153 set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
155154 for (String s : list) {
156155 set.add(s.trim());
157156 }
188187 * @param start first type
189188 * @param stop last type (included)
190189 */
191 private void genTypesForRange(Set<Integer> set, String start, String stop) {
190 private static void genTypesForRange(Set<Integer> set, String start, String stop) {
192191 GType[] types = new GType[2];
193192 String[] ranges = {start, stop};
194193 boolean ok = true;
210209 }
211210 for (int i = types[0].getType(); i <= types[1].getType(); i++) {
212211 if ((i & 0xff) > 0x1f)
213 i = ((i >> 8) + 1) << 8;
212 i = ((i >> 8) + 1) << 8;
214213 set.add(i);
215214 }
216215
5050 index.addPointer(mapNumber, recordNumber);
5151 }
5252
53 protected void putCityIndex(ImgFileWriter writer, int cityIndex, boolean isNew) {
54 int flag = (isNew && cityIndex > 0)? getSizes().getCityFlag(): 0;
53 protected void putCityIndex(ImgFileWriter writer, int cityIndex) {
54 int flag = cityIndex > 0 ? getSizes().getCityFlag() : 0;
5555 writer.putNu(getSizes().getCitySizeFlagged(), cityIndex | flag);
5656 }
5757
196196 return sections[20].getSizeForRecord();
197197 }
198198
199 private int flagForSize(int size) {
199 private static int flagForSize(int size) {
200200 int flag;
201201 if (size == 1)
202202 flag = 0x80;
1414 import java.util.ArrayList;
1515 import java.util.List;
1616
17 import uk.me.parabola.mkgmap.general.MapPoint;
1718 import uk.me.parabola.imgfmt.app.srt.Sort;
1819 import uk.me.parabola.imgfmt.app.srt.SortKey;
1920
2627 public static final int POI_INDEX_PREFIX_LEN = 4;
2728 public static final int MAX_GROUP = 13;
2829
30 private MdrUtils () {
31 // private constructor to hide the implicit public one.
32 }
33
2934 /**
3035 * Get the group number for the poi. This is the first byte of the records
3136 * in mdr9.
3237 *
3338 * Not entirely sure about how this works yet.
3439 * @param fullType The primary type of the object.
35 * @return The group number. This is a number between 1 and 9 (and later
36 * perhaps higher numbers such as 0x40, so do not assume there are no
37 * gaps).
38 * Group / Filed under
39 * 1 Cities
40 * 2 Food & Drink
41 * 3 Lodging
42 * 4-5 Recreation / Entertainment / Attractions
43 * 6 Shopping
44 * 7 Auto Services
45 * 8 Community
46 * 9 ?
47 *
40 * @return The group number. This is a number between 1 and MAX_GROUP, later
41 * might be as high as 0x40, so do not assume there are no gaps.
42 *
43 * Group Type Filed under
44 * 1 Cities (actual range defined by isCityType)
45 * 2 0x2a Food and Drink
46 * 3 0x2b Lodgings
47 * 4 0x2c Attractions/Recreation/Community
48 * 5 0x2d Entertainment/Recreation
49 * 6 0x2e Shopping
50 * 7 0x2f Auto/Transport/Community/Other
51 * 8 0x30 Civic
52 * 9 0x28 Island. Reason for having this is no longer known
53 * 10 unused
54 * 11 0x64 Geographic > Manmade Places
55 * 12 0x65 Geographic > Water Features
56 * 13 0x66 Geographic > Land Features
57 *
58 * display MdrCheck.java:toType() needs to be in-step with this
4859 */
4960 public static int getGroupForPoi(int fullType) {
5061 // We group pois based on their type. This may not be the final thoughts on this.
5162 int type = getTypeFromFullType(fullType);
5263 int group = 0;
53 if (fullType <= 0xf)
64 if (MapPoint.isCityType(fullType))
5465 group = 1;
55 else if (type >= 0x2a && type <= 0x30) {
56 group = type - 0x28;
57 } else if (type == 0x28) {
66 else if (type >= 0x2a && type <= 0x30)
67 group = type - 0x2a + 2;
68 else if (type == 0x28)
5869 group = 9;
59 } else if (type >= 0x64 && type <= 0x66) {
60 group = type - 0x59;
61 }
70 else if (type >= 0x64 && type <= 0x66)
71 group = type - 0x64 + 11;
6272 assert group >= 0 && group <= MAX_GROUP : "invalid group " + Integer.toHexString(group);
6373 return group;
6474 }
6878 }
6979
7080 public static int getTypeFromFullType(int fullType) {
71 if ((fullType & 0xfff00) > 0)
72 return (fullType>>8) & 0xfff;
73 else
74 return fullType & 0xff;
81 return (fullType>>8) & 0xfff;
7582 }
7683
7784 /**
8087 * @return If there is a subtype, then it is returned, else 0.
8188 */
8289 public static int getSubtypeFromFullType(int fullType) {
83 return fullType < 0xff ? 0 : fullType & 0xff;
90 return fullType & 0xff;
8491 }
8592
8693 /**
113120 */
114121 public static int fullTypeToNaturalType(int ftype) {
115122 int type = getTypeFromFullType(ftype);
116 int sub = 0;
117 if ((ftype & ~0xff) != 0)
118 sub = ftype & 0x1f;
119
123 int sub = getSubtypeFromFullType(ftype);
124 assert sub <= 0x1f: "Subtype doesn't fit into 5 bits: " + uk.me.parabola.mkgmap.reader.osm.GType.formatType(ftype);
120125 return type << 5 | sub;
121126 }
122127 }
1616 * Marks a record that has a name.
1717 */
1818 public interface NamedRecord {
19 public int getMapIndex();
20 public String getName();
19 int getMapIndex();
20 String getName();
2121 }
5151 /**
5252 * We can create an index for any type that has a name.
5353 * @param list A list of items that have a name.
54 * @param grouped used with MDR7 records
5455 */
5556 public void createFromList(List<? extends NamedRecord> list, boolean grouped) {
5657 maxIndex = list.size();
8485 int cmp = collator.compareOneStrengthWithLength(prefix, lastPrefix, Collator.PRIMARY, prefixLength);
8586 if (cmp > 0) {
8687 outRecord++;
87 Mdr8Record ind = new Mdr8Record();
88 ind.setPrefix(prefix);
89 ind.setRecordNumber(inRecord);
90 index.add(ind);
88 index.add(new Mdr8Record(prefix, inRecord));
9189
9290 lastPrefix = prefix;
9391
106104 }
107105 }
108106
107 /**
108 * We can create an index for any type that has a name.
109 * @param list A list of items that have a name.
110 */
109111 public void createFromList(List<? extends NamedRecord> list) {
110112 createFromList(list, false);
111113 }
1616 package uk.me.parabola.imgfmt.app.net;
1717
1818 import java.util.ArrayList;
19 import java.util.BitSet;
20 import java.util.Comparator;
1921 import java.util.HashMap;
2022 import java.util.List;
2123 import java.util.Map;
2628 import uk.me.parabola.imgfmt.app.ImgFileWriter;
2729 import uk.me.parabola.imgfmt.app.Label;
2830 import uk.me.parabola.imgfmt.app.lbl.City;
31 import uk.me.parabola.imgfmt.app.lbl.Zip;
2932 import uk.me.parabola.imgfmt.app.srt.DoubleSortKey;
3033 import uk.me.parabola.imgfmt.app.srt.IntegerSortKey;
3134 import uk.me.parabola.imgfmt.app.srt.MultiSortKey;
8184
8285 ImgFileWriter writer = netHeader.makeSortedRoadWriter(getWriter());
8386 try {
84 List<LabeledRoadDef> labeledRoadDefs = sortRoads();
87 List<LabeledRoadDef> labeledRoadDefs = deDupRoads();
88 sortByName(labeledRoadDefs);
8589 for (LabeledRoadDef labeledRoadDef : labeledRoadDefs)
8690 labeledRoadDef.roadDef.putSortedRoadEntry(writer, labeledRoadDef.label);
8791 } finally {
9397
9498 /**
9599 * Sort the roads by name and remove duplicates.
96 *
97 * We want a list of roads such that every entry in the list is a different road. Since in osm
98 * roads are frequently chopped into small pieces we have to remove the duplicates.
99 * This doesn't have to be perfect, it needs to be useful when searching for roads.
100 *
101 * So we have a separate entry if the road is in a different city. This would probably be enough
102 * except that associating streets with cities is not always very good in OSM. So I also create an
103 * extra entry for each subdivision. Finally there a search for disconnected roads within the subdivision
104 * with the same name.
105 *
106 * Performance note: The previous implementation was very, very slow when there were a large number
107 * of roads with the same name. Although this was an unusual situation, when it happened it appears
108 * that mkgmap has hung. This implementation takes a fraction of a second even for large numbers of
109 * same named roads.
110 *
111 * @return A sorted list of road labels that identify all the different roads.
112 */
113 private List<LabeledRoadDef> sortRoads() {
100 * <p>
101 * We want a list of roads such that every entry in the list is a different
102 * road. In some areas we have multiple roads with the same name, typically
103 * connected to each other. For each group we find networks of connected
104 * roads. We must store all roads with house numbers, else some numbers are
105 * not found. For networks without any road with numbers we store only one
106 * to reduce NET size.
107 * <p>
108 * Special case: With OSM data and certain styles all normally unnamed roads
109 * get a name describing the type of road, e.g. all ways with tag
110 * highway=footway get the name "fw". This can produce large groups of roads
111 * with equal names.
112 *
113 * @return A list of road labels that identify all the different roads
114 */
115 private List<LabeledRoadDef> deDupRoads() {
116 List<SortKey<LabeledRoadDef>> sortKeys = createSortKeysyNameAndCity();
117 sortKeys.sort(null);
118
119 List<LabeledRoadDef> out = new ArrayList<>(sortKeys.size());
120
121 List<LabeledRoadDef> dupes = new ArrayList<>();
122 SortKey<LabeledRoadDef> lastKey = null;
123
124 // Since they are sorted we can easily remove the duplicates.
125 // The duplicates are saved to the dupes list.
126 for (SortKey<LabeledRoadDef> key : sortKeys) {
127 if (lastKey == null || key.compareTo(lastKey) != 0) {
128 analyseRoadsOfCity(dupes, out);
129 dupes.clear();
130 lastKey = key;
131 }
132 dupes.add(key.getObject());
133 }
134 // Finish off the final set of duplicates.
135 analyseRoadsOfCity(dupes, out);
136 return out;
137 }
138
139 private List<SortKey<LabeledRoadDef>> createSortKeysyNameAndCity() {
114140 List<SortKey<LabeledRoadDef>> sortKeys = new ArrayList<>(roads.size());
115 Map<Label, byte[]> cache = new HashMap<>();
116141
117142 for (RoadDef rd : roads) {
118143 Label[] labels = rd.getLabels();
124149 // Sort by name, city, region/country and subdivision number.
125150 LabeledRoadDef lrd = new LabeledRoadDef(label, rd);
126151 SortKey<LabeledRoadDef> nameKey = new IntegerSortKey<>(lrd, label.getOffset(), 0);
152
127153 // If there is a city add it to the sort.
128 City city = (rd.getCities().isEmpty() ? null : rd.getCities().get(0)); // what if we more than one?
154 City city = (rd.getCities().isEmpty() ? null : rd.getCities().get(0));
129155 SortKey<LabeledRoadDef> cityKey;
130156 if (city != null) {
131157 int region = city.getRegionNumber();
132158 int country = city.getCountryNumber();
133 cityKey = sort.createSortKey(null, city.getLabel(), (region & 0xffff) << 16 | (country & 0xffff),
134 cache);
159 cityKey = sort.createSortKey(null, city.getLabel(), (region & 0xffff) << 16 | (country & 0xffff));
135160 } else {
136 cityKey = sort.createSortKey(null, Label.NULL_OUT_LABEL, 0, cache);
161 cityKey = sort.createSortKey(null, Label.NULL_OUT_LABEL, 0);
137162 }
138163
139 SortKey<LabeledRoadDef> sortKey = new MultiSortKey<>(nameKey, cityKey,
140 new IntegerSortKey<LabeledRoadDef>(null, rd.getStartSubdivNumber(), 0));
141 sortKeys.add(sortKey);
142 }
143 }
144
145 sortKeys.sort(null);
146
147 List<LabeledRoadDef> out = new ArrayList<>(sortKeys.size());
148
149 Label lastName = null;
150 City lastCity = null;
151 List<LabeledRoadDef> dupes = new ArrayList<>();
152
153 // Since they are sorted we can easily remove the duplicates.
154 // The duplicates are saved to the dupes list.
155 for (SortKey<LabeledRoadDef> key : sortKeys) {
156 LabeledRoadDef lrd = key.getObject();
157
158 Label name = lrd.label;
159 RoadDef road = lrd.roadDef;
160 City city = (road.getCities().isEmpty() ? null : road.getCities().get(0)); // what if we more than one?
161
162 if (road.hasHouseNumbers() || !name.equals(lastName) || city != lastCity) {
163
164 // process any previously collected duplicate road names and reset.
165 addDisconnected(dupes, out);
166 dupes = new ArrayList<>();
167
168 lastName = name;
169 lastCity = city;
170 }
171 dupes.add(lrd);
172 }
173
174 // Finish off the final set of duplicates.
175 addDisconnected(dupes, out);
176 sortByName(out);
177 return out;
164 // If there is a zip code add it to the sort.
165 Zip zip = (rd.getZips().isEmpty() ? null : rd.getZips().get(0));
166 Label zipLabel = zip == null ? Label.NULL_OUT_LABEL: zip.getLabel();
167 SortKey<LabeledRoadDef> zipKey = sort.createSortKey(null, zipLabel);
168
169 sortKeys.add(new MultiSortKey<>(nameKey, cityKey, zipKey));
170 }
171 }
172 return sortKeys;
178173 }
179174
180175 /**
205200 * @param in A list of duplicate roads.
206201 * @param out The list of sorted roads. Any new road is added to this.
207202 */
208 private void addDisconnected(List<LabeledRoadDef> in, List<LabeledRoadDef> out) {
203 private static void analyseRoadsOfCity(List<LabeledRoadDef> in, List<LabeledRoadDef> out) {
209204 // switch out to different routines depending on the input size. A normal number of
210205 // roads with the same name in the same city is a few tens.
211206 if (in.size() > 200) {
212 addDisconnectedLarge(in, out);
207 analyseRoadsOfCityLarge(in, out);
213208 } else {
214 addDisconnectedSmall(in, out);
209 analyseRoadsOfCitySmall(in, out);
215210 }
216211 }
217212
221216 * This is done in an accurate manner which is slow for large numbers (eg thousands) of items in the
222217 * input.
223218 *
219 * @param in Input set of roads with the same name and city.
220 * @param out List to add the discovered groups.
221 */
222 private static void analyseRoadsOfCitySmall(List<LabeledRoadDef> in, List<LabeledRoadDef> out) {
223 if (in.size() < 2) {
224 out.addAll(in);
225 } else {
226 // sort so that roads with numbers and those with more than one city appear first
227 in.sort((o1, o2) -> Boolean.compare(needed(o2), needed(o1)));
228
229 // write all roads with numbers or multiple cities so that they are found in address search
230 int posOther = -1;
231 for (int i = 0; i < in.size(); i++) {
232 if (needed(in.get(i))) {
233 out.add(in.get(i));
234 } else {
235 posOther = i;
236 break;
237 }
238 }
239 if (posOther >= 0) {
240 findRoadNetworks(in, posOther, out);
241 }
242 }
243 }
244
245 /**
246 * Split the input set of roads into disconnected groups and output one member from each group.
247 *
248 * This is an modified algorithm for large numbers in the input set (eg thousands).
249 * First sort into groups by subdivision and then call {@link #analyseRoadsOfCitySmall} on each
250 * one. Since roads in the same subdivision are near each other this finds most connected roads, but
251 * since there is a maximum number of roads in a subdivision, the test can be done very quickly.
252 * You will get a few extra duplicate entries in the index.
253 *
254 * In normal cases this routine gives almost the same results as {@link #analyseRoadsOfCitySmall}.
255 *
224256 * @param in Input set of roads with the same name.
225257 * @param out List to add the discovered groups.
226258 */
227 private void addDisconnectedSmall(List<LabeledRoadDef> in, List<LabeledRoadDef> out) {
259 private static void analyseRoadsOfCityLarge(List<LabeledRoadDef> in, List<LabeledRoadDef> out) {
260 in.sort(Comparator.comparingInt(lr -> lr.roadDef.getStartSubdivNumber()));
261
262 int lastDiv = 0;
263 List<LabeledRoadDef> dupes = new ArrayList<>();
264 for (LabeledRoadDef lrd : in) {
265 int sd = lrd.roadDef.getStartSubdivNumber();
266 if (sd != lastDiv) {
267 analyseRoadsOfCitySmall(dupes, out);
268 dupes.clear();
269 lastDiv = sd;
270 }
271 dupes.add(lrd);
272 }
273 // the rest
274 analyseRoadsOfCitySmall(dupes, out);
275 }
276
277 private static boolean needed (LabeledRoadDef lr) {
278 return lr.roadDef.hasHouseNumbers() || lr.roadDef.getCities().size() > 1;
279 }
280
281 /**
282 * Find road networks which are not connected to the roads with numbers, write one of each.
283 * @param in Input set of roads with the same name, sorted so that roads with numbers appear first
284 * @param posOther position of first road without numbers or multiple cities in input
285 * @param out List to add the discovered groups with roads not connected to the roads with numbers
286 */
287 private static void findRoadNetworks(List<LabeledRoadDef> in, int posOther, List<LabeledRoadDef> out) {
228288 // Each road starts out with a different group number
229 int[] groups = new int[in.size()];
289 int inSize = in.size();
290 int[] groups = new int[inSize];
230291 for (int i = 0; i < groups.length; i++)
231292 groups[i] = i;
232293
294 // cache for results of RoadDef#connectedTo(RoadDef) where result was false.
295 BitSet unconnected = new BitSet(inSize * inSize);
296
233297 // Go through pairs of roads, any that are connected we mark with the same (lowest) group number.
234298 boolean done;
235299 do {
236300 done = true;
237301 for (int current = 0; current < groups.length; current++) {
238302 RoadDef first = in.get(current).roadDef;
239
240 for (int i = current; i < groups.length; i++) {
241 // If the groups are already the same, then no need to test
242 if (groups[current] == groups[i])
243 continue;
244
245 if (first.connectedTo(in.get(i).roadDef)) {
246 groups[current] = groups[i] = Math.min(groups[current], groups[i]);
247 done = false;
303
304 for (int i = current + 1; i < groups.length; i++) {
305 // If the groups are already the same or roads are known to be unconnected, then no need to test
306 if (groups[current] != groups[i] && !unconnected.get(current * inSize + i)) {
307 // we have to do the costly connectedTo() test
308 if (first.connectedTo(in.get(i).roadDef)) {
309 groups[current] = groups[i] = Math.min(groups[current], groups[i]);
310 done = false;
311 } else {
312 unconnected.set(current * inSize + i);
313 }
248314 }
249315 }
250316 }
251317 } while (!done);
252318
253 // Output the first road in each group
254 int last = -1;
255 for (int i = 0; i < groups.length; i++) {
319 // Output the first road in each group that was not yet added
320 int last = posOther - 1;
321 for (int i = posOther; i < groups.length; i++) {
256322 if (groups[i] > last) {
257323 LabeledRoadDef lrd = in.get(i);
258324 out.add(lrd);
261327 }
262328 }
263329
264 /**
265 * Split the input set of roads into disconnected groups and output one member from each group.
266 *
267 * This is an modified algorithm for large numbers in the input set (eg thousands).
268 * First sort into groups by subdivision and then call {@link #addDisconnectedSmall} on each
269 * one. Since roads in the same subdivision are near each other this finds most connected roads, but
270 * since there is a maximum number of roads in a subdivision, the test can be done very quickly.
271 * You will get a few extra duplicate entries in the index.
272 *
273 * In normal cases this routine gives almost the same results as {@link #addDisconnectedSmall}.
274 *
275 * @param in Input set of roads with the same name.
276 * @param out List to add the discovered groups.
277 */
278 private void addDisconnectedLarge(List<LabeledRoadDef> in, List<LabeledRoadDef> out) {
279 in.sort((o1, o2) -> Integer.compare(o1.roadDef.getStartSubdivNumber(), o2.roadDef.getStartSubdivNumber()));
280
281 int lastDiv = 0;
282 List<LabeledRoadDef> dupes = new ArrayList<>();
283 for (LabeledRoadDef lrd : in) {
284 int sd = lrd.roadDef.getStartSubdivNumber();
285 if (sd != lastDiv) {
286 addDisconnectedSmall(dupes, out);
287 dupes = new ArrayList<>();
288 lastDiv = sd;
289 }
290 dupes.add(lrd);
291 }
292
293 addDisconnectedSmall(dupes, out);
294 }
295
296330 public void setNetwork(List<RoadDef> roads) {
297331 this.roads = roads;
298332 }
5151
5252 private final NODHeader nodHeader = new NODHeader();
5353
54 private List<RouteCenter> centers = new ArrayList<RouteCenter>();
55 private List<RoadDef> roads = new ArrayList<RoadDef>();
56 private List<RouteNode> boundary = new ArrayList<RouteNode>();
54 private List<RouteCenter> centers = new ArrayList<>();
55 private List<RoadDef> roads = new ArrayList<>();
56 private List<RouteNode> boundaryNodes = new ArrayList<>();
5757
5858 public NODFile(ImgChannel chan, boolean write) {
5959 setHeader(nodHeader);
133133 private void writeBoundary() {
134134 log.info("writeBoundary");
135135
136 boundary.sort(null);
137
138136 ImgFileWriter writer = new SectionWriter(getWriter(), nodHeader.getBoundarySection());
139137
140138 boolean debug = log.isDebugEnabled();
141 for (RouteNode node : boundary) {
139 for (RouteNode node : boundaryNodes) {
142140 if(debug)
143141 log.debug("wrting nod3", writer.position());
144142 node.writeNod3OrNod4(writer);
165163 ImgFileWriter writer = new SectionWriter(getWriter(), section);
166164
167165 boolean debug = log.isDebugEnabled();
168 for (RouteNode node : boundary) {
166 for (RouteNode node : boundaryNodes) {
169167 if (node.getNodeClass() == 0)
170168 continue;
171169 if(debug)
180178 public void setNetwork(List<RouteCenter> centers, List<RoadDef> roads, List<RouteNode> boundary) {
181179 this.centers = centers;
182180 this.roads = roads;
183 this.boundary = boundary;
181 this.boundaryNodes = new ArrayList<>(boundary);
182 this.boundaryNodes.sort(null);
184183 }
185184
186185 public void setDriveOnLeft(boolean dol) {
1212 package uk.me.parabola.imgfmt.app.net;
1313
1414 import java.io.ByteArrayOutputStream;
15 import java.util.ArrayList;
1516 import java.util.List;
1617
1718 import uk.me.parabola.imgfmt.Utils;
4849 CityZipWriter cityWriter;
4950
5051 public NumberPreparer(List<Numbers> numbers) {
51 this.numbers = numbers;
52 this.numbers = new ArrayList<>(numbers);
5253 this.zipWriter = new CityZipWriter("zip", 0, 0);
5354 this.cityWriter = new CityZipWriter("city", 0, 0);
5455 }
157158 int lastNode = -1;
158159 for (Numbers n : numbers) {
159160 if (!n.hasIndex())
160 throw new Abandon("no r node set");
161 throw new Abandon("no index set");
161162 // See if we need to skip some nodes
162163 if (n.getIndex() != lastNode + 1)
163164 state.writeSkip(bw, n.getIndex() - lastNode - 2);
1111 */
1212 package uk.me.parabola.imgfmt.app.net;
1313
14 import java.util.Objects;
15
1416 import uk.me.parabola.log.Logger;
1517 import uk.me.parabola.mkgmap.general.CityInfo;
1618 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
2628
2729 private static final int MAX_DELTA = 131071; // see NumberPreparer
2830
29 // The node in the road where these numbers apply. In the polish notation it is the
30 // node in the road, whereas in the NET file it is the index of the number node.
31 private int nodeNumber; // node in road index
32 private Integer indexNumber; // the position in the list of Numbers (starting with 0)
33 // the data on side of the road
31 /**
32 * Polish format: The point in the road where these numbers apply. Not written to NET!
33 */
34 private int polishPointIndex;
35 /**
36 * The position in the list of nodes (starting with 0). Negative value means it is not yet set.
37 * Identifies the node (not point) in the road. This is needed to encode the bitstream in NET.
38 */
39 private int nodeIndex = -1;
40
41 /** the data on side of the road */
3442 private RoadSide leftSide,rightSide;
3543
3644 private class RoadSide {
4553
4654 private class NumDesc{
4755 NumberStyle numberStyle;
48 int start,end;
56 int start, end;
4957
5058 public NumDesc(NumberStyle numberStyle, int start, int end) {
5159 this.numberStyle = numberStyle;
5260 this.start = start;
5361 this.end = end;
5462 }
55 public boolean contained(int hn){
63
64 public boolean contained(int hn) {
5665 boolean isEven = (hn % 2 == 0);
57 if (numberStyle == NumberStyle.BOTH
66 if (numberStyle == NumberStyle.BOTH
5867 || numberStyle == NumberStyle.EVEN && isEven
59 || numberStyle == NumberStyle.ODD && !isEven){
68 || numberStyle == NumberStyle.ODD && !isEven) {
6069 if (start <= end) {
61 if (start <= hn && hn <= end)
62 return true;
63 }
64 else {
65 if (end <= hn && hn <= start)
66 return true;
70 return start <= hn && hn <= end;
71 } else {
72 return end <= hn && hn <= start;
6773 }
6874 }
6975 return false;
8793 */
8894 public Numbers(String spec) {
8995 String[] strings = spec.split(",");
90 nodeNumber = Integer.valueOf(strings[0]);
96 polishPointIndex = Integer.parseInt(strings[0]);
9197 NumberStyle numberStyle = NumberStyle.fromChar(strings[1]);
92 int start = Integer.valueOf(strings[2]);
93 int end = Integer.valueOf(strings[3]);
98 int start = Integer.parseInt(strings[2]);
99 int end = Integer.parseInt(strings[3]);
94100 setNumbers(LEFT, numberStyle, start, end);
95101 numberStyle = NumberStyle.fromChar(strings[4]);
96 start = Integer.valueOf(strings[5]);
97 end = Integer.valueOf(strings[6]);
102 start = Integer.parseInt(strings[5]);
103 end = Integer.parseInt(strings[6]);
98104 setNumbers(RIGHT, numberStyle, start, end);
99105
100106 if (strings.length > 8){
101107 // zip codes
102108 String zip = strings[7];
103 if ("-1".equals(zip) == false)
109 if (!"-1".equals(zip))
104110 setZipCode(LEFT, new ZipCodeInfo(zip));
105111 zip = strings[8];
106 if ("-1".equals(zip) == false)
112 if (!"-1".equals(zip))
107113 setZipCode(RIGHT, new ZipCodeInfo(zip));
108114 }
109 if (strings.length > 9){
110 String city,region,country;
115 if (strings.length > 9) {
116 String city, region, country;
111117 int nextPos = 9;
112118 city = strings[nextPos];
113 if ("-1".equals(city) == false){
119 if (!"-1".equals(city)) {
114120 region = strings[nextPos + 1];
115121 country = strings[nextPos + 2];
116122 setCityInfo(LEFT, new CityInfo(city, region, country));
117123 nextPos = 12;
118 } else
124 } else {
119125 nextPos = 10;
126 }
120127 city = strings[nextPos];
121 if ("-1".equals(city) == false){
128 if (!"-1".equals(city)) {
122129 region = strings[nextPos + 1];
123130 country = strings[nextPos + 2];
124131 setCityInfo(RIGHT, new CityInfo(city, region, country));
125 }
126 }
127 }
128
129 public void setNumbers(boolean left, NumberStyle numberStyle, int start, int end){
132 }
133 }
134 }
135
136 public void setNumbers(boolean useLeft, NumberStyle numberStyle, int start, int end){
130137 if (numberStyle != NumberStyle.NONE || start != -1 || end != -1){
131 RoadSide rs = assureSideIsAllocated(left);
138 RoadSide rs = assureSideIsAllocated(useLeft);
132139 rs.numbers = new NumDesc(numberStyle, start, end);
133140 } else {
134 RoadSide rs = (left) ? leftSide : rightSide;
141 RoadSide rs = (useLeft) ? leftSide : rightSide;
135142 if (rs != null)
136143 rs.numbers = null;
137 removeIfEmpty(left);
144 removeIfEmpty(useLeft);
138145 }
139146 }
140147
189196 return (left) ? leftSide : rightSide;
190197 }
191198
192 public int getNodeNumber() {
193 return nodeNumber;
194 }
195
196 public void setNodeNumber(int nodeNumber) {
197 this.nodeNumber = nodeNumber;
198 }
199
199 public int getPolishIndex() {
200 return polishPointIndex;
201 }
202
203 public void setPolishIndex(int n) {
204 this.polishPointIndex = n;
205 }
206
207 /**
208 * @return The index of the nth number node where these numbers apply.
209 */
200210 public int getIndex() {
201 if (indexNumber == null) {
202 log.error("WARNING: index not set!!");
203 return nodeNumber;
204 }
205 return indexNumber;
211 if (nodeIndex < 0) {
212 log.error("WARNING: node index not set!!");
213 return 0;
214 }
215 return nodeIndex;
206216 }
207217
208218 public boolean hasIndex() {
209 return indexNumber != null;
219 return nodeIndex >= 0;
210220 }
211221
212222 /**
213223 * @param index the nth number node
214224 */
215225 public void setIndex(int index) {
216 this.indexNumber = index;
226 this.nodeIndex = index;
217227 }
218228
219229 private NumDesc getNumbers(boolean left) {
238248
239249 public String toString() {
240250 String nodeStr = "0";
241 if (nodeNumber > 0)
242 nodeStr = String.valueOf(nodeNumber);
251 if (polishPointIndex > 0)
252 nodeStr = Integer.toString(polishPointIndex);
243253 else if (getIndex() > 0)
244254 nodeStr = String.format("(n%d)", getIndex());
245255
300310 return false;
301311 if (!isPlausible(getRightNumberStyle(), getRightStart(), getRightEnd()))
302312 return false;
303 if (getLeftNumberStyle() == NumberStyle.NONE
304 || getRightNumberStyle() == NumberStyle.NONE)
313 if (getLeftNumberStyle() == NumberStyle.NONE || getRightNumberStyle() == NumberStyle.NONE) {
314 return true; // no need to compare values of road sides
315 }
316 if (!Objects.equals(getCityInfo(LEFT), getCityInfo(RIGHT))) {
305317 return true;
306 if (getCityInfo(LEFT) != null){
307 if (getCityInfo(LEFT).equals(getCityInfo(RIGHT)) == false)
308 return true;
309 } else if (getCityInfo(RIGHT) != null)
318 }
319 if (!Objects.equals(getZipCodeInfo(LEFT), getZipCodeInfo(RIGHT))) {
310320 return true;
311 if (getZipCodeInfo(LEFT) != null){
312 if (getZipCodeInfo(LEFT).equals(getZipCodeInfo(RIGHT)) == false)
313 return true;
314 } else if (getCityInfo(RIGHT) != null)
315 return true;
316 if (getLeftNumberStyle() == getRightNumberStyle() || getLeftNumberStyle() == NumberStyle.BOTH || getRightNumberStyle()==NumberStyle.BOTH){
321 }
322 // city and zip codes say that the intervals of numbers should not overlap
323 if (getLeftNumberStyle() == getRightNumberStyle()
324 || getLeftNumberStyle() == NumberStyle.BOTH
325 || getRightNumberStyle() == NumberStyle.BOTH) {
317326 // check if intervals are overlapping
318 int start1, start2,end1,end2;
319 if (getLeftStart() < getLeftEnd()){
327 int start1, start2, end1, end2;
328 if (getLeftStart() < getLeftEnd()) {
320329 start1 = getLeftStart();
321330 end1 = getLeftEnd();
322331 } else {
323332 start1 = getLeftEnd();
324333 end1 = getLeftStart();
325334 }
326 if (getRightStart() < getRightEnd()){
335 if (getRightStart() < getRightEnd()) {
327336 start2 = getRightStart();
328337 end2 = getRightEnd();
329338 } else {
332341 }
333342 if (start2 > end1 || end2 < start1)
334343 return true;
335 if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd() && getLeftStart() == getRightStart())
336 return true; // single number on both sides of the road
337
338 return false;
344 // single number on both sides of the road?
345 return getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd()
346 && getLeftStart() == getRightStart();
339347 }
340348
341349 return true;
351359 return true;
352360 }
353361
354 public boolean isContained(int hn, boolean left){
355 RoadSide rs = left ? leftSide : rightSide;
362 public boolean isContained(int hn, boolean useLeft){
363 RoadSide rs = useLeft ? leftSide : rightSide;
356364 if (rs == null || rs.numbers == null)
357365 return false;
358366 return rs.numbers.contained(hn);
369377 matches++;
370378 if (isContained(hn, RIGHT))
371379 matches++;
372 if (matches > 1){
373 if (getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd())
374 matches = 1; // single number on both sides of the road
380 if (matches > 1 && getLeftStart() == getLeftEnd() && getRightStart() == getRightEnd()) {
381 matches = 1; // single number on both sides of the road
375382 }
376383 return matches;
377384 }
384391 public boolean isSimilar(Numbers other){
385392 if (other == null)
386393 return false;
387 if (getLeftNumberStyle() != other.getLeftNumberStyle()
388 || getLeftStart() != other.getLeftStart() || getLeftEnd() != other.getLeftEnd()
394 return !(getLeftNumberStyle() != other.getLeftNumberStyle()
395 || getLeftStart() != other.getLeftStart()
396 || getLeftEnd() != other.getLeftEnd()
389397 || getRightNumberStyle() != other.getRightNumberStyle()
390 || getRightStart() != other.getRightStart() || getRightEnd() != other.getRightEnd())
391 return false;
392 return true;
398 || getRightStart() != other.getRightStart()
399 || getRightEnd() != other.getRightEnd());
393400
394401 }
395402
152152 // for diagnostic purposes
153153 private final long id;
154154 private final String name;
155
156 // road/address search data from (house) numbers
155157 private List<Numbers> numbersList;
156158 private List<City> cityList;
157159 private List<Zip> zipList;
158 private int nodeCount;
159
160
161 /** cumulative number of special nodes between first and last node of line segments */
162 private int nodeCountInner;
163
164 private int lenInMeter;
165
160166 public RoadDef(long id, String name) {
161167 this.id = id;
162168 this.name = name;
178184 // for diagnostic purposes
179185 public String toString() {
180186 // assumes id is an OSM id
181 String browseURL = "http://www.openstreetmap.org/browse/way/" + id;
182 //if(getName() != null)
183 // return "(" + getName() + ", " + browseURL + ")";
184 //else
185 return "(" + browseURL + ")";
187 return "(" + "http://www.openstreetmap.org/browse/way/" + id + ")";
186188 }
187189
188190 public String getName() {
230232 writeLevelDivs(writer, maxlevel);
231233
232234 if((netFlags & NET_FLAG_ADDRINFO) != 0) {
233 nodeCount--;
234 if (nodeCount + 2 != nnodes){
235 log.error("internal error? The nodeCount doesn't match value calculated by RoadNetWork:",this);
236 }
237 writer.put1u(nodeCount & 0xff); // lo bits of node count
238
239 int code = (nodeCount >> 8) & 0x3; // top bits of node count
235 if (!hasHouseNumbers() && skipAddToNOD()) {
236 // no need to write node info (decreases bitstream length)
237 nodeCountInner = 0;
238 }
239 writer.put1u(nodeCountInner & 0xff); // lo bits of node count
240
241 int code = (nodeCountInner >> 8) & 0x3; // top bits of node count
240242 int len, flag;
241243
242244 ByteArrayOutputStream zipBuf = null, cityBuf = null;
244246 if (len > 0){
245247 zipBuf = numbers.zipWriter.getBuffer();
246248 flag = Utils.numberToPointerSize(len) - 1;
247 } else
249 } else {
248250 flag = (zip == null) ? 3 : 2;
251 }
249252 code |= flag << 2;
250253
251254 len = (numbers == null) ? 0: numbers.cityWriter.getBuffer().size();
252255 if (len > 0){
253256 cityBuf = numbers.cityWriter.getBuffer();
254257 flag = Utils.numberToPointerSize(len) - 1;
255 } else
258 } else {
256259 flag = (city == null) ? 3 : 2;
260 }
257261 code |= flag << 4;
258262
259263 len = (numbers == null) ? 0 : numbers.fetchBitStream().getLength();
260264 if (len > 0){
261265 flag = Utils.numberToPointerSize(len) - 1;
262 } else
266 } else {
263267 flag = 3;
268 }
264269 code |= flag << 6;
265270
266271 writer.put1u(code);
267 // System.out.printf("%d %d %d\n", (code >> 2 & 0x3), (code >> 4 & 0x3), (code >> 6 & 0x3));
268272
269273 if (zipBuf != null){
270274 len = zipBuf.size();
381385 if(log.isDebugEnabled())
382386 log.debug("adding polyline ref", this, pl.getSubdiv());
383387 int level = pl.getSubdiv().getZoom().getLevel();
384 List<RoadIndex> l = roadIndexes.get(level);
385 if (l == null) {
386 l = new ArrayList<>();
387 roadIndexes.put(level, l);
388 }
389 l.add(new RoadIndex(pl));
388 roadIndexes.computeIfAbsent(level, k -> new ArrayList<>()).add(new RoadIndex(pl));
390389
391390 if (level == 0) {
392 nodeCount += pl.getNodeCount(hasHouseNumbers());
391 nodeCountInner += pl.getNodeCount(hasHouseNumbers());
393392 }
394393 }
395394
426425 * Set the road length (in meters).
427426 */
428427 public void setLength(double lenInMeter) {
428 this.lenInMeter = (int) Math.round(lenInMeter);
429429 roadLength = NODHeader.metersToRaw(lenInMeter);
430430 }
431431
480480 rgn.position(off.getPosition());
481481 rgn.put3u(offsetNet1 | off.getFlags());
482482 }
483 }
484
485 private boolean internalNodes;
486
487 /**
488 * Does the road have any nodes besides start and end?
489 * These can be number nodes or routing nodes.
490 * This affects whether we need to write extra bits in
491 * the bitstream in RGN.
492 */
493 public boolean hasInternalNodes() {
494 return internalNodes;
495 }
496
497 public void setInternalNodes(boolean n) {
498 internalNodes = n;
499483 }
500484
501485 /**
573557 // If the road has house numbers, we count also
574558 // the number nodes, and these get a 0 in the bit stream.
575559 int nbits = nnodes;
576 if (!startsWithNode)
560 if (!startsWithNode && !hasHouseNumbers())
577561 nbits++;
578562 writer.put2u(nbits);
579563 boolean[] bits = new boolean[nbits];
580564
581565 if (hasHouseNumbers()){
582 int off = startsWithNode ? 0 :1;
583566 for (int i = 0; i < bits.length; i++){
584567 if (nod2BitSet.get(i))
585 bits[i+off] = true;
568 bits[i] = true;
586569 }
587570 } else {
588571 for (int i = 0; i < bits.length; i++)
592575 }
593576 for (int i = 0; i < bits.length; i += 8) {
594577 int b = 0;
595 for (int j = 0; j < 8 && j < bits.length - i; j++)
578 for (int j = 0; j < 8 && j < bits.length - i; j++) {
596579 if (bits[i+j])
597580 b |= 1 << j;
581 }
598582 writer.put1u(b);
599583 }
600584 }
745729 if (cityList == null){
746730 cityList = new ArrayList<>(2);
747731 }
748 if (cityList.contains(city) == false)
732 if (!cityList.contains(city))
749733 cityList.add(city);
750734 }
751735
758742 if (zipList == null){
759743 zipList = new ArrayList<>(2);
760744 }
761 if (zipList.contains(zip) == false)
745 if (!zipList.contains(zip))
762746 zipList.add(zip);
763747 }
764748
835819
836820 public void skipAddToNOD(boolean skip) {
837821 this.skipAddToNOD = skip;
822 if (hasNodInfo()) {
823 log.info("road", this, "is removed from NOD, length:", lenInMeter, "m");
824 netFlags &= ~NET_FLAG_NODINFO;
825 }
838826 }
839827
840828 public void resetImgData() {
854842 }
855843 }
856844 }
845
846 public double getLenInMeter() {
847 return lenInMeter;
848 }
857849
858850 }
1818 import java.util.ArrayList;
1919 import java.util.Arrays;
2020 import java.util.BitSet;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.HashSet;
2124 import java.util.Iterator;
2225 import java.util.LinkedHashMap;
2326 import java.util.List;
2427 import java.util.Map;
2528 import java.util.Map.Entry;
29 import java.util.Set;
2630 import java.util.TreeMap;
2731
2832 import uk.me.parabola.imgfmt.app.Coord;
2933 import uk.me.parabola.imgfmt.app.CoordNode;
3034 import uk.me.parabola.log.Logger;
3135 import uk.me.parabola.util.EnhancedProperties;
36 import uk.me.parabola.util.MultiHashMap;
3237
3338 /**
3439 * This holds the road network. That is all the roads and the nodes
4045 public class RoadNetwork {
4146 private static final Logger log = Logger.getLogger(RoadNetwork.class);
4247
43 private final static int MAX_RESTRICTIONS_ARCS = 7;
48 private static final int MAX_RESTRICTIONS_ARCS = 7;
4449 private final Map<Integer, RouteNode> nodes = new LinkedHashMap<>();
4550
4651 // boundary nodes
5459 private boolean checkRoundaboutFlares;
5560 private int maxFlareLengthRatio ;
5661 private boolean reportSimilarArcs;
62 private boolean routable;
63
64 private long maxSumRoadLenghts;
65 /** for route island search */
66 private int visitId;
5767
5868 public void config(EnhancedProperties props) {
5969 checkRoundabouts = props.getProperty("check-roundabouts", false);
6070 checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false);
6171 maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0);
6272 reportSimilarArcs = props.getProperty("report-similar-arcs", false);
73 maxSumRoadLenghts = props.getProperty("check-routing-island-len", -1);
74 routable = props.containsKey("route");
6375 angleChecker.config(props);
6476 }
6577
6678 public void addRoad(RoadDef roadDef, List<Coord> coordList) {
6779 roadDefs.add(roadDef);
6880
69 CoordNode lastCoord = null;
70 int lastIndex = 0;
81 int lastNodePos = -1;
7182 double roadLength = 0;
7283 double arcLength = 0;
7384 int pointsHash = 0;
7485
7586 int npoints = coordList.size();
7687 int numCoordNodes = 0;
77 boolean hasInternalNodes = false;
7888 int numNumberNodes = 0;
7989 BitSet nodeFlags = new BitSet();
8090 for (int index = 0; index < npoints; index++) {
8191 Coord co = coordList.get(index);
82 int id = co.getId();
83
84 if (id != 0){
92 pointsHash += co.hashCode();
93 if (index > 0) {
94 double segLenth = co.distance(coordList.get(index - 1));
95 arcLength += segLenth;
96 roadLength += segLenth;
97 }
98
99 if (co.getId() > 0) {
100 // we will create a RouteNode for this point if routable
85101 nodeFlags.set(numNumberNodes);
86102 ++numCoordNodes;
87 if(index > 0 && index < npoints - 1)
88 hasInternalNodes = true;
103
104 if (!roadDef.skipAddToNOD()) {
105 if (lastNodePos < 0) {
106 // This is the first node in the road
107 roadDef.setNode(getOrAddNode(co.getId(), co));
108 } else {
109 createDirectArcs(roadDef, coordList, lastNodePos, index, roadLength, arcLength, pointsHash);
110 }
111
112 lastNodePos = index;
113 arcLength = 0;
114 pointsHash = co.hashCode();
115 }
89116 }
90117 if (co.isNumberNode())
91118 ++numNumberNodes;
92 if (index == 0){
93 if (id == 0)
94 roadDef.setStartsWithNode(false);
95
96 } else {
97 double d = co.distance(coordList.get(index-1));
98 arcLength += d;
99 roadLength += d;
100 }
101 if (roadDef.skipAddToNOD())
102 continue;
103
104 pointsHash += co.hashCode();
105
106 if (id == 0)
107 // not a routing node
108 continue;
109
110 // The next coord determines the heading
111 // If this is the not the first node, then create an arc from
112 // the previous node to this one (and back again).
113 if (lastCoord != null) {
114 int lastId = lastCoord.getId();
115 if(log.isDebugEnabled()) {
116 log.debug("lastId = " + lastId + " curId = " + id);
117 log.debug("from " + lastCoord.toDegreeString()
118 + " to " + co.toDegreeString());
119 log.debug("arclength=" + arcLength + " roadlength=" + roadLength);
120 }
121
122 RouteNode node1 = getOrAddNode(lastId, lastCoord);
123 RouteNode node2 = getOrAddNode(id, co);
124 if(node1 == node2)
125 log.error("Road " + roadDef + " contains consecutive identical nodes at " + co.toOSMURL() + " - routing will be broken");
126 else if(arcLength == 0)
127 log.warn("Road " + roadDef + " contains zero length arc at " + co.toOSMURL());
128
129 Coord forwardBearingPoint = coordList.get(lastIndex + 1);
130 if(lastCoord.equals(forwardBearingPoint) || forwardBearingPoint.isAddedNumberNode()) {
131 // bearing point is too close to last node to be
132 // useful - try some more points
133 for(int bi = lastIndex + 2; bi <= index; ++bi) {
134 Coord coTest = coordList.get(bi);
135 if (coTest.isAddedNumberNode() || lastCoord.equals(coTest))
136 continue;
137 forwardBearingPoint = coTest;
138 break;
139 }
140 }
141 Coord reverseBearingPoint = coordList.get(index - 1);
142 if(co.equals(reverseBearingPoint) || reverseBearingPoint.isAddedNumberNode()) {
143 // bearing point is too close to this node to be
144 // useful - try some more points
145 for(int bi = index - 2; bi >= lastIndex; --bi) {
146 Coord coTest = coordList.get(bi);
147 if (coTest.isAddedNumberNode() || co.equals(coTest))
148 continue;
149 reverseBearingPoint = coTest;
150 break;
151 }
152 }
153
154 double forwardInitialBearing = lastCoord.bearingTo(forwardBearingPoint);
155 double forwardDirectBearing = (co == forwardBearingPoint) ? forwardInitialBearing: lastCoord.bearingTo(co);
156
157 double reverseInitialBearing = co.bearingTo(reverseBearingPoint);
158 double directLength = (lastIndex + 1 == index) ? arcLength : lastCoord.distance(co);
159 double reverseDirectBearing = 0;
160 if (directLength > 0){
161 // bearing on rhumb line is a constant, so we can simply revert
162 reverseDirectBearing = (forwardDirectBearing <= 0) ? 180 + forwardDirectBearing: -(180 - forwardDirectBearing) % 180.0;
163 }
164 // Create forward arc from node1 to node2
165 RouteArc arc = new RouteArc(roadDef,
166 node1,
167 node2,
168 forwardInitialBearing,
169 forwardDirectBearing,
170 arcLength,
171 arcLength,
172 directLength,
173 pointsHash);
174 arc.setForward();
175 node1.addArc(arc);
176
177 // Create the reverse arc
178 RouteArc reverseArc = new RouteArc(roadDef,
179 node2, node1,
180 reverseInitialBearing,
181 reverseDirectBearing,
182 arcLength,
183 arcLength,
184 directLength,
185 pointsHash);
186 node2.addArc(reverseArc);
187 // link the two arcs
188 arc.setReverseArc(reverseArc);
189 reverseArc.setReverseArc(arc);
190 } else {
191 // This is the first node in the road
192 roadDef.setNode(getOrAddNode(id, co));
193 }
194
195 lastCoord = (CoordNode) co;
196 lastIndex = index;
197 arcLength = 0;
198 pointsHash = co.hashCode();
199119 }
200120 if (roadDef.hasHouseNumbers()){
201 // we ignore number nodes when we have no house numbers
202 if (numCoordNodes < numNumberNodes)
203 hasInternalNodes = true;
204121 roadDef.setNumNodes(numNumberNodes);
205122 roadDef.setNod2BitSet(nodeFlags);
206123 } else {
124 // we ignore number nodes when we have no house numbers
207125 roadDef.setNumNodes(numCoordNodes);
208126 }
209 if (hasInternalNodes)
210 roadDef.setInternalNodes(true);
211127 roadDef.setLength(roadLength);
212 }
213
128 if (coordList.get(0).getId() == 0) {
129 roadDef.setStartsWithNode(false);
130 }
131 }
132
133 /**
134 * Create forward arc and reverse arc between current and last node. They are stored with the nodes.
135 */
136 private void createDirectArcs(RoadDef roadDef, List<Coord> coordList, int prevPos, int currPos, double roadLength, double arcLength,
137 int pointsHash) {
138
139 Coord prevNode = coordList.get(prevPos);
140 Coord currNode = coordList.get(currPos);
141 int lastId = prevNode.getId();
142 int currId = currNode.getId();
143
144 if(log.isDebugEnabled()) {
145 log.debug("lastId = " + lastId + " curId = " + currId);
146 log.debug("from " + prevNode.toDegreeString()
147 + " to " + currNode.toDegreeString());
148 log.debug("arclength=" + arcLength + " roadlength=" + roadLength);
149 }
150
151 RouteNode node1 = getOrAddNode(lastId, prevNode);
152 RouteNode node2 = getOrAddNode(currId, currNode);
153 if(node1 == node2)
154 log.error("Road " + roadDef + " contains consecutive identical nodes at " + currNode.toOSMURL() + " - routing will be broken");
155 else if(arcLength == 0)
156 log.warn("Road " + roadDef + " contains zero length arc at " + currNode.toOSMURL());
157
158 double directLength = (prevPos + 1 == currPos) ? arcLength : prevNode.distance(currNode);
159
160 // calculate forward bearing
161 Coord forwardBearingPoint = coordList.get(prevPos + 1);
162 if(prevNode.equals(forwardBearingPoint) || forwardBearingPoint.isAddedNumberNode()) {
163 // bearing point is too close to last node to be
164 // useful - try some more points
165 for (int bi = prevPos + 2; bi <= currPos; ++bi) {
166 Coord coTest = coordList.get(bi);
167 if (!(coTest.isAddedNumberNode() || prevNode.equals(coTest))) {
168 forwardBearingPoint = coTest;
169 break;
170 }
171 }
172 }
173 double forwardInitialBearing = prevNode.bearingTo(forwardBearingPoint);
174 double forwardDirectBearing = (currNode == forwardBearingPoint) ? forwardInitialBearing: prevNode.bearingTo(currNode);
175
176
177 // Create forward arc from node1 to node2
178 RouteArc arc = new RouteArc(roadDef,
179 node1, node2,
180 forwardInitialBearing,
181 forwardDirectBearing,
182 arcLength,
183 arcLength,
184 directLength,
185 pointsHash);
186 arc.setForward();
187 node1.addArc(arc);
188
189 // Create the reverse arc
190 Coord reverseBearingPoint = coordList.get(currPos - 1);
191 if(currNode.equals(reverseBearingPoint) || reverseBearingPoint.isAddedNumberNode()) {
192 // bearing point is too close to this node to be
193 // useful - try some more points
194 for (int bi = currPos - 2; bi >= prevPos; --bi) {
195 Coord coTest = coordList.get(bi);
196 if (!(coTest.isAddedNumberNode() || currNode.equals(coTest))) {
197 reverseBearingPoint = coTest;
198 break;
199 }
200 }
201 }
202 double reverseInitialBearing = currNode.bearingTo(reverseBearingPoint);
203 double reverseDirectBearing = 0;
204 if (directLength > 0){
205 // bearing on rhumb line is a constant, so we can simply revert
206 reverseDirectBearing = (forwardDirectBearing <= 0) ? 180 + forwardDirectBearing: -(180 - forwardDirectBearing) % 180.0;
207 }
208 RouteArc reverseArc = new RouteArc(roadDef,
209 node2, node1,
210 reverseInitialBearing,
211 reverseDirectBearing,
212 arcLength,
213 arcLength,
214 directLength,
215 pointsHash);
216 node2.addArc(reverseArc);
217
218 // link the two arcs
219 arc.setReverseArc(reverseArc);
220 reverseArc.setReverseArc(arc);
221 }
222
214223 private RouteNode getOrAddNode(int id, Coord coord) {
215 RouteNode node = nodes.get(id);
216 if (node == null) {
217 node = new RouteNode(coord);
218 nodes.put(id, node);
219 if (node.isBoundary())
220 boundary.add(node);
221 }
222 return node;
224 return nodes.computeIfAbsent(id, key -> new RouteNode(coord));
223225 }
224226
225227 public List<RoadDef> getRoadDefs() {
227229 }
228230
229231 /**
230 * Split the network into RouteCenters.
232 * Split the network into RouteCenters, calls nodes.clear().
231233 *
232234 * The resulting centers must satisfy several constraints,
233235 * documented in NOD1Part.
244246 NOD1Part nod1 = new NOD1Part();
245247 int n = 0;
246248 for (RouteNode node : nodeList) {
247 if (node.getGroup() != group)
248 continue;
249 if(!node.isBoundary()) {
250 if(checkRoundabouts)
251 node.checkRoundabouts();
252 if(checkRoundaboutFlares)
253 node.checkRoundaboutFlares(maxFlareLengthRatio);
254 if(reportSimilarArcs)
255 node.reportSimilarArcs();
256 }
257
258 nod1.addNode(node);
259 n++;
249 if (node.getGroup() == group) {
250 performChecks(node);
251
252 nod1.addNode(node);
253 n++;
254 }
260255 }
261256 if (n > 0)
262257 centers.addAll(nod1.subdivide());
263258 }
264259 }
265260
261 private void performChecks(RouteNode node) {
262 if(!node.isBoundary()) {
263 if(checkRoundabouts)
264 node.checkRoundabouts();
265 if(checkRoundaboutFlares)
266 node.checkRoundaboutFlares(maxFlareLengthRatio);
267 if(reportSimilarArcs)
268 node.reportSimilarArcs();
269 }
270 }
271
266272 public List<RouteCenter> getCenters() {
267 if (centers.isEmpty()){
273 if (routable && centers.isEmpty()){
274 checkRoutingIslands();
275 for (RouteNode n : nodes.values()) {
276 if (n.isBoundary()) {
277 boundary.add(n);
278 }
279 }
280
268281 angleChecker.check(nodes);
269282 addArcsToMajorRoads();
270283 splitCenters();
271284 }
272285 return centers;
286 }
287
288 /**
289 * report routing islands and maybe remove them from NOD.
290 */
291 private void checkRoutingIslands() {
292 if (maxSumRoadLenghts < 0)
293 return; // island check is disabled
294 long t1 = System.currentTimeMillis();
295
296 // calculate all islands
297 List<List<RouteNode>> islands = searchIslands();
298 long t2 = System.currentTimeMillis();
299 log.info("Search for routing islands found", islands.size(), "islands in", (t2 - t1), "ms");
300 if (!islands.isEmpty()) {
301 analyseIslands(islands);
302 }
303 if (maxSumRoadLenghts > 0) {
304 long t3 = System.currentTimeMillis();
305 log.info("routing island removal took", (t3 - t2), "ms");
306 }
307 }
308
309
310 private List<List<RouteNode>> searchIslands() {
311 final int myVisitId = ++visitId;
312 List<List<RouteNode>> islands = new ArrayList<>();
313 for (RouteNode firstNode : nodes.values()) {
314 if (firstNode.getVisitID() != myVisitId) {
315 List<RouteNode> island = new ArrayList<>();
316 firstNode.visitNet(myVisitId, island);
317 // we ignore islands which have boundary nodes
318 if (island.stream().noneMatch(RouteNode::isBoundary)) {
319 islands.add(island);
320 }
321 }
322 }
323 return islands;
324 }
325
326 private void analyseIslands (List<List<RouteNode>> islands) {
327 // index : maps first node in road to the road
328 MultiHashMap<RouteNode, RoadDef> nodeToRoadMap = new MultiHashMap<>();
329 roadDefs.forEach(rd -> {
330 if (rd.getNode() != null) {
331 nodeToRoadMap.add(rd.getNode(), rd);
332 }
333 });
334
335 boolean cleanNodes = false;
336 for (List<RouteNode> island : islands) {
337 // compute size of island as sum of road lengths
338 Set<RoadDef> visitedRoads = new HashSet<>();
339 long sumOfRoadLenghts = calcIslandSize(island, nodeToRoadMap, visitedRoads);
340 log.info("Routing island at", island.get(0).getCoord().toDegreeString(), "with", island.size(),
341 "routing node(s) and total length of", sumOfRoadLenghts, "m");
342 if (sumOfRoadLenghts < maxSumRoadLenghts) {
343 // set discarded flag for all nodes of the island
344 island.forEach(RouteNode::discard);
345 visitedRoads.forEach(rd -> rd.skipAddToNOD(true));
346 cleanNodes = true;
347 }
348 }
349
350 if (cleanNodes) {
351 // remove discarded nodes from map nodes
352 Iterator<Entry<Integer, RouteNode>> iter = nodes.entrySet().iterator();
353 while (iter.hasNext()) {
354 RouteNode n = iter.next().getValue();
355 if (n.isDiscarded()) {
356 iter.remove();
357 }
358 }
359 }
360 }
361
362 /**
363 * Calculate sum of road lengths for the routing island described by the routing nodes.
364 * @param islandNodes the nodes that form the island
365 * @param nodeToRoadMap maps first routing node in RoadDef to RoadDef
366 * @param visitedRoads set that will be filled with the roads seen in this island
367 * @return the rounded sum of lengths in meters
368 */
369 private long calcIslandSize(Collection<RouteNode> islandNodes, MultiHashMap<RouteNode, RoadDef> nodeToRoadMap,
370 Set<RoadDef> visitedRoads) {
371 double sumLen = 0;
372 for (RouteNode node : islandNodes) {
373 for (RouteArc arc : node.arcsIteration()) {
374 if (arc.isDirect() && arc.isForward()) {
375 visitedRoads.add(arc.getRoadDef());
376 }
377 }
378 // we also have to check the first node of a each road, there might
379 // be no arc for it in the node
380 for (RoadDef rd : nodeToRoadMap.get(node)) {
381 visitedRoads.add(rd);
382 }
383 }
384 for (RoadDef rd : visitedRoads) {
385 sumLen += rd.getLenInMeter();
386 }
387 return Math.round(sumLen);
273388 }
274389
275390 /**
293408 * Currently empty.
294409 */
295410 public List<RouteNode> getBoundary() {
296 return boundary;
411 return Collections.unmodifiableList(boundary);
297412 }
298413
299414 /**
479594 List<RouteArc> arcs = arcLists.get(i);
480595 int countNoEffect = 0;
481596 int countOneway= 0;
482 for (int j = arcs.size()-1; j >= 0; --j){
597 for (int j = arcs.size() - 1; j >= 0; --j) {
483598 RouteArc arc = arcs.get(j);
484 if (isUsable(arc.getRoadDef().getAccess(), grr.getExceptionMask()) == false){
599 if (!isUsable(arc.getRoadDef().getAccess(), grr.getExceptionMask())) {
485600 countNoEffect++;
486601 arcs.remove(j);
487 }
488 else if (arc.getRoadDef().isOneway()){
489 if (!arc.isForward()){
490 countOneway++;
491 arcs.remove(j);
492 }
602 } else if (arc.getRoadDef().isOneway() && !arc.isForward()) {
603 countOneway++;
604 arcs.remove(j);
493605 }
494606 }
495607 String arcType = null;
505617 else
506618 arcType = "via way is";
507619 String reason;
508 if (countNoEffect > 0 & countOneway > 0)
620 if (countNoEffect > 0 && countOneway > 0)
509621 reason = "wrong direction in oneway or not accessible for restricted vehicles";
510622 else if (countNoEffect > 0)
511623 reason = "not accessible for restricted vehicles";
537649 byte pathNoAccessMask = 0;
538650 for (int j = 0; j < indexes.length; j++){
539651 RouteArc arc = arcLists.get(j).get(indexes[j]);
540 if (arc.getDest() == vn || viaNodeFound == false){
652 if (!viaNodeFound || arc.getDest() == vn){
541653 arc = arc.getReverseArc();
542654 }
543655 if (arc.getSource() == vn)
576688 }
577689
578690 /**
579 * Compare the disallowed vehicles for the path with the exceptions from the restriction
580 * @param roadNoAccess
581 * @param exceptionMask
582 * @return
691 * Compare the allowed vehicles for the path with the exceptions from the restriction
692 * @param roadAccess access of the road
693 * @param exceptionMask exceptions for the restriction
694 * @return false if no allowed vehicle is concerned by this restriction
583695 */
584696 private static boolean isUsable(byte roadAccess, byte exceptionMask) {
585 if ((roadAccess & (byte) ~exceptionMask) == 0)
586 return false; // no allowed vehicle is concerned by this restriction
587 return true;
697 return (roadAccess & (byte) ~exceptionMask) != 0;
588698 }
589699
590700 private int addNoThroughRoute(GeneralRouteRestriction grr) {
598708 }
599709 int added = 0;
600710
601 for (RouteArc out: vn.arcsIteration()){
711 for (RouteArc out : vn.arcsIteration()) {
602712 if (!out.isDirect())
603713 continue;
604 for (RouteArc in: vn.arcsIteration()){
714 for (RouteArc in : vn.arcsIteration()) {
605715 if (!in.isDirect() || in == out || in.getDest() == out.getDest())
606716 continue;
607717 byte pathAccessMask = (byte) (out.getRoadDef().getAccess() & in.getRoadDef().getAccess());
608 if (isUsable(pathAccessMask, grr.getExceptionMask())){
609 vn.addRestriction(new RouteRestriction(vn, Arrays.asList(in,out), grr.getExceptionMask()));
718 if (isUsable(pathAccessMask, grr.getExceptionMask())) {
719 vn.addRestriction(new RouteRestriction(vn, Arrays.asList(in, out), grr.getExceptionMask()));
610720 added++;
611 } else {
612 if (log.isDebugEnabled())
613 log.debug(grr.getSourceDesc(),"ignored no-through-route",in,"to",out);
721 } else if (log.isDebugEnabled()) {
722 log.debug(grr.getSourceDesc(), "ignored no-through-route", in, "to", out);
614723 }
615724 }
616725 }
648757 * @return
649758 */
650759 private static Integer getBetterAngle (Integer angle1, Integer angle2, char dirIndicator){
651 switch (dirIndicator){
760 switch (dirIndicator) {
652761 case 'l':
653 if (Math.abs(-90-angle2) < Math.abs(-90-angle1))
762 if (Math.abs(-90 - angle2) < Math.abs(-90 - angle1))
654763 return angle2; // closer to -90
655764 break;
656765 case 'r':
657 if (Math.abs(90-angle2) < Math.abs(90-angle1))
766 if (Math.abs(90 - angle2) < Math.abs(90 - angle1))
658767 return angle2; // closer to 90
659768 break;
660 case 'u':
661 double d1 = (angle1 < 0 ) ? -180-angle1 : 180-angle1;
662 double d2 = (angle2 < 0 ) ? -180-angle2 : 180-angle2;
769 case 'u':
770 double d1 = (angle1 < 0) ? -180 - angle1 : 180 - angle1;
771 double d2 = (angle2 < 0) ? -180 - angle2 : 180 - angle2;
663772 if (Math.abs(d2) < Math.abs(d1))
664773 return angle2; // closer to -180
665774 break;
666 case 's':
775 case 's':
667776 if (Math.abs(angle2) < Math.abs(angle1))
668777 return angle2; // closer to 0
669778 break;
670 }
671
779 default:
780 }
672781 return angle1;
673782 }
674783
679788 * @return
680789 */
681790 private static boolean matchDirectionInfo (float angle, char dirIndicator){
682 switch (dirIndicator){
791 switch (dirIndicator) {
683792 case 'l':
684 if (angle < -3 && angle > - 177)
793 if (angle < -3 && angle > -177)
685794 return true;
686795 break;
687796 case 'r':
700809 return true;
701810 }
702811 return false;
703 }
704
812 }
705813
706814 }
1313 */
1414 package uk.me.parabola.imgfmt.app.net;
1515
16 import it.unimi.dsi.fastutil.ints.IntArrayList;
16 import java.util.ArrayDeque;
1717 import java.util.ArrayList;
1818 import java.util.Collections;
19 import java.util.Deque;
1920 import java.util.HashSet;
20 import java.util.Iterator;
2121 import java.util.List;
2222
23 import it.unimi.dsi.fastutil.ints.IntArrayList;
2324 import uk.me.parabola.imgfmt.Utils;
2425 import uk.me.parabola.imgfmt.app.Coord;
2526 import uk.me.parabola.imgfmt.app.CoordNode;
111112 * get all direct arcs to the given node and the given way id
112113 * @param otherNode
113114 * @param roadId
114 * @return
115 * @return list with the direct arcs
115116 */
116117 public List<RouteArc> getDirectArcsTo(RouteNode otherNode, long roadId) {
117118 List<RouteArc> result = new ArrayList<>();
118 for(RouteArc a : arcs){
119 if(a.isDirect() && a.getDest() == otherNode){
120 if(a.getRoadDef().getId() == roadId)
121 result.add(a);
119 for (RouteArc a : arcs) {
120 if (a.isDirect() && a.getDest() == otherNode && a.getRoadDef().getId() == roadId) {
121 result.add(a);
122122 }
123123 }
124124 return result;
127127 /**
128128 * get all direct arcs on a given way id
129129 * @param roadId
130 * @return
130 * @return @return list with the direct arcs
131131 */
132132 public List<RouteArc> getDirectArcsOnWay(long roadId) {
133133 List<RouteArc> result = new ArrayList<>();
134 for(RouteArc a : arcs){
135 if(a.isDirect()){
136 if(a.getRoadDef().getId() == roadId)
137 result.add(a);
134 for (RouteArc a : arcs) {
135 if (a.isDirect() && a.getRoadDef().getId() == roadId) {
136 result.add(a);
138137 }
139138 }
140139 return result;
147146 * @return
148147 */
149148 public RouteArc getDirectArcTo(RouteNode otherNode, RoadDef roadDef) {
150 for(RouteArc a : arcs){
151 if(a.isDirect() && a.getDest() == otherNode){
152 if(a.getRoadDef()== roadDef)
153 return a;
149 for (RouteArc a : arcs) {
150 if (a.isDirect() && a.getDest() == otherNode && a.getRoadDef() == roadDef) {
151 return a;
154152 }
155153 }
156154 return null;
228226 int index = 0;
229227 for (RouteArc arc: arcs){
230228 Byte compactedDir = null;
231 if (useCompactDirs){
232 if (lastArc == null || lastArc.getIndexA() != arc.getIndexA() || lastArc.isForward() != arc.isForward()){
233 if (index % 2 == 0)
234 compactedDir = (byte) ((initialHeadings.get(index) >> 4) | initialHeadings.getInt(index+1));
235 index++;
236 }
229 if (useCompactDirs && (lastArc == null || lastArc.getIndexA() != arc.getIndexA() || lastArc.isForward() != arc.isForward())){
230 if (index % 2 == 0)
231 compactedDir = (byte) ((initialHeadings.get(index) >> 4) | initialHeadings.getInt(index+1));
232 index++;
237233 }
238234 arc.write(writer, lastArc, useCompactDirs, compactedDir);
239235 lastArc = arc;
261257 public void discard() {
262258 // mark the node as having been discarded
263259 flags |= F_DISCARDED;
260 if (isBoundary()) {
261 log.error("intermal error? boundary node at",coord.toDegreeString(), "is discarded");
262 }
263 }
264
265 public boolean isDiscarded() {
266 return (flags & F_DISCARDED) != 0;
264267 }
265268
266269 public int getOffsetNod1() {
321324 return nodeClass;
322325 }
323326
324 public Iterable<? extends RouteArc> arcsIteration() {
325 return new Iterable<RouteArc>() {
326 public Iterator<RouteArc> iterator() {
327 return arcs.iterator();
328 }
329 };
327 public Iterable<RouteArc> arcsIteration() {
328 return () -> arcs.iterator();
330329 }
331330
332331 public List<RouteRestriction> getRestrictions() {
659658 public void reportSimilarArcs() {
660659 for(int i = 0; i < arcs.size(); ++i) {
661660 RouteArc arci = arcs.get(i);
662 if (arci.isDirect() == false)
661 if (!arci.isDirect())
663662 continue;
664663 for(int j = i + 1; j < arcs.size(); ++j) {
665664 RouteArc arcj = arcs.get(j);
666 if (arcj.isDirect() == false)
665 if (!arcj.isDirect())
667666 continue;
668667 if(arci.getDest() == arcj.getDest() &&
669668 arci.getLength() == arcj.getLength() &&
706705 RouteNode next = null;
707706 for (int i = 0; i < current.arcs.size(); i++){
708707 RouteArc arc = current.arcs.get(i);
709 if (arc.getRoadDef() == road){
710 if (arc.isDirect()){
711 if (arc.isForward()){
712 next = arc.getDest();
713 nodes.add(next);
714 forwardArcs.add(arc);
715 forwardArcPositions.add(i);
716 } else {
717 reverseArcPositions.add(i);
718 reverseArcs.add(arc);
719 }
720 }
721 }
708 if (arc.getRoadDef() == road && arc.isDirect()){
709 if (arc.isForward()){
710 next = arc.getDest();
711 nodes.add(next);
712 forwardArcs.add(arc);
713 forwardArcPositions.add(i);
714 } else {
715 reverseArcPositions.add(i);
716 reverseArcs.add(arc);
717 }
718 }
722719 }
723720 current = next;
724721 }
817814 */
818815 public int getGroup() {
819816 if (nodeGroup < 0){
817 if (arcs.isEmpty()) {
818 nodeGroup = 0;
819 return nodeGroup;
820 }
820821 HashSet<RoadDef> roads = new HashSet<>();
821822 for (RouteArc arc: arcs){
822823 roads.add(arc.getRoadDef());
856857 public List<RouteArc> getArcs() {
857858 return arcs;
858859 }
859
860 public int hashCode(){
861 return getCoord().getId();
860
861 @Override
862 public int hashCode() {
863 return getCoord().getId();
862864 }
863865
864866 public List<RouteArc> getDirectArcsBetween(RouteNode otherNode) {
871873 return result;
872874 }
873875
876 /** used to find routing island, but may also be used for other checks */
877 private int visitId;
874878
879 public int getVisitID() {
880 return visitId;
881 }
882
883 public void visitNet(int visitId, List<RouteNode> visited) {
884 if (this.visitId != visitId) {
885 Deque<RouteNode> toVisit = new ArrayDeque<>();
886 toVisit.add(this);
887 while(!toVisit.isEmpty()) {
888 RouteNode n = toVisit.pop();
889 if (n.visitId != visitId) {
890 n.arcs.forEach(a -> toVisit.addLast(a.getDest()));
891 visited.add(n);
892 n.visitId = visitId;
893 }
894 }
895 }
896 }
875897 }
1919 * @author Steve Ratcliffe
2020 */
2121 public class CodePosition {
22 private char primary;
23 private byte secondary;
24 private byte tertiary;
22 private int primary;
23 private int secondary;
24 private int tertiary;
2525
26 public char getPrimary() {
26 public int getPrimary() {
2727 return primary;
2828 }
2929
30 public byte getSecondary() {
30 public int getSecondary() {
3131 return secondary;
3232 }
3333
34 public byte getTertiary() {
34 public int getTertiary() {
3535 return tertiary;
3636 }
3737
4646 case Collator.PRIMARY:
4747 return primary;
4848 case Collator.SECONDARY:
49 return secondary & 0xff;
49 return secondary;
5050 case Collator.TERTIARY:
51 return tertiary & 0xff;
51 return tertiary;
5252 default:
5353 return 0;
5454 }
5555 }
5656
57 public void setPrimary(char primary) {
57 public void setPrimary(int primary) {
5858 this.primary = primary;
5959 }
6060
61 public void setSecondary(byte secondary) {
61 public void setSecondary(int secondary) {
6262 this.secondary = secondary;
6363 }
6464
65 public void setTertiary(byte tertiary) {
65 public void setTertiary(int tertiary) {
6666 this.tertiary = tertiary;
6767 }
6868 }
2323 private final int first;
2424 private final int second;
2525
26 //public CombinedSortKey(SortKey<T> key, int first) {
27 // this(key, first, 0);
28 //}
29
3026 public CombinedSortKey(SortKey<T> obj, int first, int second) {
3127 this.key = obj;
3228 this.first = first;
4137 CombinedSortKey<T> other = (CombinedSortKey<T>) o;
4238 int res = key.compareTo(other.key);
4339 if (res == 0) {
44 res = compareInts(first, other.first);
40 res = Integer.compare(first, other.first);
4541 if (res == 0) {
46 res = compareInts(second, other.second);
42 res = Integer.compare(second, other.second);
4743 }
4844 }
4945 return res;
5046 }
51
52 private int compareInts(int i1, int i2) {
53 int res;
54 if (i1 == i2)
55 res = 0;
56 else if (i1 < i2)
57 res = -1;
58 else
59 res = 1;
60 return res;
61 }
6247 }
105105 int secondary = sort.getSecondary(i);
106106 int tertiary = sort.getTertiary(i);
107107 if (isMulti) {
108 assert primary <= 0xffff;
109 assert secondary <= 0xff;
110 assert tertiary <= 0xff;
108 assert primary <= 0xffff : primary;
109 assert secondary <= 0xff : secondary;
110 assert tertiary <= 0xff : tertiary;
111111 writer.put2u(primary);
112112 writer.put1u(secondary);
113113 writer.put1u(tertiary);
114114 } else {
115 assert primary <= 0xff;
116 assert secondary <= 0xf;
117 assert tertiary <= 0xf;
115 assert primary <= 0xff : primary;
116 assert secondary <= 0xf : secondary;
117 assert tertiary <= 0xf : tertiary;
118118 writer.put1u(primary);
119119 writer.put1u((tertiary << 4) | (secondary & 0xf));
120120 }
134134 for (int i = 0; i < 256; i++) {
135135 if (((p.flags[i] >>> 4) & 0xf) == 0) {
136136 if (p.getPrimary(i) != 0) {
137 byte second = p.getSecondary(i);
137 int second = p.getSecondary(i);
138138 maxSecondary = Math.max(maxSecondary, second);
139139 if (second != 0) {
140140 maxTertiary = Math.max(maxTertiary, p.getTertiary(i));
360360 // We need +1 for the null bytes, we also +2 for a couple of expanded characters. For a complete
361361 // german map this was always enough in tests.
362362 byte[] key = new byte[(chars.length + 1 + 2) * 4];
363 int needed = 0;
363 int needed;
364364 try {
365365 needed = fillCompleteKey(chars, key);
366366 } catch (ArrayIndexOutOfBoundsException e) {
596596
597597 /**
598598 * Allocate space for up to n pages.
599 * @param n
599 * @param n Number of pages
600600 */
601601 public void setMaxPage(int n) {
602602 pages = Arrays.copyOf(pages, n + 1);
625625 private final byte[] tertiary = new byte[256];
626626 private final byte[] flags = new byte[256];
627627
628 char getPrimary(int ch) {
628 int getPrimary(int ch) {
629629 return primary[ch & 0xff];
630630 }
631631
633633 primary[ch & 0xff] = (char) val;
634634 }
635635
636 byte getSecondary(int ch) {
637 return secondary[ch & 0xff];
636 int getSecondary(int ch) {
637 return secondary[ch & 0xff] & 0xff;
638638 }
639639
640640 void setSecondary(int ch, int val) {
641641 secondary[ch & 0xff] = (byte) val;
642642 }
643643
644 byte getTertiary(int ch) {
645 return tertiary[ch & 0xff];
644 int getTertiary(int ch) {
645 return tertiary[ch & 0xff] & 0xff;
646646 }
647647
648648 void setTertiary(int ch, int val) {
658658 public int getPos(int type, int ch) {
659659 switch (type) {
660660 case Collator.PRIMARY:
661 return getPrimary(ch) & 0xffff;
661 return getPrimary(ch);
662662 case Collator.SECONDARY:
663 return getSecondary(ch) & 0xff;
663 return getSecondary(ch);
664664 case Collator.TERTIARY:
665 return getTertiary(ch) & 0xff;
665 return getTertiary(ch);
666666 default:
667667 assert false : "bad collation type passed";
668668 return 0;
849849 }
850850
851851 // Get the first non-ignorable at this level
852 int c = chars[(pos++ & 0xff)];
852 int c = chars[pos++ & 0xff];
853853 if (!hasPage(c >>> 8)) {
854854 next = 0;
855855 continue;
172172 int rec;
173173 if (posLength == 2) {
174174 rec = reader.get2u();
175 cp.setPrimary((char) (rec & 0xff));
176 cp.setSecondary((byte) ((rec >> 8) & 0xf));
177 cp.setTertiary((byte) ((rec >> 12) & 0xf));
175 cp.setPrimary(rec & 0xff);
176 cp.setSecondary((rec >> 8) & 0xf);
177 cp.setTertiary((rec >> 12) & 0xf);
178178
179179 } else if (posLength == 3) {
180 // what is the extra byte for ???
180181 rec = reader.get3u();
181 cp.setPrimary((char) (rec & 0xff));
182 cp.setSecondary((byte) ((rec >> 8) & 0xf));
183 cp.setTertiary((byte) ((rec >> 12) & 0xf));
182 cp.setPrimary(rec & 0xff);
183 cp.setSecondary((rec >> 8) & 0xf);
184 cp.setTertiary((rec >> 12) & 0xf);
184185 } else if (posLength == 4) {
185186 rec = reader.get4();
186 cp.setPrimary((char) (rec & 0xffff));
187 cp.setSecondary((byte) ((rec >> 16) & 0xff));
188 cp.setTertiary((byte) ((rec >> 24) & 0xff));
187 cp.setPrimary(rec & 0xffff);
188 cp.setSecondary((rec >> 16) & 0xff);
189 cp.setTertiary((rec >> 24) & 0xff);
189190 } else {
190191 throw new RuntimeException("unexpected value posLength " + posLength);
191192 }
3030 // These are our inputs.
3131 private final Polyline polyline;
3232
33 private boolean extraBit;
33 /** if true, we must write the extraBits which allow to find out which points are nodes */
34 private final boolean extraBit;
3435 private final boolean extTypeLine;
3536 private boolean xSameSign;
3637 private boolean xSignNegative; // Set if all negative
4647 private int[] deltas;
4748 private boolean[] nodes;
4849
49 private boolean ignoreNumberOnlyNodes;
50 private final boolean ignoreNumberOnlyNodes;
5051
5152 LinePreparer(Polyline line) {
52 if (line.isRoad() &&
53 line.getSubdiv().getZoom().getLevel() == 0 &&
54 line.roadHasInternalNodes()) {
55 // it might be safe to write the extra bits regardless,
56 // but who knows
57 extraBit = true;
58 }
59 if (!line.hasHouseNumbers())
60 ignoreNumberOnlyNodes = true;
61
53 extraBit = line.isRoad() && line.getSubdiv().getZoom().getLevel() == 0 && line.hasInternalNodes();
54 ignoreNumberOnlyNodes = !line.hasHouseNumbers();
6255 extTypeLine = line.hasExtendedType();
6356
6457 polyline = line;
274267 int lon = subdiv.roundLonToLocalShifted(co.getLongitude());
275268 if (log.isDebugEnabled())
276269 log.debug("shifted pos", lat, lon);
277 if (first) {
278 lastLat = lat;
279 lastLong = lon;
280 first = false;
281 continue;
282 }
283270
284271 int dx = lon - lastLong;
285272 int dy = lat - lastLat;
286273 lastLong = lon;
287274 lastLat = lat;
288 boolean isSpecialNode = false;
289 if (co.getId() > 0 || (co.isNumberNode() && ignoreNumberOnlyNodes == false))
290 isSpecialNode = true;
291 if (dx != 0 || dy != 0 || extraBit && isSpecialNode)
292 firstsame = i;
275 if (first) {
276 first = false;
277 continue;
278 }
293279
294280 /*
295281 * Current thought is that the node indicator is set when
300286 * polyline making up the road.
301287 */
302288 if (extraBit) {
289 boolean isSpecialNode = co.getId() > 0 || (!ignoreNumberOnlyNodes && co.isNumberNode());
290 if (dx != 0 || dy != 0 || isSpecialNode)
291 firstsame = i;
303292 boolean extra = false;
304293 if (isSpecialNode) {
305294 if (i < nodes.length - 1)
4949 boolean hasSubtype = false;
5050 int type = getType();
5151 int subtype = 0;
52 if (type > 0xff) {
53 if((type & 0xff) != 0) {
54 hasSubtype = true;
55 subtype = type & 0xff;
56 }
57 type >>= 8;
52 if ((type & 0xff) != 0) {
53 hasSubtype = true;
54 subtype = type & 0xff;
5855 }
56 type >>= 8;
5957
6058 file.put1u(type);
6159
232232 return roaddef != null;
233233 }
234234
235 public boolean roadHasInternalNodes() {
236 return roaddef.hasInternalNodes();
235 /**
236 * @return true if this polyline contanins special nodes between the first and last node
237 */
238 boolean hasInternalNodes() {
239 if (isRoad() && (roaddef.hasHouseNumbers() || !roaddef.skipAddToNOD())) {
240 // exclude first and last node
241 for (int i = 1; i < points.size() - 1; i++) {
242 Coord co = points.get(i);
243 if (co.getId() > 0 || (roaddef.hasHouseNumbers() && co.isNumberNode())) {
244 return true;
245 }
246 }
247 }
248 return false;
237249 }
238250
239251 public void setLastSegment(boolean last) {
257269 public boolean sharesNodeWith(Polyline other) {
258270 for (Coord p1 : points) {
259271 if (p1.getId() != 0) {
260 // point is a node, see if the other line contain the
272 // point is a node, see if the other line contains the
261273 // same node
262274 for (Coord p2 : other.points)
263275 if (p1.getId() == p2.getId())
277289 }
278290
279291 /**
280 *
292 * Count special nodes between (excluding) first and last node.
281293 * @param countAllNodes : false: count only coord nodes, true: count number nodes
282 * @return
294 * @return number of special nodes in points, ignoring the first and last node
283295 */
284296 public int getNodeCount(boolean countAllNodes ) {
285 int idx = 0;
286297 int count = 0;
287298
288 for (Coord co : points) {
289 if (idx++ > 0 && (co.getId() > 0 || countAllNodes && co.isNumberNode()))
299 for (int i = 1; i< points.size() -1; i++) {
300 Coord co = points.get(i);
301 if (co.getId() > 0 || countAllNodes && co.isNumberNode())
290302 count++;
291303 }
292304 return count;
101101
102102 int t = reader.get1u();
103103 int val = reader.get3u();
104 boolean hasSubtype = false;
105 if ((val & 0x800000) != 0)
106 hasSubtype = true;
107
108 boolean hasPoi = false;
109 if ((val & 0x400000) != 0)
110 hasPoi = true;
104 boolean hasSubtype = (val & 0x800000) != 0;
105 boolean hasPoi = (val & 0x400000) != 0;
111106
112107 Label l;
113108 int labelOffset = val & 0x3fffff;
126121 p.setDeltaLong(reader.get2s());
127122 p.setDeltaLat(reader.get2s());
128123
124 t <<= 8;
129125 if (hasSubtype) {
130126 int st = reader.get1u();
131 p.setType((t << 8) | st);
127 t |= st;
132128 //p.setHasSubtype(true);
133 } else {
134 p.setType(t);
135 }
129 }
130 p.setType(t);
136131
137132 p.setNumber(number++);
138133 points.add(p);
5252 private final EnhancedProperties args = new EnhancedProperties();
5353 private Set<String> validOptions;
5454
55 {
55 public CommandArgsReader(ArgumentProcessor proc) {
56 this.proc = proc;
5657 // Set some default values. It is as if these were on the command
5758 // line before any user supplied options.
5859 add(new CommandOption("mapname", "63240001"));
6162 add(new CommandOption("overview-mapnumber", "63240000"));
6263 add(new CommandOption("poi-address", ""));
6364 add(new CommandOption("merge-lines", ""));
64 }
65
66 public CommandArgsReader(ArgumentProcessor proc) {
67 this.proc = proc;
6865 }
6966
7067 /**
178175 log.debug("adding option", option, value);
179176
180177 // Note if an explicit mapname is set
181 if (option.equals("mapname"))
182 mapnameWasSet = true;
178 if (option.equals("mapname")) {
179 if (extractMapName(value) == null || "00000000".equals(extractMapName(value))) {
180 throw new ExitException("invalid value for option: mapname="+value+" - mapname should be a 8 digit integer, default is 63240001");
181 } else {
182 mapnameWasSet = true;
183 }
184 }
183185
184186 switch (option) {
185187 case "input-file":
218220 * @param filename The filename to obtain options from.
219221 */
220222 private void readConfigFile(String filename) {
221 Options opts = new Options(new OptionProcessor() {
222 public void processOption(Option opt) {
223 log.debug("incoming opt", opt.getOption(), opt.getValue());
224 addOption(new CommandOption(opt));
225 }
223 Options opts = new Options(opt -> {
224 log.debug("incoming opt", opt.getOption(), opt.getValue());
225 addOption(new CommandOption(opt));
226226 });
227227 try {
228228 opts.readOptionFile(filename);
267267 args.setProperty("mapname", mapname);
268268 }
269269
270 checkMapName();
270271 // Now process the file
271272 proc.processFilename(new CommandArgs(args), name);
272273
273274 // Increase the name number. If the next arg sets it then that
274275 // will override this new name.
275276 mapname = args.getProperty("mapname");
276 try {
277 Formatter fmt = new Formatter();
277 try (Formatter fmt = new Formatter()) {
278278 try {
279279 int n = Integer.parseInt(mapname);
280280 fmt.format("%08d", ++n);
282282 fmt.format("%8.8s", mapname);
283283 }
284284 args.setProperty("mapname", fmt.toString());
285 fmt.close();
286285 } catch (NumberFormatException e) {
287286 // If the name is not a number then we just leave it alone...
288287 }
289288 }
290289
291 private String extractMapName(String path) {
292
293 File file = new File(path);
294 String fname = file.getName();
295 Pattern pat = Pattern.compile("([0-9]{8})");
296 Matcher matcher = pat.matcher(fname);
297 boolean found = matcher.find();
298 if (found)
299 return matcher.group(1);
300
301 return null;
302 }
290 private void checkMapName() {
291 try {
292 String mapname = args.getProperty("mapname");
293 int n = Integer.parseInt(mapname);
294 if (n > 99999999) {
295 throw new ExitException("Cannot calculate mapid for " + name);
296 }
297 } catch (NumberFormatException e) {
298 throw new ExitException("Cannot calculate mapid for " + name);
299 }
300 }
301
302 }
303
304 private String extractMapName(String path) {
305 File file = new File(path);
306 String fname = file.getName();
307 Pattern pat = Pattern.compile("([0-9]{8})");
308 Matcher matcher = pat.matcher(fname);
309 boolean found = matcher.find();
310 if (found)
311 return matcher.group(1);
312
313 return null;
303314 }
304315
305316 /**
348359 private int filenameCount;
349360
350361 ArgList() {
351 alist = new ArrayList<ArgType>();
362 alist = new ArrayList<>();
352363 }
353364
354365 protected void add(CommandOption option) {
1212
1313 package uk.me.parabola.mkgmap.build;
1414
15 import java.awt.Rectangle;
1516 import java.awt.geom.Path2D;
1617 import java.awt.geom.Rectangle2D;
1718 import java.io.File;
2728 import java.util.IdentityHashMap;
2829 import java.util.List;
2930 import java.util.Set;
31 import java.util.TreeMap;
3032 import java.util.function.UnaryOperator;
3133
3234 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
99101 import uk.me.parabola.mkgmap.reader.hgt.HGTConverter;
100102 import uk.me.parabola.mkgmap.reader.hgt.HGTConverter.InterpolationMethod;
101103 import uk.me.parabola.mkgmap.reader.hgt.HGTReader;
104 import uk.me.parabola.mkgmap.reader.osm.GType;
102105 import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource;
103106 import uk.me.parabola.util.Configurable;
104107 import uk.me.parabola.util.EnhancedProperties;
126129 private List<String> mapInfo = new ArrayList<>();
127130 private List<String> copyrights = new ArrayList<>();
128131
129 private boolean doRoads;
130 private Boolean driveOnLeft; // needs to be Boolean for later test: if (driveOnLeft == null){
132 private boolean doRoads;
133 private Boolean driveOnLeft; // needs to be Boolean for later null test
131134 private Locator locator;
132135
133136 private final java.util.Map<String, Highway> highways = new HashMap<>();
134137
135138 /** name that is used for cities which name are unknown */
136 private final static String UNKNOWN_CITY_NAME = "";
139 private static final String UNKNOWN_CITY_NAME = "";
137140
138141 private Country defaultCountry;
139142 private String countryName = "COUNTRY";
145148
146149 private int minSizePolygon;
147150 private String polygonSizeLimitsOpt;
148 private HashMap<Integer,Integer> polygonSizeLimits;
151 private TreeMap<Integer,Integer> polygonSizeLimits;
149152 private double reducePointError;
150153 private double reducePointErrorPolygon;
151154 private boolean mergeLines;
190193 mergeLines = props.containsKey("merge-lines");
191194
192195 // undocumented option - usually used for debugging only
193 mergeShapes = props.getProperty("no-mergeshapes", false) == false;
196 mergeShapes = !props.getProperty("no-mergeshapes", false);
194197
195198 makePOIIndex = props.getProperty("make-poi-index", false);
196199
221224 String ipm = props.getProperty("dem-interpolation", "auto");
222225 switch (ipm) {
223226 case "auto":
224 demInterpolationMethod = InterpolationMethod.Automatic;
227 demInterpolationMethod = InterpolationMethod.AUTOMATIC;
225228 break;
226229 case "bicubic":
227 demInterpolationMethod = InterpolationMethod.Bicubic;
230 demInterpolationMethod = InterpolationMethod.BICUBIC;
228231 break;
229232 case "bilinear":
230 demInterpolationMethod = InterpolationMethod.Bilinear;
233 demInterpolationMethod = InterpolationMethod.BILINEAR;
231234 break;
232235 default:
233236 throw new IllegalArgumentException("invalid argument for option dem-interpolation: '" + ipm +
235238 }
236239 }
237240
238 private List<Integer> parseDemDists(String demDists) {
241 private static List<Integer> parseDemDists(String demDists) {
239242 List<Integer> dists = new ArrayList<>();
240243 if (demDists == null)
241244 dists.add(-1);
262265 TREFile treFile = map.getTreFile();
263266 lblFile = map.getLblFile();
264267 NETFile netFile = map.getNetFile();
265 DEMFile demFile = map.getDemFile();
266
267 if(routeCenterBoundaryType != 0 &&
268 netFile != null &&
269 src instanceof MapperBasedMapDataSource) {
270 for(RouteCenter rc : src.getRoadNetwork().getCenters()) {
271 ((MapperBasedMapDataSource)src).addBoundaryLine(rc.getArea(), routeCenterBoundaryType, rc.reportSizes());
268
269 doRoads = netFile != null;
270
271 if (routeCenterBoundaryType != 0 && netFile != null && src instanceof MapperBasedMapDataSource) {
272 for (RouteCenter rc : src.getRoadNetwork().getCenters()) {
273 ((MapperBasedMapDataSource) src).addBoundaryLine(rc.getArea(), routeCenterBoundaryType,
274 rc.reportSizes());
272275 }
273276 }
274277 if (mapInfo.isEmpty())
275278 getMapInfo();
276279
280 if (map.getNodFile() != null) {
281 // make sure that island detection is done before we write any map data so that NOD flags are properly set
282 src.getRoadNetwork().getCenters();
283 }
277284 normalizeCountries(src);
278285
279286 processCities(map, src);
283290 processInfo(map, src);
284291 makeMapAreas(map, src);
285292
286 if (driveOnLeft == null){
287 // check if source gives info about driving side
288 if (src instanceof MapperBasedMapDataSource){
289 driveOnLeft = ((MapperBasedMapDataSource) src).getDriveOnLeft();
290 }
293 if (driveOnLeft == null && src instanceof MapperBasedMapDataSource) {
294 // source can give info about driving side
295 driveOnLeft = ((MapperBasedMapDataSource) src).getDriveOnLeft();
291296 }
292297 if (driveOnLeft == null)
293298 driveOnLeft = false;
317322 netFile.writePost(rgnFile.getWriter());
318323 }
319324 warnAbout3ByteImgRefs();
320 if (demFile != null) {
321 try{
322 long t1 = System.currentTimeMillis();
323 java.awt.geom.Area demArea = null;
324 if (demPolygon != null) {
325 Area bbox = src.getBounds();
326 // the rectangle is a bit larger to avoid problems at tile boundaries
327 Rectangle2D r = new Rectangle2D.Double(bbox.getMinLong() - 2, bbox.getMinLat() - 2,
328 bbox.getWidth() + 4, bbox.getHeight() + 4);
329 if (demPolygon.intersects(r) && !demPolygon.contains(r)){
330 demArea = demPolygon;
331 }
332 }
333 if (demArea == null && src instanceof OverviewMapDataSource) {
334 Path2D demPoly = ((OverviewMapDataSource) src).getTileAreaPath();
335 if (demPoly != null) {
336 demArea = new java.awt.geom.Area(demPoly);
337 }
338 }
339 Area treArea = demFile.calc(src.getBounds(), demArea, pathToHGT, demDists, demOutsidePolygonHeight, demInterpolationMethod);
340 map.setBounds(treArea);
341 long t2 = System.currentTimeMillis();
342 log.info("DEM file calculation for", map.getFilename(), "took", (t2 - t1), "ms");
343 demFile.write();
344 } catch (MapFailedException e) {
345 log.error("exception while creating DEM file", e.getMessage());
346 throw new MapFailedException("DEM"); //TODO: better remove DEM file?
347 }
348 }
325 buildDem(map, src);
349326 treFile.writePost();
350327 }
351328
329 private void buildDem(Map map, LoadableMapDataSource src) {
330 DEMFile demFile = map.getDemFile();
331 if (demFile == null)
332 return;
333 try{
334 long t1 = System.currentTimeMillis();
335 java.awt.geom.Area demArea = null;
336 if (demPolygon != null) {
337 Area bbox = src.getBounds();
338 // the rectangle is a bit larger to avoid problems at tile boundaries
339 Rectangle2D r = new Rectangle(bbox.getMinLong() - 2, bbox.getMinLat() - 2,
340 bbox.getWidth() + 4, bbox.getHeight() + 4);
341 if (demPolygon.intersects(r) && !demPolygon.contains(r)) {
342 demArea = demPolygon;
343 }
344 }
345 if (demArea == null && src instanceof OverviewMapDataSource) {
346 Path2D demPoly = ((OverviewMapDataSource) src).getTileAreaPath();
347 if (demPoly != null) {
348 demArea = new java.awt.geom.Area(demPoly);
349 }
350 }
351 Area treArea = demFile.calc(src.getBounds(), demArea, pathToHGT, demDists, demOutsidePolygonHeight, demInterpolationMethod);
352 map.setBounds(treArea);
353 long t2 = System.currentTimeMillis();
354 log.info("DEM file calculation for", map.getFilename(), "took", (t2 - t1), "ms");
355 demFile.write();
356 } catch (MapFailedException e) {
357 log.error("exception while creating DEM file", e.getMessage());
358 throw new MapFailedException("DEM");
359 }
360 }
361
352362 private void warnAbout3ByteImgRefs() {
363 String mapContains = "Map contains";
353364 String infoMsg = "- more than 65535 might cause indexing problems and excess size. Suggest splitter with lower --max-nodes";
354365 int itemCount;
355366 itemCount = lblFile.numCities();
356367 if (itemCount > 0xffff)
357 log.error("Map contains", itemCount, "Cities", infoMsg);
368 log.error(mapContains, itemCount, "Cities", infoMsg);
358369 itemCount = lblFile.numZips();
359370 if (itemCount > 0xffff)
360 log.error("Map contains", itemCount, "Zips", infoMsg);
371 log.error(mapContains, itemCount, "Zips", infoMsg);
361372 itemCount = lblFile.numHighways();
362373 if (itemCount > 0xffff)
363 log.error("Map contains", itemCount, "Highways", infoMsg);
374 log.error(mapContains, itemCount, "Highways", infoMsg);
364375 itemCount = lblFile.numExitFacilities();
365376 if (itemCount > 0xffff)
366 log.error("Map contains", itemCount, "Exit facilities", infoMsg);
377 log.error(mapContains, itemCount, "Exit facilities", infoMsg);
367378 } // warnAbout3ByteImgRefs
368379
369380 private Country getDefaultCountry() {
379390 * @return the default region in the given country ({@code null} if not available)
380391 */
381392 private Region getDefaultRegion(Country country) {
382 if (lblFile==null || regionName == null) {
393 if (lblFile == null || regionName == null) {
383394 return null;
384395 }
385396 if (country == null) {
405416 if (countryStr != null) {
406417 countryStr = locator.normalizeCountry(countryStr);
407418 p.setCountry(countryStr);
408 }
409 }
410
419 }
420 }
421
411422 for (MapLine l : src.getLines()) {
412423 String countryStr = l.getCountry();
413424 if (countryStr != null) {
414425 countryStr = locator.normalizeCountry(countryStr);
415426 l.setCountry(countryStr);
416 }
417 }
427 }
428 }
418429
419430 // shapes do not have address information
420 // untag the following lines if this is wrong
421 // for (MapShape s : src.getShapes()) {
422 // String countryStr = s.getCountry();
423 // if (countryStr != null) {
424 // countryStr = locator.normalizeCountry(countryStr);
425 // s.setCountry(countryStr);
426 // }
427 // }
428
429431 }
430432
431433 /**
441443 private void processCities(Map map, MapDataSource src) {
442444 LBLFile lbl = map.getLblFile();
443445
444 if (locationAutofill.isEmpty() == false) {
446 if (!locationAutofill.isEmpty()) {
445447 // collect the names of the cities
446448 for (MapPoint p : src.getPoints()) {
447449 if(p.isCity() && p.getName() != null)
451453 locator.autofillCities(); // Try to fill missing information that include search of next city
452454 }
453455
454 for (MapPoint p : src.getPoints())
455 {
456 if(p.isCity() && p.getName() != null)
457 {
458 String countryStr = p.getCountry();
459 Country thisCountry;
460 if(countryStr != null) {
461 thisCountry = lbl.createCountry(countryStr, locator.getCountryISOCode(countryStr));
462 } else
463 thisCountry = getDefaultCountry();
464
465 String regionStr = p.getRegion();
466 Region thisRegion;
467 if(regionStr != null)
468 {
469 thisRegion = lbl.createRegion(thisCountry,regionStr, null);
470 }
471 else
472 thisRegion = getDefaultRegion(thisCountry);
473
474 City thisCity;
475 if(thisRegion != null)
476 thisCity = lbl.createCity(thisRegion, p.getName(), true);
477 else
478 thisCity = lbl.createCity(thisCountry, p.getName(), true);
479
480 cityMap.put(p, thisCity);
481 }
456 for (MapPoint p : src.getPoints()) {
457 if (!p.isCity() || p.getName() == null)
458 continue;
459 String countryStr = p.getCountry();
460 Country thisCountry;
461 if (countryStr != null) {
462 thisCountry = lbl.createCountry(countryStr, locator.getCountryISOCode(countryStr));
463 } else {
464 thisCountry = getDefaultCountry();
465 }
466 String regionStr = p.getRegion();
467 Region thisRegion;
468 if (regionStr != null) {
469 thisRegion = lbl.createRegion(thisCountry, regionStr, null);
470 } else {
471 thisRegion = getDefaultRegion(thisCountry);
472 }
473 City thisCity;
474 if (thisRegion != null)
475 thisCity = lbl.createCity(thisRegion, p.getName(), true);
476 else
477 thisCity = lbl.createCity(thisCountry, p.getName(), true);
478
479 cityMap.put(p, thisCity);
482480 }
483481
484482 }
487485 LBLFile lbl = map.getLblFile();
488486 MapPoint searchPoint = new MapPoint();
489487 for (MapLine line : src.getLines()) {
490 if(line.isRoad()) {
491 String cityName = line.getCity();
492 String cityCountryName = line.getCountry();
493 String cityRegionName = line.getRegion();
494 String zipStr = line.getZip();
495
496 if(cityName == null && locationAutofill.contains("nearest")) {
497 // Get name of next city if untagged
498
499 searchPoint.setLocation(line.getLocation());
500 MapPoint nextCity = locator.findNextPoint(searchPoint);
501
502 if(nextCity != null) {
503 cityName = nextCity.getCity();
504 // city/region/country fields should match to the found city
505 cityCountryName = nextCity.getCountry();
506 cityRegionName = nextCity.getRegion();
507
508 // use the zip code only if no zip code is known
509 if(zipStr == null)
510 zipStr = nextCity.getZip();
488 if(!line.isRoad())
489 continue;
490 String cityName = line.getCity();
491 String cityCountryName = line.getCountry();
492 String cityRegionName = line.getRegion();
493 String zipStr = line.getZip();
494
495 if(cityName == null && locationAutofill.contains("nearest")) {
496 // Get name of next city if untagged
497
498 searchPoint.setLocation(line.getLocation());
499 MapPoint nextCity = locator.findNextPoint(searchPoint);
500
501 if(nextCity != null) {
502 cityName = nextCity.getCity();
503 // city/region/country fields should match to the found city
504 cityCountryName = nextCity.getCountry();
505 cityRegionName = nextCity.getRegion();
506
507 // use the zip code only if no zip code is known
508 if(zipStr == null)
509 zipStr = nextCity.getZip();
510 }
511 }
512
513 MapRoad road = (MapRoad) line;
514 road.resetImgData();
515
516 City roadCity = calcCity(lbl, cityName, cityRegionName, cityCountryName);
517 if (roadCity != null)
518 road.addRoadCity(roadCity);
519
520 if (zipStr != null) {
521 road.addRoadZip(lbl.createZip(zipStr));
522 }
523
524 processRoadNumbers(road, lbl);
525 }
526 }
527
528 private void processRoadNumbers(MapRoad road, LBLFile lbl) {
529 List<Numbers> numbers = road.getRoadDef().getNumbersList();
530 if (numbers == null)
531 return;
532 for (Numbers num : numbers) {
533 for (int i = 0; i < 2; i++) {
534 boolean leftRightFlag = (i == 0);
535 ZipCodeInfo zipInfo = num.getZipCodeInfo(leftRightFlag);
536 if (zipInfo != null && zipInfo.getZipCode() != null) {
537 Zip zip = zipInfo.getImgZip();
538 if (zip == null) {
539 zip = lbl.createZip(zipInfo.getZipCode());
540 zipInfo.setImgZip(zip);
511541 }
512 }
513
514 MapRoad road = (MapRoad) line;
515 road.resetImgData();
516
517 City roadCity = calcCity(lbl, cityName, cityRegionName, cityCountryName);
518 if (roadCity != null)
519 road.addRoadCity(roadCity);
520
521 if(zipStr != null) {
522 road.addRoadZip(lbl.createZip(zipStr));
523 }
524
525 List<Numbers> numbers = road.getRoadDef().getNumbersList();
526 if (numbers != null){
527 for (Numbers num : numbers){
528 for (int i = 0; i < 2; i++){
529 boolean left = (i == 0);
530 ZipCodeInfo zipInfo = num.getZipCodeInfo(left);
531 if (zipInfo != null && zipInfo.getZipCode() != null){
532 Zip zip = zipInfo.getImgZip();
533 if (zipInfo.getImgZip() == null){
534 zip = lbl.createZip(zipInfo.getZipCode());
535 zipInfo.setImgZip(zip);
536 }
537 if (zip != null)
538 road.addRoadZip(zip);
539 }
540 CityInfo cityInfo = num.getCityInfo(left);
541 if (cityInfo != null){
542 City city = cityInfo.getImgCity();
543 if (city == null ){
544 city = calcCity(lbl, cityInfo.getCity(), cityInfo.getRegion(), cityInfo.getCountry());
545 cityInfo.setImgCity(city);
546 }
547 if (city != null)
548 road.addRoadCity(city);
549 }
550 }
542 if (zip != null)
543 road.addRoadZip(zip);
544 }
545 CityInfo cityInfo = num.getCityInfo(leftRightFlag);
546 if (cityInfo != null) {
547 City city = cityInfo.getImgCity();
548 if (city == null) {
549 city = calcCity(lbl, cityInfo.getCity(), cityInfo.getRegion(), cityInfo.getCountry());
550 cityInfo.setImgCity(city);
551551 }
552 }
553 }
554 }
555 }
556
557 private City calcCity(LBLFile lbl, String city, String region, String country){
552 if (city != null)
553 road.addRoadCity(city);
554 }
555 }
556 }
557 }
558
559 private City calcCity(LBLFile lbl, String city, String region, String country) {
558560 if (city == null && region == null && country == null)
559561 return null;
560 Country cc = (country == null)? getDefaultCountry() : lbl.createCountry(locator.normalizeCountry(country), locator.getCountryISOCode(country));
561 Region cr = (region == null)? getDefaultRegion(cc) : lbl.createRegion(cc, region, null);
562 if (city == null && (country != null || region != null)) {
563 // if city name is unknown and region and/or country is known
562 Country cc = (country == null) ? getDefaultCountry()
563 : lbl.createCountry(locator.normalizeCountry(country), locator.getCountryISOCode(country));
564 Region cr = (region == null) ? getDefaultRegion(cc) : lbl.createRegion(cc, region, null);
565 if (city == null) {
566 // if city name is unknown and region and/or country is known
564567 // use empty name for the city
565568 city = UNKNOWN_CITY_NAME;
566569 }
567 if (city == null)
568 return null;
569 if(cr != null) {
570 if (cr != null) {
570571 return lbl.createCity(cr, city, false);
571 }
572 else {
572 } else {
573573 return lbl.createCity(cc, city, false);
574574 }
575575 }
576
577576
578577 private void processPOIs(Map map, MapDataSource src) {
579578
589588 // * cities (already processed)
590589 // * extended types (address information not shown in MapSource and on GPS)
591590 // * all POIs except roads in case the no-poi-address option is set
592 else if (!p.isCity() && !p.hasExtendedType() && poiAddresses)
593 {
591 else if (!p.isCity() && !p.hasExtendedType() && poiAddresses) {
594592
595593 String countryStr = p.getCountry();
596594 String regionStr = p.getRegion();
597595 String zipStr = p.getZip();
598596 String cityStr = p.getCity();
599597
600 if(locationAutofill.contains("nearest") && (countryStr == null || regionStr == null || (zipStr == null && cityStr == null)))
601 {
598 if (locationAutofill.contains("nearest")
599 && (countryStr == null || regionStr == null || (zipStr == null && cityStr == null))) {
602600 MapPoint nextCity = locator.findNearbyCityByName(p);
603
604 if(nextCity == null)
601
602 if (nextCity == null)
605603 nextCity = locator.findNextPoint(p);
606604
607 if(nextCity != null)
608 {
609 if (countryStr == null) countryStr = nextCity.getCountry();
610 if (regionStr == null) regionStr = nextCity.getRegion();
611
612 if(zipStr == null)
613 {
605 if (nextCity != null) {
606 if (countryStr == null)
607 countryStr = nextCity.getCountry();
608 if (regionStr == null)
609 regionStr = nextCity.getRegion();
610
611 if (zipStr == null) {
614612 String cityZipStr = nextCity.getZip();
615
616 // Ignore list of Zips separated by ;
617
618 if(cityZipStr != null && cityZipStr.indexOf(',') < 0)
613
614 // Ignore list of Zips separated by ,
615 if (cityZipStr != null && cityZipStr.indexOf(',') < 0)
619616 zipStr = cityZipStr;
620617 }
621
622 if(cityStr == null) cityStr = nextCity.getCity();
623
618
619 if (cityStr == null)
620 cityStr = nextCity.getCity();
621
624622 }
625 }
626
623 }
627624
628 if(countryStr != null && !checkedForPoiDispFlag)
629 {
625 if (countryStr != null && !checkedForPoiDispFlag) {
630626 // Different countries require different address notation
631627
632628 poiDisplayFlags = locator.getPOIDispFlag(countryStr);
633629 checkedForPoiDispFlag = true;
634630 }
635631
636
637632 POIRecord r = lbl.createPOI(p.getName());
638633
639 if(cityStr != null || regionStr != null || countryStr != null){
634 if (cityStr != null || regionStr != null || countryStr != null) {
640635 r.setCity(calcCity(lbl, cityStr, regionStr, countryStr));
641636 }
642637
643 if (zipStr != null)
644 {
638 if (zipStr != null) {
645639 Zip zip = lbl.createZip(zipStr);
646640 r.setZip(zip);
647641 }
648642
649 if(p.getStreet() != null)
650 {
643 if (p.getStreet() != null) {
651644 Label streetName = lbl.newLabel(p.getStreet());
652 r.setStreetName(streetName);
645 r.setStreetName(streetName);
653646 }
654647
655648 String houseNumber = p.getHouseNumber();
656 if (houseNumber != null && !houseNumber.isEmpty()) {
657 if(!r.setSimpleStreetNumber(houseNumber))
658 r.setComplexStreetNumber(lbl.newLabel(houseNumber));
649 if (houseNumber != null && !houseNumber.isEmpty() && !r.setSimpleStreetNumber(houseNumber)) {
650 r.setComplexStreetNumber(lbl.newLabel(houseNumber));
659651 }
660652
661653 String phone = p.getPhone();
662 if (phone != null && !phone.isEmpty()) {
663 if(!r.setSimplePhoneNumber(phone))
664 r.setComplexPhoneNumber(lbl.newLabel(phone));
665 }
654 if (phone != null && !phone.isEmpty() && !r.setSimplePhoneNumber(phone)) {
655 r.setComplexPhoneNumber(lbl.newLabel(phone));
656 }
666657
667658 poimap.put(p, r);
668659 }
674665 private void processExit(Map map, MapExitPoint mep) {
675666 LBLFile lbl = map.getLblFile();
676667 String ref = mep.getMotorwayRef();
677 String OSMId = mep.getOSMId();
668 String osmId = mep.getOSMId();
678669 if(ref != null) {
679670 Highway hw = highways.get(ref);
680671 if(hw == null)
681672 hw = makeHighway(map, ref);
682673 if(hw == null) {
683 log.warn("Can't create exit", mep.getName(), "(OSM id", OSMId, ") on unknown highway", ref);
674 log.warn("Can't create exit", mep.getName(), "(OSM id", osmId, ") on unknown highway", ref);
684675 return;
685676 }
686677 String exitName = mep.getName();
687678 String exitTo = mep.getTo();
688679 Exit exit = new Exit(hw);
689680 String facilityDescription = mep.getFacilityDescription();
690 log.info("Creating", ref, "exit", exitName, "(OSM id", OSMId +") to", exitTo, "with facility", ((facilityDescription == null)? "(none)" : facilityDescription));
681 log.info("Creating", ref, "exit", exitName, "(OSM id", osmId +") to", exitTo, "with facility", ((facilityDescription == null)? "(none)" : facilityDescription));
691682 if(facilityDescription != null) {
692683 // description is TYPE,DIR,FACILITIES,LABEL
693684 // (same as Polish Format)
738729 private void makeMapAreas(Map map, LoadableMapDataSource src) {
739730 // The top level has to cover the whole map without subdividing, so
740731 // do a special check to make sure.
741 LevelInfo[] levels = null;
742 if (src instanceof OverviewMapDataSource){
732 LevelInfo[] levels = null;
733 if (src instanceof OverviewMapDataSource) {
743734 mergeLines = true;
744735 prepShapesForMerge(src.getShapes());
745736 mergeShapes = true;
746737 levels = src.mapLevels();
747 }
748 else {
749 if (OverviewBuilder.isOverviewImg(map.getFilename())) {
750 levels = src.overviewMapLevels();
751 } else {
752 levels = src.mapLevels();
753 }
754 }
755 if (levels == null){
738 } else if (OverviewBuilder.isOverviewImg(map.getFilename())) {
739 levels = src.overviewMapLevels();
740 } else {
741 levels = src.mapLevels();
742 }
743 if (levels == null) {
756744 throw new ExitException("no info about levels available.");
757745 }
758746 LevelInfo levelInfo = levels[0];
802790 log.debug("ADD parent-subdiv", parent, srcDivPair.getSource(), ", z=", zoom, " new=", div);
803791 nextList.add(new SourceSubdiv(area, div));
804792 }
805 if (nextList.size() > 0){
793 if (!nextList.isEmpty()) {
806794 Subdivision lastdiv = nextList.get(nextList.size() - 1).getSubdiv();
807795 lastdiv.setLast(true);
808796 }
819807 */
820808 private static void prepShapesForMerge(List<MapShape> shapes) {
821809 Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>();
822 for (MapShape s : shapes){
810 for (MapShape s : shapes) {
823811 List<Coord> points = s.getPoints();
824812 int n = points.size();
825 for (int i = 0; i< n; i++){
813 for (int i = 0; i < n; i++) {
826814 Coord co = points.get(i);
827815 Coord repl = coordMap.get(Utils.coord2Long(co));
828816 if (repl == null)
831819 points.set(i, repl);
832820 }
833821 }
834 return;
835822 }
836823
837824 /**
931918 catch (Exception e) {
932919 throw new ExitException("Error reading license file " + licenseFileName, e);
933920 }
934 if ((licenseArray.size() > 0) && licenseArray.get(0).startsWith("\ufeff"))
921 if ((!licenseArray.isEmpty()) && licenseArray.get(0).startsWith("\ufeff"))
935922 licenseArray.set(0, licenseArray.get(0).substring(1));
936923 UnaryOperator<String> replaceVariables = s -> s.replace("$MKGMAP_VERSION$", Version.VERSION)
937924 .replace("$JAVA_VERSION$", System.getProperty("java.version"))
961948 }
962949 }
963950
964 public void setMapInfo(List<String> msgs){
951 public void setMapInfo(List<String> msgs) {
965952 mapInfo = msgs;
966953 }
967
968 public void setCopyrights(List<String> msgs){
954
955 public void setCopyrights(List<String> msgs) {
969956 copyrights = msgs;
970 }
971
957 }
972958
973959 /**
974960 * Set all the information that appears in the header.
992978 //
993979 // We use it to add copyright information that there is no room for
994980 // elsewhere
995 String info = "";
996 for (String s: mapInfo){
997 info += s.trim() + "\n";
998 }
999 if (!info.isEmpty())
1000 map.addInfo(info);
1001 if (copyrights.isEmpty()){
981 StringBuilder info = new StringBuilder();
982 for (String s : mapInfo) {
983 info.append(s.trim()).append('\n');
984 }
985 if (info.length() > 0)
986 map.addInfo(info.toString());
987
988 if (copyrights.isEmpty()) {
1002989 // There has to be (at least) two copyright messages or else the map
1003990 // does not show up. The second and subsequent ones will be displayed
1004991 // at startup, although the conditions where that happens are not known.
10381025 // pointIndex must be initialized to the number of indexed
10391026 // points (not 1)
10401027 for (MapPoint point : points) {
1041 if (point.isCity() &&
1042 point.getMinResolution() <= res) {
1028 if (point.isCity() && point.getMinResolution() <= res) {
10431029 ++pointIndex;
10441030 haveIndPoints = true;
10451031 }
10471033
10481034 for (MapPoint point : points) {
10491035
1050 if (point.isCity() ||
1051 point.getMinResolution() > res)
1036 if (point.isCity() || point.getMinResolution() > res)
10521037 continue;
10531038
10541039 String name = point.getName();
10561041 Point p = div.createPoint(name);
10571042 p.setType(point.getType());
10581043
1059 if(point.hasExtendedType()) {
1044 if (point.hasExtendedType()) {
10601045 ExtTypeAttributes eta = point.getExtTypeAttributes();
1061 if(eta != null) {
1046 if (eta != null) {
10621047 eta.processLabels(lbl);
10631048 p.setExtTypeAttributes(eta);
10641049 }
10681053 try {
10691054 p.setLatitude(coord.getLatitude());
10701055 p.setLongitude(coord.getLongitude());
1071 }
1072 catch (AssertionError ae) {
1056 } catch (AssertionError ae) {
10731057 log.error("Problem with point of type 0x" + Integer.toHexString(point.getType()) + " at " + coord.toOSMURL());
10741058 log.error(" Subdivision shift is " + div.getShift() +
10751059 " and its centre is at " + div.getCenter().toOSMURL());
10821066 p.setPOIRecord(r);
10831067
10841068 map.addMapObject(p);
1085 if(!point.hasExtendedType()) {
1086 if(name != null && div.getZoom().getLevel() == 0) {
1087 if(pointIndex > 255)
1088 log.error("Too many POIs at location " + div.getCenter().toOSMURL() + " - " + name + " will be ignored");
1089 else if(point.isExit()) {
1090 Exit e = ((MapExitPoint)point).getExit();
1091 if(e != null)
1069 if (!point.hasExtendedType()) {
1070 if (name != null && div.getZoom().getLevel() == 0) {
1071 if (pointIndex > 255) {
1072 log.error("Too many POIs near location", div.getCenter().toOSMURL(), "-", name,
1073 "will be ignored");
1074 } else if (point.isExit()) {
1075 Exit e = ((MapExitPoint) point).getExit();
1076 if (e != null)
10921077 e.getHighway().addExitPoint(name, pointIndex, div);
1078 } else if (makePOIIndex) {
1079 lbl.createPOIIndex(name, pointIndex, div, point.getType());
10931080 }
1094 else if(makePOIIndex)
1095 lbl.createPOIIndex(name, pointIndex, div, point.getType());
10961081 }
10971082
10981083 ++pointIndex;
11051090 pointIndex = 1; // reset to 1
11061091 for (MapPoint point : points) {
11071092
1108 if (!point.isCity() ||
1109 point.getMinResolution() > res)
1093 if (!point.isCity() || point.getMinResolution() > res)
11101094 continue;
11111095
11121096 String name = point.getName();
11131097
11141098 Point p = div.createPoint(name);
1115 p.setType(point.getType());
1099 int fullType = point.getType();
1100 assert (fullType & 0xff) == 0 : "indPoint " + GType.formatType(fullType) + " has subtype";
1101 p.setType(fullType);
11161102
11171103 Coord coord = point.getLocation();
11181104 try {
11191105 p.setLatitude(coord.getLatitude());
11201106 p.setLongitude(coord.getLongitude());
1121 }
1122 catch (AssertionError ae) {
1107 } catch (AssertionError ae) {
11231108 log.error("Problem with point of type 0x" + Integer.toHexString(point.getType()) + " at " + coord.toOSMURL());
11241109 log.error(" Subdivision shift is " + div.getShift() +
11251110 " and its centre is at " + div.getCenter().toOSMURL());
11341119 City c = cityMap.get(point);
11351120
11361121 if(pointIndex > 255) {
1137 System.err.println("Can't set city point index for " + name + " (too many indexed points in division)\n");
1122 log.error("Can't set city point index for", name, "(too many indexed points in division)\n");
11381123 } else {
11391124 c.setPointIndex(pointIndex);
11401125 c.setSubdivision(div);
11571142 * @param div The subdivision that the lines belong to.
11581143 * @param lines The lines to be added.
11591144 */
1160 private void processLines(Map map, Subdivision div, List<MapLine> lines)
1161 {
1145 private void processLines(Map map, Subdivision div, List<MapLine> lines) {
11621146 div.startLines(); // Signal that we are beginning to draw the lines.
11631147
11641148 int res = div.getResolution();
11661150 FilterConfig config = new FilterConfig();
11671151 config.setResolution(res);
11681152 config.setLevel(div.getZoom().getLevel());
1169 config.setRoutable(doRoads);
1153 config.setHasNet(doRoads);
11701154
11711155 //TODO: Maybe this is the wrong place to do merging.
11721156 // Maybe more efficient if merging before creating subdivisions.
11741158 LineMergeFilter merger = new LineMergeFilter();
11751159 lines = merger.merge(lines, res);
11761160 }
1161
11771162 LayerFilterChain filters = new LayerFilterChain(config);
11781163 if (enableLineCleanFilters && (res < 24)) {
11791164 filters.addFilter(new RoundCoordsFilter());
11851170 filters.addFilter(new RemoveEmpty());
11861171 filters.addFilter(new RemoveObsoletePointsFilter());
11871172 filters.addFilter(new LinePreparerFilter(div));
1188 filters.addFilter(new LineAddFilter(div, map, doRoads));
1173 filters.addFilter(new LineAddFilter(div, map));
11891174
11901175 for (MapLine line : lines) {
1191 if (line.getMinResolution() > res)
1192 continue;
1193 filters.startFilter(line);
1176 if (line.getMinResolution() <= res) {
1177 filters.startFilter(line);
1178 }
11941179 }
11951180 }
11961181
12051190 * @param div The subdivision that the polygons belong to.
12061191 * @param shapes The polygons to be added.
12071192 */
1208 private void processShapes(Map map, Subdivision div, List<MapShape> shapes)
1209 {
1193 private void processShapes(Map map, Subdivision div, List<MapShape> shapes) {
12101194 div.startShapes(); // Signal that we are beginning to draw the shapes.
12111195
12121196 int res = div.getResolution();
12141198 FilterConfig config = new FilterConfig();
12151199 config.setResolution(res);
12161200 config.setLevel(div.getZoom().getLevel());
1217 config.setRoutable(doRoads);
1201 config.setHasNet(doRoads);
12181202
12191203 if (mergeShapes){
12201204 ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea);
12471231 filters.addFilter(new ShapeAddFilter(div, map));
12481232
12491233 for (MapShape shape : shapes) {
1250 if (shape.getMinResolution() > res)
1251 continue;
1252 filters.startFilter(shape);
1234 if (shape.getMinResolution() <= res) {
1235 filters.startFilter(shape);
1236 }
12531237 }
12541238 }
12551239
12751259 int maxLon = shape.getBounds().getMaxLong();
12761260
12771261 List<Coord> points = shape.getPoints();
1278 int n = shape.getPoints().size();
1262 int n = points.size();
12791263 IdentityHashMap<Coord, Coord> coords = new IdentityHashMap<>(n);
1280 Coord first = points.get(0);
1281 Coord prev = first;
1282 Coord last = first;
1283 for(int i = 1; i < points.size(); ++i) {
1264 Coord prev = points.get(0);
1265 Coord last;
1266 for (int i = 1; i < n; ++i) {
12841267 last = points.get(i);
12851268 // preserve coord instances which are used more than once,
12861269 // these are typically produced by the ShapeMergerFilter
12871270 // to connect holes
1288 if (coords.get(last) == null){
1271 if (coords.get(last) == null) {
12891272 coords.put(last, last);
1290 }
1291 else {
1292 if (!last.preserved()){
1293 last.preserved(true);
1294 }
1273 } else if (!last.preserved()) {
1274 last.preserved(true);
12951275 }
12961276
12971277 // preserve the end points of horizontal and vertical lines that lie
12981278 // on the bbox of the shape.
12991279 if(last.getLatitude() == prev.getLatitude() && (last.getLatitude() == minLat || last.getLatitude() == maxLat) ||
1300 last.getLongitude() == prev.getLongitude()&& (last.getLongitude() == minLon || last.getLongitude() == maxLon)){
1280 last.getLongitude() == prev.getLongitude() && (last.getLongitude() == minLon || last.getLongitude() == maxLon)) {
13011281 last.preserved(true);
13021282 prev.preserved(true);
13031283 }
13071287 }
13081288
13091289 Highway makeHighway(Map map, String ref) {
1310 if(getDefaultRegion(null) == null) {
1290 if (getDefaultRegion(null) == null) {
13111291 log.warn("Highway " + ref + " has no region (define a default region to zap this warning)");
13121292 }
1313 Highway hw = highways.get(ref);
1314 if(hw == null) {
1315 LBLFile lblFile = map.getLblFile();
1316 log.info("creating highway " + ref);
1317 hw = lblFile.createHighway(getDefaultRegion(null), ref);
1318 highways.put(ref, hw);
1319 }
1320
1321 return hw;
1293 return highways.computeIfAbsent(ref, k-> {
1294 log.info("creating highway", ref);
1295 return map.getLblFile().createHighway(getDefaultRegion(null), ref);
1296 });
13221297 }
13231298
13241299 /**
13361311 return 24 - minShift;
13371312 }
13381313
1339 /**
1340 * Enable/disable the creation of a routable map
1341 * @param doRoads
1342 */
1343 public void setDoRoads(boolean doRoads) {
1344 this.doRoads = doRoads;
1345 }
1346
13471314 public void setEnableLineCleanFilters(boolean enable) {
13481315 this.enableLineCleanFilters = enable;
13491316 }
13541321 * @return the size filter value
13551322 */
13561323 private int getMinSizePolygonForResolution(int res) {
1357
13581324 if (polygonSizeLimitsOpt == null)
13591325 return minSizePolygon;
13601326
1361 if (polygonSizeLimits == null){
1362 polygonSizeLimits = new HashMap<>();
1327 if (polygonSizeLimits == null) {
1328 polygonSizeLimits = new TreeMap<>();
13631329 String[] desc = polygonSizeLimitsOpt.split("[, \\t\\n]+");
1364
1365 int count = 0;
1330
13661331 for (String s : desc) {
13671332 String[] keyVal = s.split("[=:]");
13681333 if (keyVal == null || keyVal.length < 2) {
1369 System.err.println("incorrect polygon-size-limits specification " + polygonSizeLimitsOpt);
1370 continue;
1334 throw new ExitException("incorrect polygon-size-limits specification " + polygonSizeLimitsOpt);
13711335 }
13721336
13731337 try {
13741338 int key = Integer.parseInt(keyVal[0]);
13751339 int value = Integer.parseInt(keyVal[1]);
13761340 Integer testDup = polygonSizeLimits.put(key, value);
1377 if (testDup != null){
1378 System.err.println("duplicate resolution value in polygon-size-limits specification " + polygonSizeLimitsOpt);
1379 continue;
1341 if (testDup != null) {
1342 throw new ExitException("duplicate resolution value in polygon-size-limits specification "
1343 + polygonSizeLimitsOpt);
13801344 }
13811345 } catch (NumberFormatException e) {
1382 System.err.println("polygon-size-limits specification not all numbers " + keyVal[count]);
1383 }
1384 count++;
1385 }
1386 }
1387 if (polygonSizeLimits != null){
1388 // return the value for the desired resolution or the next higher one
1389 for (int r = res; r <= 24; r++){
1390 Integer limit = polygonSizeLimits.get(r);
1391 if (limit != null){
1392 if (r != res)
1393 polygonSizeLimits.put(res, limit);
1394 return limit;
1395 }
1396 }
1397 return 0;
1398 }
1399 return minSizePolygon;
1346 throw new ExitException("polygon-size-limits specification not all numbers: " + s);
1347 }
1348 }
1349 if (polygonSizeLimits.get(24) == null)
1350 polygonSizeLimits.put(24, 0);
1351 }
1352 // return the value for the desired resolution or the next higher one
1353 return polygonSizeLimits.ceilingEntry(res).getValue();
14001354 }
14011355
14021356 private static class SourceSubdiv {
14171371 }
14181372 }
14191373
1420 private class LineAddFilter extends BaseFilter implements MapFilter {
1374 private static class LineAddFilter extends BaseFilter implements MapFilter {
14211375 private final Subdivision div;
14221376 private final Map map;
1423 private final boolean doRoads;
1424
1425 LineAddFilter(Subdivision div, Map map, boolean doRoads) {
1377
1378 LineAddFilter(Subdivision div, Map map) {
14261379 this.div = div;
14271380 this.map = map;
1428 this.doRoads = doRoads;
14291381 }
14301382
14311383 public void doFilter(MapElement element, MapFilterChain next) {
14391391 eta.processLabels(map.getLblFile());
14401392 pl.setExtTypeAttributes(eta);
14411393 }
1442 } else
1394 } else {
14431395 div.setPolylineNumber(pl);
1396 }
14441397
14451398 pl.setDirection(line.isDirection());
14461399
14471400 pl.addCoords(line.getPoints());
14481401
14491402 pl.setType(line.getType());
1450 if (doRoads){
1451 if (line instanceof MapRoad) {
1452 if (log.isDebugEnabled())
1453 log.debug("adding road def: " + line.getName());
1454 MapRoad road = (MapRoad) line;
1455 RoadDef roaddef = road.getRoadDef();
1456
1457 pl.setRoadDef(roaddef);
1458 if (road.hasSegmentsFollowing() )
1459 pl.setLastSegment(false);
1460
1461 roaddef.addPolylineRef(pl);
1462 }
1403 if (map.getNetFile() != null && line instanceof MapRoad) {
1404 if (log.isDebugEnabled())
1405 log.debug("adding road def: " + line.getName());
1406 MapRoad road = (MapRoad) line;
1407 RoadDef roaddef = road.getRoadDef();
1408
1409 pl.setRoadDef(roaddef);
1410 if (road.hasSegmentsFollowing())
1411 pl.setLastSegment(false);
1412
1413 roaddef.addPolylineRef(pl);
14631414 }
14641415 map.addMapObject(pl);
14651416 }
14831434 pg.addCoords(shape.getPoints());
14841435
14851436 pg.setType(shape.getType());
1486 if(element.hasExtendedType()) {
1437 if (element.hasExtendedType()) {
14871438 ExtTypeAttributes eta = element.getExtTypeAttributes();
1488 if(eta != null) {
1439 if (eta != null) {
14891440 eta.processLabels(map.getLblFile());
14901441 pg.setExtTypeAttributes(eta);
14911442 }
4545 import uk.me.parabola.imgfmt.fs.ImgChannel;
4646 import uk.me.parabola.imgfmt.sys.ImgFS;
4747 import uk.me.parabola.mkgmap.CommandArgs;
48 import uk.me.parabola.mkgmap.general.MapPoint;
4849 import uk.me.parabola.mkgmap.srt.SrtTextReader;
4950
5051 /**
265266
266267 Mdr5Record mdrCity = null;
267268 boolean isCity;
268 if (p.getType() >= 0x1 && p.getType() <= 0x11) {
269 if (MapPoint.isCityType(p.getType())) {
269270 // This is itself a city, it gets a reference to its own MDR 5 record.
270271 // and we also use it to set the name of the city.
271272 mdrCity = maps.cities.get((p.getSubdiv().getNumber() << 8) + p.getNumber());
2626 public class FilterConfig {
2727 private int resolution;
2828 private int level;
29 private boolean routable;
29 private boolean hasNet;
3030
3131 protected int getResolution() {
3232 return resolution;
5757 this.level = level;
5858 }
5959
60 public boolean isRoutable() {
61 return routable;
60 public boolean hasNet() {
61 return hasNet;
6262 }
6363
64 public void setRoutable(boolean routable) {
65 this.routable = routable;
64 public void setHasNet(boolean flag) {
65 this.hasNet = flag;
6666 }
6767 }
8787 int dy = (lat - lastLat) << offset >> offset;
8888 lastLong = lon;
8989 lastLat = lat;
90 if (dx == 0 && dy == 0){
91 if(!line.isRoad() || (co.getId() == 0 && co.isNumberNode() == false))
92 continue;
90 if (dx == 0 && dy == 0 && !(line.isRoad() && co.isNumberNode())) {
91 continue;
9392 }
9493 ++numPointsEncoded;
95 if (numPointsEncoded >= minPointsRequired && element instanceof MapShape == false)
94 if (numPointsEncoded >= minPointsRequired && !(element instanceof MapShape))
9695 break;
9796 // find out largest and 2nd largest delta for both dx and dy
9897 for (int k = 0; k < 2; k++){
4444 private boolean isRoutable;
4545 public void init(FilterConfig config) {
4646 this.level = config.getLevel();
47 this.isRoutable = config.isRoutable();
47 this.isRoutable = config.hasNet();
4848 }
4949
5050
3333
3434 private boolean checkPreserved;
3535 public void init(FilterConfig config) {
36 checkPreserved = config.getLevel() == 0 && config.isRoutable();
36 checkPreserved = config.getLevel() == 0 && config.hasNet();
3737 }
3838
3939 /**
4848 return;
4949 }
5050 int requiredPoints = (line instanceof MapShape ) ? 4:2;
51 List<Coord> newPoints = new ArrayList<Coord>(numPoints);
51 List<Coord> newPoints = new ArrayList<>(numPoints);
5252 while (true){
5353 boolean removedSpike = false;
5454 numPoints = points.size();
5656
5757 Coord lastP = points.get(0);
5858 newPoints.add(lastP);
59 for(int i = 1; i < numPoints; i++) {
59 for (int i = 1; i < numPoints; i++) {
6060 Coord newP = points.get(i);
6161 int last = newPoints.size()-1;
6262 lastP = newPoints.get(last);
6464 // only add the new point if it has different
6565 // coordinates to the last point or is preserved
6666 if (checkPreserved && line.isRoad()){
67 if (newP.preserved() == false)
67 if (!newP.preserved()) {
6868 continue;
69 else if (lastP.preserved() == false){
69 } else if (!lastP.preserved()){
7070 newPoints.set(last, newP); // replace last
7171 }
72 } else
72 } else {
7373 continue;
74 }
7475 }
7576 if (newPoints.size() > 1) {
7677 switch (Utils.isStraight(newPoints.get(last-1), lastP, newP)){
102103 if (!removedSpike || newPoints.size() < requiredPoints)
103104 break;
104105 points = newPoints;
105 newPoints = new ArrayList<Coord>(points.size());
106 newPoints = new ArrayList<>(points.size());
106107 }
107108 if (line instanceof MapShape){
108109 // Check special cases caused by the fact that the first and last point
1919 import uk.me.parabola.imgfmt.app.CoordNode;
2020 import uk.me.parabola.mkgmap.general.MapElement;
2121 import uk.me.parabola.mkgmap.general.MapLine;
22 import uk.me.parabola.mkgmap.general.MapRoad;
2223
2324 public class RoundCoordsFilter implements MapFilter {
2425
2526 private int shift;
26 private boolean checkRouting;
27 private boolean keepNodes;
28 private int level;
2729
2830 public void init(FilterConfig config) {
2931 shift = config.getShift();
30 checkRouting = config.getLevel() == 0 && config.isRoutable() == true;
31
32 keepNodes = config.getLevel() == 0 && config.hasNet();
33 level = config.getLevel();
3234 }
3335
3436 /**
3739 */
3840 public void doFilter(MapElement element, MapFilterChain next) {
3941 MapLine line = (MapLine) element;
40 int half = 1 << (shift - 1); // 0.5 shifted
41 int mask = ~((1 << shift) - 1); // to remove fraction bits
42
4342 if(shift == 0) {
4443 // do nothing
4544 next.doFilter(line);
4645 }
4746 else {
47 int half = 1 << (shift - 1); // 0.5 shifted
48 int mask = ~((1 << shift) - 1); // to remove fraction bits
49
4850 // round lat/lon values to nearest for shift
49 List<Coord> newPoints = new ArrayList<Coord>(line.getPoints().size());
51 List<Coord> newPoints = new ArrayList<>(line.getPoints().size());
5052 Coord lastP = null;
53 boolean hasNumbers = level == 0 && line.isRoad() && ((MapRoad) line).getRoadDef().hasHouseNumbers();
5154 for(Coord p : line.getPoints()) {
55 if (level > 0 && p.isAddedNumberNode()) {
56 // ignore nodes added by housenumber processing for levels > 0
57 continue;
58 }
59
5260 int lat = (p.getLatitude() + half) & mask;
5361 int lon = (p.getLongitude() + half) & mask;
5462 Coord newP;
5563
56 if(p instanceof CoordNode && checkRouting)
64 if(p instanceof CoordNode && keepNodes)
5765 newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary(), p.getOnCountryBorder());
58 else
66 else {
5967 newP = new Coord(lat, lon);
60 newP.preserved(p.preserved());
61
68 newP.preserved(p.preserved());
69 newP.setNumberNode(hasNumbers && p.isNumberNode());
70 }
71
6272 // only add the new point if it has different
6373 // coordinates to the last point or if it's a
64 // CoordNode and the last point wasn't a CoordNode
65 if(lastP == null ||
66 !lastP.equals(newP) ||
67 (newP instanceof CoordNode && !(lastP instanceof CoordNode))) {
74 // special node
75 if (lastP == null || !lastP.equals(newP) || newP.getId() > 0|| (hasNumbers && newP.isNumberNode())) {
6876 newPoints.add(newP);
6977 lastP = newP;
70 }
71 else if(newP.preserved()) {
78 } else if (newP.preserved()) {
7279 // this point is not going to be used because it
7380 // has the same (rounded) coordinates as the last
7481 // node but it has been marked as being "preserved" -
7582 // transfer that property to the previous point so
76 // that it's not lost
83 // that it's not lost in further filters
7784 lastP.preserved(true);
7885 }
7986 }
2626 private final int size;
2727
2828 private int minSize;
29 private boolean checkRouting;
29 private boolean keepRoads;
3030
3131 public SizeFilter(int s) {
3232 size = s;
3333 }
3434
3535 public void init(FilterConfig config) {
36 minSize = size * (1<<config.getShift());
36 minSize = size * (1 << config.getShift());
3737 // don't remove roads on level 0
38 checkRouting = config.getLevel() == 0 && config.isRoutable() == true;
38 keepRoads = config.getLevel() == 0 && config.hasNet();
3939 }
4040
4141 /**
4848 public void doFilter(MapElement element, MapFilterChain next) {
4949 MapLine line = (MapLine) element;
5050
51 if ((line.isSkipSizeFilter() || (checkRouting && line.isRoad())) == false){
52 if (line.getBounds().getMaxDimension() < minSize){
53 return;
54 }
51 if (!line.isSkipSizeFilter() && !(keepRoads && line.isRoad()) && line.getBounds().getMaxDimension() < minSize) {
52 return;
5553 }
5654 next.doFilter(line);
5755 }
1010 * General Public License for more details.
1111 */
1212 package uk.me.parabola.mkgmap.general;
13
14 import java.util.Objects;
1315
1416 import uk.me.parabola.imgfmt.app.lbl.City;
1517
7173 public boolean equals(Object obj) {
7274 if (this == obj)
7375 return true;
74 if (obj == null)
75 return false;
7676 if (!(obj instanceof CityInfo))
7777 return false;
7878 CityInfo other = (CityInfo) obj;
79 if (city == null) {
80 if (other.city != null)
81 return false;
82 } else if (!city.equals(other.city))
79 if (!Objects.equals(city, other.city))
8380 return false;
84 if (country == null) {
85 if (other.country != null)
86 return false;
87 } else if (!country.equals(other.country))
81 if (!Objects.equals(country, other.country))
8882 return false;
89 if (region == null) {
90 if (other.region != null)
91 return false;
92 } else if (!region.equals(other.region))
93 return false;
94 return true;
83 return Objects.equals(region, other.region);
9584 }
9685
9786 @Override
6363
6464 public static boolean isCityType(int type)
6565 {
66 return type >= 0x0100 && type <= 0x1100;
66 return type >= 0x0100 && type < 0x1100;
6767 }
6868
6969 public boolean isExit() {
1010 * General Public License for more details.
1111 */
1212 package uk.me.parabola.mkgmap.general;
13
14 import java.util.Objects;
1315
1416 import uk.me.parabola.imgfmt.app.lbl.Zip;
1517
4951 public boolean equals(Object obj) {
5052 if (this == obj)
5153 return true;
52 if (obj == null)
53 return false;
5454 if (!(obj instanceof ZipCodeInfo))
5555 return false;
5656 ZipCodeInfo other = (ZipCodeInfo) obj;
57 if (zipCode == null) {
58 if (other.zipCode != null)
59 return false;
60 } else if (!zipCode.equals(other.zipCode))
61 return false;
62 return true;
57 return Objects.equals(zipCode, other.zipCode);
6358 }
6459
6560 @Override
249249 }
250250
251251 public void startOptions() {
252 MapProcessor saver = new NameSaver();
252 /**
253 * A null implementation that just returns the input name as the output.
254 */
255 MapProcessor saver = (args, filename) -> filename;
253256 processMap.put("img", saver);
254257 processMap.put("mdx", saver);
255258
753756 return SrtTextReader.sortForCodepage(args.getCodePage());
754757 }
755758
756 /**
757 * A null implementation that just returns the input name as the output.
758 */
759 private static class NameSaver implements MapProcessor {
760 public String makeMap(CommandArgs args, String filename) {
761 return filename;
762 }
763 }
764
765759 private static class FilenameTask extends FutureTask<String> {
766760 private CommandArgs args;
767761 private String filename;
103103
104104 MapBuilder builder = new MapBuilder();
105105 builder.config(args.getProperties());
106 if(! OverviewBuilder.OVERVIEW_PREFIX.equals(mapNamePrefix)){
107 if (args.getProperties().containsKey("route") || args.getProperties().containsKey("net"))
108 builder.setDoRoads(true);
109 }
110106 builder.makeMap(map, src);
111107
112108 // Collect information on map complete.
4545 private long numTrue; // count how often the evaluation returned true
4646
4747 /** Finalize rules must not have an element type definition so the add method must never be called. */
48 private final static TypeResult finalizeTypeResult = new TypeResult() {
49 public void add(Element el, GType type) {
50 throw new UnsupportedOperationException("Finalize rules must not contain an element type definition.");
51 }
52 };
48 private static final TypeResult finalizeTypeResult = (el, t) -> {
49 throw new UnsupportedOperationException("Finalize rules must not contain an element type definition.");
50 };
5351
5452 public ActionRule(Op expression, List<Action> actions, GType type) {
5553 assert actions != null;
3838 private long numTrue; // count how often the evaluation returned true
3939
4040 /** Finalize rules must not have an element type definition so the add method must never be called. */
41 private final static TypeResult finalizeTypeResult = new TypeResult() {
42 public void add(Element el, GType type) {
43 throw new UnsupportedOperationException("Finalize rules must not contain an element type definition.");
44 }
41 private static final TypeResult finalizeTypeResult = (el, type) -> {
42 throw new UnsupportedOperationException("Finalize rules must not contain an element type definition.");
4543 };
4644
4745 public ExpressionRule(Op expression, GType gtype) {
1616 import java.util.BitSet;
1717 import java.util.HashMap;
1818 import java.util.HashSet;
19 import java.util.Iterator;
2019 import java.util.LinkedHashMap;
2120 import java.util.List;
2221 import java.util.Map;
6362 * @author Steve Ratcliffe
6463 */
6564 public class RuleIndex {
66 private final List<RuleDetails> ruleDetails = new ArrayList<RuleDetails>();
65 private final List<RuleDetails> ruleDetails = new ArrayList<>();
6766
6867 private final Map<Short, TagHelper> tagKeyMap = new HashMap<>();
6968 private TagHelper[] tagKeyArray = null;
8887 merged.or(exists);
8988 merged.or(value);
9089 tagVals.put(val, merged);
91 } else
90 } else {
9291 tagVals.put(val, value);
92 }
9393 }
9494
9595 public BitSet getBitSet(String tagVal) {
144144 assert tagKey > 0;
145145 if (tagKey < tagKeyArray.length){
146146 th = tagKeyArray[tagKey];
147 } else
147 } else {
148148 th = null;
149 }
149150 } else {
150151 th = tagKeyMap.get(tagKey);
151152 }
163164 if (inited)
164165 return;
165166 // This is an index of all rules that start with EXISTS (A=*)
166 Map<String, BitSet> existKeys = new HashMap<String, BitSet>();
167 Map<String, BitSet> existKeys = new HashMap<>();
167168 // This is an index of all rules that start with EQUALS (A=B)
168 Map<String, BitSet> tagVals = new HashMap<String, BitSet>();
169 Map<String, BitSet> tagVals = new HashMap<>();
169170
170171 // This is an index of all rules by the tag name (A).
171 Map<String, BitSet> tagnames = new HashMap<String, BitSet>();
172 Map<String, BitSet> tagnames = new HashMap<>();
172173
173174 // remove unnecessary rules
174175 filterRules();
175
176 buildInitialIndex(existKeys, tagVals, tagnames);
177 findDependingRules(existKeys, tagVals, tagnames);
178
179 // compress the index: create one hash map with one entry for each key
180 for (Map.Entry<String, BitSet> entry : existKeys.entrySet()) {
181 Short skey = TagDict.getInstance().xlate(entry.getKey());
182 tagKeyMap.put(skey, new TagHelper(entry.getValue()));
183 }
184 for (Map.Entry<String, BitSet> entry : tagVals.entrySet()) {
185 String keyString = entry.getKey();
186 int ind = keyString.indexOf('=');
187 if (ind >= 0) {
188 short key = TagDict.getInstance().xlate(keyString.substring(0, ind));
189 String val = keyString.substring(ind + 1);
190 TagHelper th = tagKeyMap.computeIfAbsent(key, k-> new TagHelper(null));
191 th.addTag(val, entry.getValue());
192 }
193 }
194 Optional<Short> minKey = tagKeyMap.keySet().stream().min(Short::compare);
195 if (minKey.isPresent() && minKey.get() > 0) {
196 Optional<Short> maxKey = tagKeyMap.keySet().stream().max(Short::compare);
197 tagKeyArray = new TagHelper[maxKey.get() + 1];
198 for (Map.Entry<Short, TagHelper> entry : tagKeyMap.entrySet()) {
199 tagKeyArray[entry.getKey()] = entry.getValue();
200 }
201 tagKeyMap.clear();
202 }
203
204 inited = true;
205 }
206
207 private void buildInitialIndex(Map<String, BitSet> existKeys, Map<String, BitSet> tagVals,
208 Map<String, BitSet> tagnames) {
176209 for (int i = 0; i < ruleDetails.size(); i++) {
177210 int ruleNumber = i;
178211 RuleDetails rd = ruleDetails.get(i);
189222 String key = keystring.substring(0, ind);
190223 addNumberToMap(tagnames, key, ruleNumber);
191224 } else {
192 assert false: "rule index: no = in keystring " + keystring;
193 }
194 }
195 }
196
225 assert false : "rule index: no = in keystring " + keystring;
226 }
227 }
228 }
229 }
230
231 private void findDependingRules(Map<String, BitSet> existKeys, Map<String, BitSet> tagVals,
232 Map<String, BitSet> tagnames) {
197233 // find the additional rules which might be triggered as a result of actions changing tags.
198234 Map<Integer, BitSet> additionalRules = new LinkedHashMap<>();
199235 for (int i = 0; i < ruleDetails.size(); i++) {
221257 addedRules.or(set);
222258 }
223259 }
224 // Only rules after the current one can be affected
260 // Only rules after the current one can be affected
225261 addedRules.clear(0, ruleNumber);
226262 if (!addedRules.isEmpty()) {
227263 additionalRules.put(ruleNumber, addedRules);
228264 }
229265 }
230
266
231267 // now add all the additional rules to the existing sets
232268 for (Entry<Integer, BitSet> e : additionalRules.entrySet()) {
233269 int ruleNumber = e.getKey();
244280 }
245281 }
246282 }
247
248 }
249
250 // compress the index: create one hash map with one entry for each key
251 for (Map.Entry<String, BitSet> entry : existKeys.entrySet()){
252 Short skey = TagDict.getInstance().xlate(entry.getKey());
253 tagKeyMap.put(skey, new TagHelper(entry.getValue()));
254 }
255 for (Map.Entry<String, BitSet> entry : tagVals.entrySet()){
256 String keyString = entry.getKey();
257 int ind = keyString.indexOf('=');
258 if (ind >= 0) {
259 short key = TagDict.getInstance().xlate(keyString.substring(0, ind));
260 String val = keyString.substring(ind+1);
261 TagHelper th = tagKeyMap.get(key);
262 if (th == null){
263 th = new TagHelper(null);
264 tagKeyMap.put(key, th);
265 }
266 th.addTag(val, entry.getValue());
267 }
268 }
269 Optional<Short> minKey = tagKeyMap.keySet().stream().min(Short::compare);
270 if (minKey.isPresent() && minKey.get() > 0){
271 Optional<Short> maxKey = tagKeyMap.keySet().stream().max(Short::compare);
272 tagKeyArray = new TagHelper[maxKey.get() + 1];
273 for (Map.Entry<Short, TagHelper> entry : tagKeyMap.entrySet()){
274 tagKeyArray[entry.getKey()] = entry.getValue();
275 }
276 tagKeyMap.clear();
277 }
278
279 inited = true;
283 }
280284 }
281285
282286 /**
294298 ruleDetails.addAll(filteredRules);
295299 }
296300
297 private void removeUnused(List<RuleDetails> filteredRules, Set<String> usedIfVars) {
301 private static void removeUnused(List<RuleDetails> filteredRules, Set<String> usedIfVars) {
298302 if (usedIfVars.isEmpty())
299303 return;
300 Iterator<RuleDetails> iter = filteredRules.iterator();
301 while (iter.hasNext()) {
302 RuleDetails rd = iter.next();
304 filteredRules.removeIf(rd -> {
303305 if (rd.getRule() instanceof ActionRule) {
304306 ActionRule ar = (ActionRule) rd.getRule();
305307 if (ar.toString().contains("set " + RuleFileReader.IF_PREFIX)) {
306 boolean needed = false;
307308 for (String ifVars : usedIfVars) {
308309 if (ar.toString().contains("set " + ifVars)) {
309 needed = true;
310 return false;
310311 }
311312 }
312 if (!needed)
313 iter.remove();
314 }
315 }
316 }
317 }
318
319 private void findIfVarUsage(Rule rule, Set<String> usedIfVars) {
313 return true;
314 }
315 }
316 return false;
317 });
318 }
319
320 private static void findIfVarUsage(Rule rule, Set<String> usedIfVars) {
320321 if (rule == null)
321322 return;
322 // if (rule.getFinalizeRule() != null)
323 // findIfVarUsage(rule.getFinalizeRule(), usedIfVars);
324323 Op expr = null;
325 if (rule instanceof ExpressionRule)
324 if (rule instanceof ExpressionRule)
326325 expr = ((ExpressionRule) rule).getOp();
327326 else if (rule instanceof ActionRule)
328327 expr = ((ActionRule) rule).getOp();
335334 }
336335
337336 private static void addNumberToMap(Map<String, BitSet> map, String key, int ruleNumber) {
338 BitSet set = map.get(key);
339 if (set == null) {
340 set = new BitSet();
341 map.put(key, set);
342 }
343 set.set(ruleNumber);
337 map.computeIfAbsent(key, k -> new BitSet()).set(ruleNumber);
344338 }
345339
346340 public List<RuleDetails> getRuleDetails() {
5252 int cacheId;
5353 boolean compiled = false;
5454
55 private final static short executeFinalizeRulesTagKey = TagDict.getInstance().xlate("mkgmap:execute_finalize_rules");
55 private static final short executeFinalizeRulesTagKey = TagDict.getInstance().xlate("mkgmap:execute_finalize_rules");
5656
5757 private RuleIndex index = new RuleIndex();
58 private final Set<String> usedTags = new HashSet<String>();
58 private final Set<String> usedTags = new HashSet<>();
5959
6060 @Override
6161 public void resolveType(Element el, TypeResult result) {
8585 // Get all the rules that could match from the index.
8686 BitSet candidates = new BitSet();
8787 for (Entry<Short, String> tagEntry : el.getFastTagEntryIterator()) {
88 BitSet rules = index.getRulesForTag(tagEntry.getKey(), tagEntry.getValue());
89 if (rules != null && !rules.isEmpty() )
90 candidates.or(rules);
88 BitSet bsRules = index.getRulesForTag(tagEntry.getKey(), tagEntry.getValue());
89 if (bsRules != null && !bsRules.isEmpty() )
90 candidates.or(bsRules);
9191 }
9292 Rule lastRule = null;
93 for (int i = candidates.nextSetBit(0); i >= 0; i = candidates.nextSetBit(i + 1)) {
93 for (int i = candidates.nextSetBit(0); i >= 0; i = candidates.nextSetBit(i + 1)) {
9494 a.reset();
9595 lastRule = rules[i];
9696 cacheId = lastRule.resolveType(cacheId, el, a);
9797 if (a.isResolved())
9898 return cacheId;
9999 }
100 if (lastRule != null && lastRule.getFinalizeRule() != null){
101 if ("true".equals(el.getTag(executeFinalizeRulesTagKey))){
102 cacheId = lastRule.getFinalizeRule().resolveType(cacheId, el, a);
103 }
100 if (lastRule != null && lastRule.getFinalizeRule() != null
101 && "true".equals(el.getTag(executeFinalizeRulesTagKey))) {
102 cacheId = lastRule.getFinalizeRule().resolveType(cacheId, el, a);
104103 }
105104 return cacheId;
106105 }
162161
163162 index = newIndex;
164163 rules = newIndex.getRules();
165 //System.out.println("Merging used tags: "
166 // + getUsedTags().toString()
167 // + " + "
168 // + rs.getUsedTags());
169164 addUsedTags(rs.usedTags);
170 //System.out.println("Result: " + getUsedTags().toString());
171165 compiled = false;
172166 }
173167
194188 * make sure that all rules use the same instance of these common
195189 * sub-expressions.
196190 */
197 private void compile(){
198 HashMap<String, Op> tests = new HashMap<String, Op>();
199
200 for (Rule rule:rules){
191 private void compile() {
192 HashMap<String, Op> tests = new HashMap<>();
193
194 for (Rule rule : rules) {
201195 Op op;
202196 if (rule instanceof ExpressionRule)
203 op = ((ExpressionRule)rule).getOp();
197 op = ((ExpressionRule) rule).getOp();
204198 else if (rule instanceof ActionRule)
205199 op = ((ActionRule) rule).getOp();
206200 else {
207201 log.error("unexpected rule instance");
208202 continue;
209203 }
210 if (op instanceof AbstractBinaryOp){
204 if (op instanceof AbstractBinaryOp) {
211205 AbstractBinaryOp binOp = (AbstractBinaryOp) op;
212206 binOp.setFirst(compileOp(tests, binOp.getFirst()));
213207 binOp.setSecond(compileOp(tests, binOp.getSecond()));
214208 op = compileOp(tests, binOp);
215 } else if (op instanceof AbstractOp){
209 } else if (op instanceof AbstractOp) {
216210 op = compileOp(tests, op);
217 } else if (op instanceof LinkedBinaryOp){
211 } else if (op instanceof LinkedBinaryOp) {
218212 ((LinkedBinaryOp) op).setFirst(compileOp(tests, ((LinkedBinaryOp) op).getFirst()));
219213 ((LinkedBinaryOp) op).setSecond(compileOp(tests, ((LinkedBinaryOp) op).getSecond()));
220214 } else if (op instanceof LinkedOp) {
225219 continue;
226220 }
227221 if (rule instanceof ExpressionRule)
228 ((ExpressionRule)rule).setOp(op);
222 ((ExpressionRule) rule).setOp(op);
229223 else if (rule instanceof ActionRule)
230224 ((ActionRule) rule).setOp(op);
231225 else {
232226 log.error("unexpected rule instance");
233 continue;
234227 }
235228 }
236229 cacheId = 0;
295288 // that we have rules to which the finalize rules can be applied
296289 throw new IllegalStateException("First call prepare() before setting the finalize rules");
297290 }
298 for (Rule rule : rules){
291 for (Rule rule : rules) {
299292 if (rule.containsExpression(exp))
300293 return true;
301294 }
302 if (finalizeRule != null){
303 if (finalizeRule.containsExpression(exp))
304 return true;
305 }
306 return false;
295 return finalizeRule != null && finalizeRule.containsExpression(exp);
307296 }
308297
309298 public BitSet getRules(Element el) {
312301 // new element, invalidate all caches
313302 cacheId++;
314303
315 // Get all the rules that could match from the index.
304 // Get all the rules that could match from the index.
316305 BitSet candidates = new BitSet();
317306 for (Entry<Short, String> tagEntry : el.getFastTagEntryIterator()) {
318 BitSet rules = index.getRulesForTag(tagEntry.getKey(), tagEntry.getValue());
319 if (rules != null && !rules.isEmpty() )
320 candidates.or(rules);
307 BitSet bsRules = index.getRulesForTag(tagEntry.getKey(), tagEntry.getValue());
308 if (bsRules != null && !bsRules.isEmpty())
309 candidates.or(bsRules);
321310 }
322311 return candidates;
323312 }
3939 import uk.me.parabola.imgfmt.ExitException;
4040 import uk.me.parabola.imgfmt.Utils;
4141 import uk.me.parabola.log.Logger;
42 import uk.me.parabola.mkgmap.Option;
43 import uk.me.parabola.mkgmap.OptionProcessor;
4442 import uk.me.parabola.mkgmap.Options;
4543 import uk.me.parabola.mkgmap.general.LevelInfo;
4644 import uk.me.parabola.mkgmap.general.LineAdder;
47 import uk.me.parabola.mkgmap.general.MapLine;
4845 import uk.me.parabola.mkgmap.reader.osm.FeatureKind;
4946 import uk.me.parabola.mkgmap.reader.osm.Rule;
5047 import uk.me.parabola.mkgmap.reader.osm.Style;
7370 private static final int VERSION = 1;
7471
7572 // General options just have a value and don't need any special processing.
76 private static final Collection<String> OPTION_LIST = new ArrayList<String>(
73 private static final Collection<String> OPTION_LIST = new ArrayList<>(
7774 Arrays.asList("levels", "overview-levels", "extra-used-tags"));
7875
7976 // File names
9390 private StyleInfo info = new StyleInfo();
9491
9592 // Set if this style is based on another one.
96 private final List<StyleImpl> baseStyles = new ArrayList<StyleImpl>();
93 private final List<StyleImpl> baseStyles = new ArrayList<>();
9794
9895 // Options from the option file that are used outside this file.
99 private final Map<String, String> generalOptions = new HashMap<String, String>();
96 private final Map<String, String> generalOptions = new HashMap<>();
10097
10198 private final RuleSet lines = new RuleSet();
10299 private final RuleSet polygons = new RuleSet();
150147
151148 // read overlays before the style rules to be able to ignore overlaid "wrong" types.
152149 readOverlays();
153
154 readRules();
150
151 readRules(props.getProperty("levels"));
155152
156153 ListIterator<StyleImpl> listIterator = baseStyles.listIterator(baseStyles.size());
157154 while (listIterator.hasPrevious())
202199 LineAdder adder = null;
203200
204201 if (overlays != null) {
205 adder = new LineAdder() {
206 public void add(MapLine element) {
207 overlays.addLine(element, lineAdder);
208 }
209 };
202 adder = element -> overlays.addLine(element, lineAdder);
210203 }
211204 return adder;
212205 }
213206
214207 public Set<String> getUsedTags() {
215 Set<String> set = new HashSet<String>();
208 Set<String> set = new HashSet<>();
216209 set.addAll(relations.getUsedTags());
217210 set.addAll(lines.getUsedTags());
218211 set.addAll(polygons.getUsedTags());
223216 // around situations that we haven't thought of - the style is expected
224217 // to get it right for itself.
225218 String s = getOption("extra-used-tags");
226 if (s != null && s.trim().isEmpty() == false)
219 if (s != null && !s.trim().isEmpty())
227220 set.addAll(Arrays.asList(COMMA_OR_SPACE_PATTERN.split(s)));
228221
229222 // There are a lot of tags that are used within mkgmap that
230 InputStream is = this.getClass().getResourceAsStream("/styles/builtin-tag-list");
231 try {
223 try (InputStream is = this.getClass().getResourceAsStream("/styles/builtin-tag-list");) {
232224 if (is != null) {
233225 BufferedReader br = new BufferedReader(new InputStreamReader(is));
234 //System.out.println("Got built in list");
226 // System.out.println("Got built in list");
235227 String line;
236228 while ((line = br.readLine()) != null) {
237229 line = line.trim();
238230 if (line.startsWith("#"))
239231 continue;
240 //System.out.println("adding " + line);
232 // System.out.println("adding " + line);
241233 set.add(line);
242234 }
243235 }
244236 } catch (IOException e) {
245237 // the file doesn't exist, this is ok but unlikely
246238 System.err.println("warning: built in tag list not found");
247 } finally {
248 Utils.closeFile(is);
249239 }
250240 return set;
251241 }
252242
253 private void readRules() {
254 String l = generalOptions.get("levels");
243 private void readRules(String l) {
244 if (l == null)
245 l = generalOptions.get("levels");
255246 if (l == null)
256247 l = LevelInfo.DEFAULT_LEVELS;
257248 LevelInfo[] levels = LevelInfo.createFromString(l);
258 if (performChecks){
259 if (levels[0].getBits() <= 10){
260 System.err.println("Warning: Resolution values <= 10 may confuse MapSource: " + l);
261 }
249 if (performChecks && levels[0].getBits() <= 10) {
250 System.err.println("Warning: Resolution values <= 10 may confuse MapSource: " + l);
262251 }
263252 l = generalOptions.get("overview-levels");
264253 if (l != null){
272261 System.err.println("Warning: Overview level not higher than highest normal level. " + l);
273262 }
274263 }
275 List<LevelInfo> tmp = new ArrayList<LevelInfo>();
264 List<LevelInfo> tmp = new ArrayList<>();
276265 tmp.addAll(Arrays.asList(levels));
277266 tmp.addAll(Arrays.asList(ovLevels));
278267 levels = tmp.toArray(new LevelInfo[tmp.size()]);
319308 private void readOptions() {
320309 try {
321310 Reader r = fileLoader.open(FILE_OPTIONS);
322 Options opts = new Options(new OptionProcessor() {
323 public void processOption(Option opt) {
324 String key = opt.getOption();
325 String val = opt.getValue();
326 if (key.equals("name-tag-list")) {
327 if ("name".equals(val) == false){
328 System.err.println("Warning: option name-tag-list used in the style options is ignored. "
329 + "Please use only the command line option to specify this value." );
330 }
331 } else if (OPTION_LIST.contains(key)) {
332 // Simple options that have string value. Perhaps we should allow
333 // anything here?
334 generalOptions.put(key, val);
311 Options opts = new Options(opt -> {
312 String key = opt.getOption();
313 String val = opt.getValue();
314 if (key.equals("name-tag-list")) {
315 if (!"name".equals(val)) {
316 System.err.println("Warning: option name-tag-list used in the style options is ignored. "
317 + "Please use only the command line option to specify this value.");
335318 }
319 } else if (OPTION_LIST.contains(key)) {
320 // Simple options that have string value. Perhaps we should allow
321 // anything here?
322 generalOptions.put(key, val);
336323 }
337324 });
338325
351338 Reader br = new BufferedReader(fileLoader.open(FILE_INFO));
352339 info = new StyleInfo();
353340
354 Options opts = new Options(new OptionProcessor() {
355 public void processOption(Option opt) {
356 String word = opt.getOption();
357 String value = opt.getValue();
358 if (word.equals("summary"))
359 info.setSummary(value);
360 else if (word.equals("version")) {
361 info.setVersion(value);
362 } else if (word.equals("base-style")) {
363 info.addBaseStyleName(value);
364 } else if (word.equals("description")) {
365 info.setLongDescription(value);
366 }
367
341 Options opts = new Options(opt -> {
342 String word = opt.getOption();
343 String value = opt.getValue();
344 if (word.equals("summary"))
345 info.setSummary(value);
346 else if (word.equals("version")) {
347 info.setVersion(value);
348 } else if (word.equals("base-style")) {
349 info.addBaseStyleName(value);
350 } else if (word.equals("description")) {
351 info.setLongDescription(value);
368352 }
369353 });
370354
108108 // limit arc lengths to what can be handled by RouteArc
109109 private static final int MAX_ARC_LENGTH = 20450000; // (1 << 22) * 16 / 3.2808 ~ 20455030*/
110110
111 private static final int MAX_NODES_IN_WAY = 64; // possibly could be increased
111 /** Number of routing nodes in way, possibly could be increased, not a hard limit in IMG format.
112 * See also RoadDef.MAX_NUMBER_NODES.
113 */
114 private static final int MAX_NODES_IN_WAY = 64;
112115
113116 // nodeIdMap maps a Coord into a CoordNode
114117 private IdentityHashMap<Coord, CoordNode> nodeIdMap = new IdentityHashMap<>();
115118
116 public final static String WAY_POI_NODE_IDS = "mkgmap:way-poi-node-ids";
119 public static final String WAY_POI_NODE_IDS = "mkgmap:way-poi-node-ids";
117120 private final HashMap<Integer, Map<String,MapPoint>> pointMap;
118121
119122 /** boundary ways with admin_level=2 */
127130 private HashMap<Long, ConvertedWay> modifiedRoads = new HashMap<>();
128131 private HashSet<Long> deletedRoads = new HashSet<>();
129132
130 private int nextNodeId = 1;
131133 private int nextRoadId = 1;
132134
133135 private HousenumberGenerator housenumberGenerator;
153155 private final boolean mergeRoads;
154156 private final boolean routable;
155157 private final Tags styleOptionTags;
156 private final static String STYLE_OPTION_PREF = "mkgmap:option:";
158 private static final String STYLE_OPTION_PREF = "mkgmap:option:";
157159 private final PrefixSuffixFilter prefixSuffixFilter;
158160
159 private LineAdder lineAdder = new LineAdder() {
160 public void add(MapLine element) {
161 if (element instanceof MapRoad){
162 prefixSuffixFilter.filter((MapRoad) element);
163 collector.addRoad((MapRoad) element);
164 }
165 else
166 collector.addLine(element);
167 }
168 };
161 private LineAdder lineAdder;
169162
170163 public StyledConverter(Style style, MapCollector collector, EnhancedProperties props) {
171164 this.collector = collector;
207200
208201 checkRoundabouts = props.getProperty("check-roundabouts",false);
209202 reportDeadEnds = props.getProperty("report-dead-ends", 1);
210
203 prefixSuffixFilter = new PrefixSuffixFilter(props);
204
205 lineAdder = line -> {
206 if (line instanceof MapRoad) {
207 prefixSuffixFilter.filter((MapRoad) line);
208 collector.addRoad((MapRoad) line);
209 } else {
210 collector.addLine(line);
211 }
212 };
211213 LineAdder overlayAdder = style.getOverlays(lineAdder);
212214 if (overlayAdder != null)
213215 lineAdder = overlayAdder;
214216 linkPOIsToWays = props.getProperty("link-pois-to-ways", false);
215217
216218 // undocumented option - usually used for debugging only
217 mergeRoads = props.getProperty("no-mergeroads", false) == false;
219 mergeRoads = !props.getProperty("no-mergeroads", false);
218220 routable = props.containsKey("route");
219221 String styleOption= props.getProperty("style-option",null);
220222 styleOptionTags = parseStyleOption(styleOption);
221 prefixSuffixFilter = new PrefixSuffixFilter(props);
222223
223224 // control calculation of extra nodes in NOD3 / NOD4
224225 admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2);
254255 // flag options used in style but not specified in --style-option
255256 if (style.getUsedTags() != null) {
256257 for (String s : style.getUsedTags()) {
257 if (s != null && s.startsWith(STYLE_OPTION_PREF)) {
258 if (styleTags.get(s) == null) {
259 System.err.println("Warning: Option style-options doesn't specify '"
260 + s.replaceFirst(STYLE_OPTION_PREF, "") + "' (for " + s + ")");
261 }
258 if (s != null && s.startsWith(STYLE_OPTION_PREF) && styleTags.get(s) == null) {
259 System.err.println("Warning: Option style-options doesn't specify '"
260 + s.replaceFirst(STYLE_OPTION_PREF, "") + "' (for " + s + ")");
262261 }
263262 }
264263 }
267266
268267 /** One type result for ways to avoid recreating one for each way. */
269268 private final WayTypeResult wayTypeResult = new WayTypeResult();
270 private class WayTypeResult implements TypeResult
271 {
269
270 private class WayTypeResult implements TypeResult {
272271 private Way way;
273272 /** flag if the rule was fired */
274273 private boolean matched;
280279
281280 public void add(Element el, GType type) {
282281 this.matched = true;
283 if (type.isContinueSearch()) {
282 if (type.isContinueSearch() && el == way) {
284283 // If not already copied, do so now
285 if (el == way)
286 el = way.copy();
284 el = way.copy();
287285 }
288286 postConvertRules(el, type);
289 if (type.isRoad() == false)
290 housenumberGenerator.addWay((Way)el);
287 if (!type.isRoad())
288 housenumberGenerator.addWay((Way) el);
291289 addConvertedWay((Way) el, type);
292290 }
291
292 private void addConvertedWay(Way way, GType foundType) {
293 if (foundType.getFeatureKind() == FeatureKind.POLYGON){
294 addShape(way, foundType);
295 return;
296 }
297
298 boolean wasReversed = false;
299 String oneWay = way.getTag(onewayTagKey);
300 if (oneWay != null){
301 if("-1".equals(oneWay) || "reverse".equals(oneWay)) {
302 // it's a oneway street in the reverse direction
303 // so reverse the order of the nodes and change
304 // the oneway tag to "yes"
305 way.reverse();
306 wasReversed = true;
307 way.addTag(onewayTagKey, "yes");
308 }
309
310 if (way.tagIsLikeYes(onewayTagKey)) {
311 way.addTag(onewayTagKey, "yes");
312 if (foundType.isRoad() && checkFixmeCoords(way) )
313 way.addTag("mkgmap:dead-end-check", "false");
314 } else {
315 way.deleteTag(onewayTagKey);
316 }
317 }
318 ConvertedWay cw = new ConvertedWay(lineIndex++, way, foundType);
319 cw.setReversed(wasReversed);
320 if (cw.isRoad()){
321 roads.add(cw);
322 numRoads++;
323 if (!cw.isFerry()) {
324 String country = way.getTag(countryTagKey);
325 if (country != null) {
326 boolean drivingSideIsLeft = LocatorConfig.get().getDriveOnLeftFlag(country);
327 if (drivingSideIsLeft)
328 numDriveOnLeftRoads++;
329 else
330 numDriveOnRightRoads++;
331 if (driveOnLeft != null && drivingSideIsLeft != driveOnLeft)
332 log.warn("wrong driving side", way.toBrowseURL());
333 if (log.isDebugEnabled())
334 log.debug("assumed driving side is", (drivingSideIsLeft ? "left" : "right"),
335 way.toBrowseURL());
336 } else {
337 numDriveOnSideUnknown++;
338 }
339 }
340 if (cw.isRoundabout() && wasReversed) {
341 log.warn("Roundabout", way.getId(),
342 "has reverse oneway tag (" + way.getFirstPoint().toOSMURL() + ")");
343 }
344 lastRoadId = way.getId();
345 } else {
346 lines.add(cw);
347 }
348 }
349
350 private void addShape(Way way, GType gt) {
351 // This is deceptively simple. At the time of writing, splitter only retains points that are within
352 // the tile and some distance around it. Therefore a way that is closed in reality may not be closed
353 // as we see it in its incomplete state.
354 //
355 if (!way.hasIdenticalEndPoints() && way.hasEqualEndPoints())
356 log.error("shape is not closed with identical points " + way.getId());
357 if (!way.hasIdenticalEndPoints())
358 return;
359 // TODO: split self intersecting polygons?
360 final MapShape shape = new MapShape(way.getId());
361 elementSetup(shape, gt, way);
362 shape.setPoints(way.getPoints());
363
364 long areaVal = 0;
365 String tagStringVal = way.getTag(drawLevelTagKey);
366 if (tagStringVal != null) {
367 try {
368 areaVal = Integer.parseInt(tagStringVal);
369 if (areaVal < 1 || areaVal > 100) {
370 log.error("mkgmap:drawLevel must be in range 1..100, not", areaVal);
371 areaVal = 0;
372 } else if (areaVal <= 50) {
373 areaVal = Long.MAX_VALUE - areaVal; // 1 => MAX_VALUE-1, 50 => MAX_VALUE-50
374 } else {
375 areaVal = 101 - areaVal; // 51 => 50, 100 => 1
376 }
377 } catch (NumberFormatException e) {
378 log.error("mkgmap:drawLevel invalid integer:", tagStringVal);
379 }
380 }
381 if (areaVal == 0)
382 areaVal = way.getFullArea();
383 shape.setFullArea(areaVal);
384
385 clipper.clipShape(shape, collector);
386 }
387
388 /**
389 * Check if the first or last of the coords of the way has the fixme flag set
390 * @param way the way to check
391 * @return true if fixme flag was found
392 */
393 private boolean checkFixmeCoords(Way way) {
394 return way.getFirstPoint().isFixme() || way.getLastPoint().isFixme();
395 }
396
293397
294398 /**
295399 * Retrieves if a rule of the style matched and the way is converted.
310414 *
311415 * @param way The OSM way.
312416 */
313 private final static short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter");
314 private final static short makeCycleWayTagKey = TagDict.getInstance().xlate("mkgmap:make-cycle-way");
417 private static final short styleFilterTagKey = TagDict.getInstance().xlate("mkgmap:stylefilter");
418 private static final short makeCycleWayTagKey = TagDict.getInstance().xlate("mkgmap:make-cycle-way");
315419 private long lastRoadId = 0;
316420 private int lineCacheId = 0;
317421 private BitSet routingWarningWasPrinted = new BitSet();
354458 }
355459 wayTypeResult.setWay(way);
356460 lineCacheId = rules.resolveType(lineCacheId, way, wayTypeResult);
357 if (wayTypeResult.isMatched() == false) {
461 if (!wayTypeResult.isMatched()) {
358462 // no match found but we have to keep it for house number processing
359463 housenumberGenerator.addWay(way);
360464 }
361465 if (cycleWay != null){
362466 wayTypeResult.setWay(cycleWay);
363467 lineCacheId = rules.resolveType(lineCacheId, cycleWay, wayTypeResult);
364 if (wayTypeResult.isMatched() == false) {
468 if (!wayTypeResult.isMatched()) {
365469 // no match found but we have to keep it for house number processing
366470 housenumberGenerator.addWay(cycleWay);
367471 }
372476 } else {
373477 // way was added as road, check if we also have non-routable lines for the way
374478 // which have to be skipped by WrongAngleFixer
375 for (int i = lines.size()-1; i >= 0; --i){
376 ConvertedWay cw = lines.get(i);
377 if (cw.getWay().getId() == way.getId()){
479 for (int i = lines.size() - 1; i >= 0; --i) {
480 ConvertedWay cw = lines.get(i);
481 if (cw.getWay().getId() == way.getId()) {
378482 cw.setOverlay(true);
379483 int lineType = cw.getGType().getType();
380 if (GType.isSpecialRoutableLineType(lineType) && cw.getGType().getMinLevel() == 0){
381 if (!routingWarningWasPrinted.get(lineType)){
382 log.error("routable type", GType.formatType(cw.getGType().getType()),
383 "is used with a non-routable way which was also added as a routable way. This leads to routing errors.",
384 "Try --check-styles to check the style.");
385 routingWarningWasPrinted.set(lineType);
386 }
484 if (GType.isSpecialRoutableLineType(lineType) && cw.getGType().getMinLevel() == 0
485 && !routingWarningWasPrinted.get(lineType)) {
486 log.error("routable type", GType.formatType(cw.getGType().getType()),
487 "is used with a non-routable way which was also added as a routable way. This leads to routing errors.",
488 "Try --check-styles to check the style.");
489 routingWarningWasPrinted.set(lineType);
387490 }
388 }
389 else
491 } else {
390492 break;
493 }
391494 }
392495 }
393496 }
397500 String admLevelString = el.getTag("admin_level");
398501 if (admLevelString != null) {
399502 try {
400 int al = Integer.valueOf(admLevelString);
503 int al = Integer.parseInt(admLevelString);
401504 return al <= admLevelNod3;
402505 } catch (NumberFormatException e) {
506 // ignore invalid osm data
403507 }
404508 }
405509 }
407511 }
408512
409513 private int lineIndex = 0;
410 private final static short onewayTagKey = TagDict.getInstance().xlate("oneway");
411 private void addConvertedWay(Way way, GType foundType) {
412 if (foundType.getFeatureKind() == FeatureKind.POLYGON){
413 addShape(way, foundType);
414 return;
415 }
416
417 boolean wasReversed = false;
418 String oneWay = way.getTag(onewayTagKey);
419 if (oneWay != null){
420 if("-1".equals(oneWay) || "reverse".equals(oneWay)) {
421 // it's a oneway street in the reverse direction
422 // so reverse the order of the nodes and change
423 // the oneway tag to "yes"
424 way.reverse();
425 wasReversed = true;
426 way.addTag(onewayTagKey, "yes");
427 }
428
429 if (way.tagIsLikeYes(onewayTagKey)) {
430 way.addTag(onewayTagKey, "yes");
431 if (foundType.isRoad() && checkFixmeCoords(way) )
432 way.addTag("mkgmap:dead-end-check", "false");
433 } else
434 way.deleteTag(onewayTagKey);
435 }
436 ConvertedWay cw = new ConvertedWay(lineIndex++, way, foundType);
437 cw.setReversed(wasReversed);
438 if (cw.isRoad()){
439 roads.add(cw);
440 numRoads++;
441 if (cw.isFerry() == false){
442 String country = way.getTag(countryTagKey);
443 if (country != null) {
444 boolean drivingSideIsLeft =LocatorConfig.get().getDriveOnLeftFlag(country);
445 if (drivingSideIsLeft)
446 numDriveOnLeftRoads++;
447 else
448 numDriveOnRightRoads++;
449 if (driveOnLeft != null && drivingSideIsLeft != driveOnLeft)
450 log.warn("wrong driving side",way.toBrowseURL());
451 if (log.isDebugEnabled())
452 log.debug("assumed driving side is",(drivingSideIsLeft ? "left" : "right"),way.toBrowseURL());
453 } else
454 numDriveOnSideUnknown++;
455 }
456 if (cw.isRoundabout()) {
457 if (wasReversed)
458 log.warn("Roundabout", way.getId(), "has reverse oneway tag (" + way.getFirstPoint().toOSMURL() + ")");
459 }
460 lastRoadId = way.getId();
461 }
462 else
463 lines.add(cw);
464 }
514 private static final short onewayTagKey = TagDict.getInstance().xlate("oneway");
465515
466516 /** One type result for nodes to avoid recreating one for each node. */
467517 private NodeTypeResult nodeTypeResult = new NodeTypeResult();
478528
479529 public void add(Element el, GType type) {
480530 this.matched = true;
481 if (type.isContinueSearch()) {
531 if (type.isContinueSearch() && el == node) {
482532 // If not already copied, do so now
483 if (el == node)
484 el = node.copy();
485 }
486
533 el = node.copy();
534 }
535
487536 postConvertRules(el, type);
488 housenumberGenerator.addNode((Node)el);
537 housenumberGenerator.addNode((Node) el);
489538 addPoint((Node) el, type);
539 }
540
541 private void addPoint(Node node, GType gt) {
542 if (!clipper.contains(node.getLocation()))
543 return;
544
545 // to handle exit points we use a subclass of MapPoint
546 // to carry some extra info (a reference to the
547 // motorway associated with the exit)
548 MapPoint mp;
549 int type = gt.getType();
550 if (type >= 0x2000 && type < 0x2800) {
551 String ref = node.getTag(Exit.TAG_ROAD_REF);
552 String id = node.getTag("mkgmap:osmid");
553 if (ref != null) {
554 String to = node.getTag(Exit.TAG_TO);
555 MapExitPoint mep = new MapExitPoint(ref, to);
556 String fd = node.getTag(Exit.TAG_FACILITY);
557 if (fd != null)
558 mep.setFacilityDescription(fd);
559 if (id != null)
560 mep.setOSMId(id);
561 mp = mep;
562 } else {
563 mp = new MapPoint();
564 if ("motorway_junction".equals(node.getTag("highway")))
565 log.warn("Motorway exit", node.getName(), "(" + node.toBrowseURL()
566 + ") has no (motorway) ref! (either make the exit share a node with the motorway or specify the motorway ref with a",
567 Exit.TAG_ROAD_REF, "tag)");
568 }
569 } else {
570 mp = new MapPoint();
571 }
572 elementSetup(mp, gt, node);
573 mp.setLocation(node.getLocation());
574
575 boolean dupPOI = checkDuplicatePOI(mp);
576 if (dupPOI) {
577 if (log.isInfoEnabled()) {
578 if (FakeIdGenerator.isFakeId(node.getId()))
579 log.info("ignoring duplicate POI with type", GType.formatType(type), mp.getName(),
580 "for generated element with id", node.getId(), "at", mp.getLocation().toDegreeString());
581 else
582 log.info("ignoring duplicate POI with type", GType.formatType(type), mp.getName(),
583 "for element", node.toBrowseURL());
584 }
585 return;
586 }
587
588 collector.addPoint(mp);
589 }
590
591 /**
592 * Check if we already have added a point with the same type + name and equal
593 * location.
594 *
595 * @param mp
596 * @return
597 */
598 private boolean checkDuplicatePOI(MapPoint mp) {
599 Map<String, MapPoint> typeMap = pointMap.get(mp.getType());
600 if (typeMap == null) {
601 typeMap = new HashMap<>();
602 pointMap.put(mp.getType(), typeMap);
603 }
604 MapPoint old = typeMap.get(mp.getName());
605 if (old == null) {
606 typeMap.put(mp.getName(), mp);
607 } else {
608 if (old.getLocation().equals(mp.getLocation()))
609 return true;
610 }
611 return false;
490612 }
491613
492614 /**
514636
515637 nodeTypeResult.setNode(node);
516638 nodeRules.resolveType(node, nodeTypeResult);
517 if (nodeTypeResult.isMatched() == false) {
639 if (!nodeTypeResult.isMatched()) {
518640 // no match found but we have to keep it for house number processing
519641 housenumberGenerator.addNode(node);
520642 }
606728 */
607729 private void removeRestrictionsWithWay(Level logLevel, Way way, String reason){
608730 List<RestrictionRelation> rrList = wayRelMap.get(way.getId());
609 for (RestrictionRelation rr : rrList){
610 if (rr.isValidWithoutWay(way.getId()) == false){
611 if (log.isLoggable(logLevel)){
612 log.log(logLevel, "restriction",rr.toBrowseURL()," is ignored because referenced way",way.toBrowseURL(),reason);
731 for (RestrictionRelation rr : rrList) {
732 if (!rr.isValidWithoutWay(way.getId())) {
733 if (log.isLoggable(logLevel)) {
734 log.log(logLevel, "restriction", rr.toBrowseURL(), " is ignored because referenced way",
735 way.toBrowseURL(), reason);
613736 }
614737 rr.setInvalid();
615738 restrictions.remove(rr);
740863 * road network.
741864 */
742865 private void mergeRoads() {
743 if (mergeRoads == false) {
866 if (mergeRoads) {
867 RoadMerger merger = new RoadMerger();
868 roads = merger.merge(roads, restrictions);
869 } else {
744870 log.info("Merging roads is disabled");
745 return;
746 }
747
748 RoadMerger merger = new RoadMerger();
749 roads = merger.merge(roads, restrictions);
871 }
750872 }
751873
752874 public void end() {
811933 if (cw.isValid())
812934 addRoad(cw);
813935 }
814 housenumberGenerator.generate(lineAdder, nextNodeId);
936 housenumberGenerator.generate(lineAdder);
815937 housenumberGenerator = null;
816938
817939 if (routable)
9121034 *
9131035 */
9141036 private void checkRoundabout(ConvertedWay cw) {
915 if (cw.isRoundabout() == false)
1037 if (!cw.isRoundabout())
9161038 return;
9171039 Way way = cw.getWay();
9181040 List<Coord> points = way.getPoints();
9561078 boolean clockwise = dir > 0;
9571079 if (points.get(0) == points.get(points.size() - 1)) {
9581080 // roundabout is a loop
959 if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) {
960 log.warn("Roundabout "
961 + way.getId()
962 + " direction is wrong - reversing it (see "
1081 if (driveOnLeft && !clockwise || !driveOnLeft && clockwise) {
1082 log.warn("Roundabout " + way.getId() + " direction is wrong - reversing it (see "
9631083 + centre.toOSMURL() + ")");
9641084 way.reverse();
9651085 }
966 } else if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) {
1086 } else if (driveOnLeft && !clockwise || !driveOnLeft && clockwise) {
9671087 // roundabout is a line
968 log.warn("Roundabout segment " + way.getId()
969 + " direction looks wrong (see "
1088 log.warn("Roundabout segment " + way.getId() + " direction looks wrong (see "
9701089 + points.get(0).toOSMURL() + ")");
9711090 }
9721091 }
9731092 }
9741093 }
975
9761094
9771095 /**
9781096 * If POI changes access restrictions (e.g. bollards), create corresponding
9961114 CoordNode lastNode = null;
9971115 for (Coord co: way.getPoints()){
9981116 // not 100% fail safe: points may have been replaced before
999 if (co instanceof CoordNode == false)
1117 if (!(co instanceof CoordNode))
10001118 continue;
10011119 CoordNode cn = (CoordNode) co;
10021120 if (p.highPrecEquals(cn)){
10201138 log.error("Did not find CoordPOI node at " + p.toOSMURL() + " in ways " + wayList);
10211139 continue;
10221140 }
1023 if (viaIsUnique == false){
1141 if (!viaIsUnique){
10241142 log.error("Found multiple points with equal coords as CoordPOI at " + p.toOSMURL());
10251143 continue;
10261144 }
10671185 for (long id : rr.getWayIds())
10681186 wayRelMap.add(id, rr);
10691187 }
1070 } else if (addBoundaryNodesAtAdminBoundaries) {
1071 if (relation instanceof MultiPolygonRelation || "boundary".equals(relation.getTag("type"))) {
1072 if (isNod3Border(relation)) {
1073 for (Entry<String, Element> e : relation.getElements()) {
1074 if (FakeIdGenerator.isFakeId(e.getValue().getId()))
1075 continue;
1076 if (e.getValue() instanceof Way)
1077 borders.add((Way) e.getValue());
1078 }
1079 }
1188 } else if (addBoundaryNodesAtAdminBoundaries
1189 && (relation instanceof MultiPolygonRelation || "boundary".equals(relation.getTag("type")))
1190 && isNod3Border(relation)) {
1191 for (Entry<String, Element> e : relation.getElements()) {
1192 if (FakeIdGenerator.isFakeId(e.getValue().getId()))
1193 continue;
1194 if (e.getValue() instanceof Way)
1195 borders.add((Way) e.getValue());
10801196 }
10811197 }
10821198 }
11271243 clipper.clipLine(line, lineAdder);
11281244 }
11291245
1130 private void addShape(Way way, GType gt) {
1131 // This is deceptively simple. At the time of writing, splitter only retains points that are within
1132 // the tile and some distance around it. Therefore a way that is closed in reality may not be closed
1133 // as we see it in its incomplete state.
1134 //
1135 if (!way.hasIdenticalEndPoints() && way.hasEqualEndPoints())
1136 log.error("shape is not closed with identical points " + way.getId());
1137 if (!way.hasIdenticalEndPoints())
1138 return;
1139 // TODO: split self intersecting polygons?
1140 final MapShape shape = new MapShape(way.getId());
1141 elementSetup(shape, gt, way);
1142 shape.setPoints(way.getPoints());
1143
1144 long areaVal = 0;
1145 String tagStringVal = way.getTag(drawLevelTagKey);
1146 if (tagStringVal != null) {
1147 try {
1148 areaVal = Integer.parseInt(tagStringVal);
1149 if (areaVal < 1 || areaVal > 100) {
1150 log.error("mkgmap:drawLevel must be in range 1..100, not", areaVal);
1151 areaVal = 0;
1152 } else if (areaVal <= 50)
1153 areaVal = Long.MAX_VALUE - areaVal; // 1 => MAX_VALUE-1, 50 => MAX_VALUE-50
1154 else
1155 areaVal = 101 - areaVal; // 51 => 50, 100 => 1
1156 } catch (NumberFormatException e) {
1157 log.error("mkgmap:drawLevel invalid integer:", tagStringVal);
1158 }
1159 }
1160 if (areaVal == 0)
1161 areaVal = way.getFullArea();
1162 shape.setFullArea(areaVal);
1163
1164 clipper.clipShape(shape, collector);
1165 }
1166
1167 private void addPoint(Node node, GType gt) {
1168 if (!clipper.contains(node.getLocation()))
1169 return;
1170
1171 // to handle exit points we use a subclass of MapPoint
1172 // to carry some extra info (a reference to the
1173 // motorway associated with the exit)
1174 MapPoint mp;
1175 int type = gt.getType();
1176 if(type >= 0x2000 && type < 0x2800) {
1177 String ref = node.getTag(Exit.TAG_ROAD_REF);
1178 String id = node.getTag("mkgmap:osmid");
1179 if(ref != null) {
1180 String to = node.getTag(Exit.TAG_TO);
1181 MapExitPoint mep = new MapExitPoint(ref, to);
1182 String fd = node.getTag(Exit.TAG_FACILITY);
1183 if(fd != null)
1184 mep.setFacilityDescription(fd);
1185 if(id != null)
1186 mep.setOSMId(id);
1187 mp = mep;
1188 }
1189 else {
1190 mp = new MapPoint();
1191 if ("motorway_junction".equals(node.getTag("highway")))
1192 log.warn("Motorway exit", node.getName(), "(" + node.toBrowseURL() + ") has no (motorway) ref! (either make the exit share a node with the motorway or specify the motorway ref with a", Exit.TAG_ROAD_REF, "tag)");
1193 }
1194 }
1195 else {
1196 mp = new MapPoint();
1197 }
1198 elementSetup(mp, gt, node);
1199 mp.setLocation(node.getLocation());
1200
1201 boolean dupPOI = checkDuplicatePOI(mp);
1202 if (dupPOI){
1203 if (log.isInfoEnabled()){
1204 if (FakeIdGenerator.isFakeId(node.getId()))
1205 log.info("ignoring duplicate POI with type",GType.formatType(type),mp.getName(),"for generated element with id",node.getId(),"at",mp.getLocation().toDegreeString());
1206 else
1207 log.info("ignoring duplicate POI with type",GType.formatType(type),mp.getName(),"for element",node.toBrowseURL());
1208 }
1209 return;
1210 }
1211
1212 collector.addPoint(mp);
1213 }
1214
1215 /**
1216 * Check if we already have added a point with the same type + name and equal location.
1217 * @param mp
1218 * @return
1219 */
1220 private boolean checkDuplicatePOI(MapPoint mp) {
1221 Map<String, MapPoint> typeMap = pointMap.get(mp.getType());
1222 if (typeMap == null){
1223 typeMap = new HashMap<>();
1224 pointMap.put(mp.getType(), typeMap);
1225 }
1226 MapPoint old = typeMap.get(mp.getName());
1227 if (old == null){
1228 typeMap.put(mp.getName(), mp);
1229 } else {
1230 if (old.getLocation().equals(mp.getLocation()))
1231 return true;
1232 }
1233 return false;
1234 }
1235
12361246 private static final short[] labelTagKeys = {
12371247 TagDict.getInstance().xlate("mkgmap:label:1"),
12381248 TagDict.getInstance().xlate("mkgmap:label:2"),
13281338 /**
13291339 * Add a way to the road network. May call itself recursively and
13301340 * might truncate the way if splitting is required.
1331 * @param way the way
1332 * @param gt the type assigned by the style
1341 * @param cw the converted way
13331342 */
13341343 private void addRoad(ConvertedWay cw) {
13351344 Way way = cw.getWay();
13911400 splitPoint = prev.makeBetweenPoint(splitPoint, neededLength / dist);
13921401 double newDist = splitPoint.distance(prev);
13931402 segmentLength += newDist - dist;
1394 splitPoint.incHighwayCount();
13951403 points.add(splitPos, splitPoint);
1404 splitPoint.incHighwayCount(); // new point is on highway
13961405 }
13971406 if ((splitPos + 1) < points.size()
13981407 && safeToSplitWay(points, splitPos, i, points.size() - 1)) {
14021411 }
14031412 }
14041413 boolean classChanged = cw.recalcRoadClass(node);
1405 if (classChanged && log.isInfoEnabled()){
1406 log.info("POI changing road class of", way.toBrowseURL(), "to", cw.getRoadClass(), "at", points.get(0).toOSMURL());
1414 if (classChanged && log.isInfoEnabled()) {
1415 log.info("POI changing road class of", way.toBrowseURL(), "to", cw.getRoadClass(), "at",
1416 points.get(0).toOSMURL());
14071417 }
14081418 boolean speedChanged = cw.recalcRoadSpeed(node);
1409 if (speedChanged && log.isInfoEnabled()){
1410 log.info("POI changing road speed of", way.toBrowseURL(), "to", cw.getRoadSpeed(), "at" , points.get(0).toOSMURL());
1419 if (speedChanged && log.isInfoEnabled()) {
1420 log.info("POI changing road speed of", way.toBrowseURL(), "to", cw.getRoadSpeed(), "at",
1421 points.get(0).toOSMURL());
14111422 }
14121423 }
14131424 }
14211432 && points.get(i + 1) instanceof CoordPOI) {
14221433 CoordPOI cp = (CoordPOI) points.get(i + 1);
14231434 Node node = cp.getNode();
1424 if (cp.isUsed() && wayPOI.contains("["+node.getId()+"]")){
1425 if (node.getTag("mkgmap:road-class") != null
1426 || node.getTag("mkgmap:road-speed") != null) {
1427 // find good split point before POI
1428 double segmentLength = 0;
1429 int splitPos = i;
1430 Coord splitPoint;
1431 while( splitPos >= 0){
1432 splitPoint = points.get(splitPos);
1433 segmentLength += splitPoint.distance(points.get(splitPos + 1));
1434 if (splitPoint.getHighwayCount() >= 2
1435 || segmentLength > stubSegmentLength - 5)
1436 break;
1437 --splitPos;
1438 }
1439 if (segmentLength > stubSegmentLength + 10){
1440 // insert a new point before the POI to
1441 // make a short stub segment
1442 splitPoint = points.get(splitPos);
1443 Coord prev = points.get(splitPos+1);
1444 double dist = splitPoint.distance(prev);
1445 double neededLength = stubSegmentLength - (segmentLength - dist);
1446 splitPoint = prev.makeBetweenPoint(splitPoint, neededLength / dist);
1447 segmentLength += splitPoint.distance(prev) - dist;
1448 splitPoint.incHighwayCount();
1449 splitPos++;
1450 points.add(splitPos, splitPoint);
1451 }
1452 if(splitPos > 0 &&
1453 safeToSplitWay(points, splitPos, 0, points.size()-1)) {
1454 Way tail = splitWayAt(way, splitPos);
1455 // recursively process tail of way
1456 addRoad(new ConvertedWay(cw, tail));
1457 }
1435 if (cp.isUsed() && wayPOI.contains("[" + node.getId() + "]")
1436 && (node.getTag("mkgmap:road-class") != null || node.getTag("mkgmap:road-speed") != null)) {
1437 // find good split point before POI
1438 double segmentLength = 0;
1439 int splitPos = i;
1440 Coord splitPoint;
1441 while (splitPos >= 0) {
1442 splitPoint = points.get(splitPos);
1443 segmentLength += splitPoint.distance(points.get(splitPos + 1));
1444 if (splitPoint.getHighwayCount() >= 2 || segmentLength > stubSegmentLength - 5)
1445 break;
1446 --splitPos;
1447 }
1448 if (segmentLength > stubSegmentLength + 10) {
1449 // insert a new point before the POI to
1450 // make a short stub segment
1451 splitPoint = points.get(splitPos);
1452 Coord prev = points.get(splitPos + 1);
1453 double dist = splitPoint.distance(prev);
1454 double neededLength = stubSegmentLength - (segmentLength - dist);
1455 splitPoint = prev.makeBetweenPoint(splitPoint, neededLength / dist);
1456 segmentLength += splitPoint.distance(prev) - dist;
1457 splitPos++;
1458 points.add(splitPos, splitPoint);
1459 splitPoint.incHighwayCount(); // new point is on highway
1460 }
1461 if (splitPos > 0 && safeToSplitWay(points, splitPos, 0, points.size() - 1)) {
1462 Way tail = splitWayAt(way, splitPos);
1463 // recursively process tail of way
1464 addRoad(new ConvertedWay(cw, tail));
14581465 }
14591466 }
14601467 }
15011508 }
15021509 }
15031510
1511 /**
1512 * Split way so that it does not self intersect.
1513 * TODO: Maybe avoid if map is not routable?
1514 * @param cw the converted way
1515 */
15041516 private void addRoadAfterSplittingLoops(ConvertedWay cw) {
15051517 Way way = cw.getWay();
1506 // make sure the way has nodes at each end
1507 way.getFirstPoint().incHighwayCount();
1508 way.getLastPoint().incHighwayCount();
1509
15101518 // check if the way is a loop or intersects with itself
15111519
15121520 boolean wayWasSplit = true; // aka rescan required
17031711 for (;;){
17041712 int dlat = Math.abs(nextP.getLatitude() - p.getLatitude());
17051713 int dlon = Math.abs(nextP.getLongitude() - p.getLongitude());
1706 if (d > MAX_ARC_LENGTH || Math.max(dlat, dlon) >= LineSizeSplitterFilter.MAX_SIZE){
1707 double frac = Math.min(0.5, 0.95 * (MAX_ARC_LENGTH / d));
1714 if (d > MAX_ARC_LENGTH || Math.max(dlat, dlon) >= LineSizeSplitterFilter.MAX_SIZE) {
1715 double frac = Math.min(0.5, 0.95 * (MAX_ARC_LENGTH / d));
17081716 nextP = p.makeBetweenPoint(nextP, frac);
17091717 nextP.incHighwayCount();
17101718 points.add(i + 1, nextP);
17111719 double newD = p.distance(nextP);
17121720 if (log.isInfoEnabled())
1713 log.info("Way", debugWayName, "contains a segment that is", (int)d + "m long but I am adding a new point to reduce its length to", (int)newD + "m");
1721 log.info("Way", debugWayName, "contains a segment that is",
1722 (int) d + "m long but I am adding a new point to reduce its length to",
1723 (int) newD + "m");
17141724 d = newD;
1715 } else
1725 } else {
17161726 break;
1727 }
17171728 }
17181729
17191730 wayBBox.addPoint(nextP);
17201731
1721 if((arcLength + d) > MAX_ARC_LENGTH) {
1732 if ((arcLength + d) > MAX_ARC_LENGTH) {
17221733 if (i <= 0)
17231734 log.error("internal error: long arc segment was not split", debugWayName);
17241735 assert i > 0 : "long arc segment was not split";
17491760 arcLength += d;
17501761 }
17511762 }
1752 if(p.getHighwayCount() > 1 || p.getOnCountryBorder()) {
1763 if(p.getHighwayCount() > 1 || (routable && p.getOnCountryBorder())) {
17531764 // this point is a node connecting highways
1754 CoordNode coordNode = nodeIdMap.get(p);
1755 if(coordNode == null) {
1756 // assign a node id
1757 coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary(), p.getOnCountryBorder());
1758 nodeIdMap.put(p, coordNode);
1759 }
1760
17611765 if (p instanceof CoordPOI){
1762 // check if this poi should be converted to a route restriction
1766 // check if this POI should be converted to a route restriction
17631767 CoordPOI cp = (CoordPOI) p;
17641768 if (cp.getConvertToViaInRouteRestriction()){
17651769 String wayPOI = way.getTag(WAY_POI_NODE_IDS);
18001804 elementSetup(line, cw.getGType(), way);
18011805 line.setPoints(points);
18021806 MapRoad road = new MapRoad(nextRoadId++, way.getId(), line);
1803 if (routable == false)
1807 if (!routable) {
18041808 road.skipAddToNOD(true);
1809 }
18051810
18061811 boolean doFlareCheck = true;
18071812
18401845 if (cw.isCarpool())
18411846 road.setCarpoolLane();
18421847
1843 if (cw.isThroughroute() == false)
1848 if (!cw.isThroughroute())
18441849 road.setNoThroughRouting();
18451850
1846 if(cw.isToll())
1851 if (cw.isToll())
18471852 road.setToll();
18481853
18491854 // by default, ways are paved
1850 if(cw.isUnpaved())
1855 if (cw.isUnpaved())
18511856 road.paved(false);
18521857
18531858 // by default, way's are not ferry routes
1854 if(cw.isFerry())
1859 if (cw.isFerry())
18551860 road.ferry(true);
18561861
1862 if (routable && nodeIndices.isEmpty()) {
1863 // this is a road not connected to other roads, make sure that its first node will be a routing node
1864 nodeIndices.add(0);
1865 }
1866
18571867 int numNodes = nodeIndices.size();
1858 if (way.isViaWay() && numNodes > 2){
1868 if (way.isViaWay() && numNodes > 2) {
18591869 List<RestrictionRelation> rrList = wayRelMap.get(way.getId());
1860 for (RestrictionRelation rr : rrList){
1870 for (RestrictionRelation rr : rrList) {
18611871 rr.updateViaWay(way, nodeIndices);
18621872 }
18631873 }
18661876 // replace Coords that are nodes with CoordNodes
18671877 for(int i = 0; i < numNodes; ++i) {
18681878 int n = nodeIndices.get(i);
1869 Coord coord = points.get(n);
1870 CoordNode thisCoordNode = nodeIdMap.get(coord);
1871 assert thisCoordNode != null : "Way " + debugWayName + " node " + i + " (point index " + n + ") at " + coord.toOSMURL() + " yields a null coord node";
1872 boolean boundary = coord.getOnBoundary() || coord.getOnCountryBorder();
1873 if(boundary && log.isInfoEnabled()) {
1874 log.info("Way", debugWayName + "'s point #" + n, "at", coord.toOSMURL(), "is a boundary node");
1875 }
1876 points.set(n, thisCoordNode);
1879 Coord p = points.get(n);
1880 CoordNode coordNode = nodeIdMap.get(p);
1881 if(coordNode == null) {
1882 // assign a unique node id > 0
1883 int uniqueId = nodeIdMap.size() + 1;
1884 coordNode = new CoordNode(p, uniqueId, p.getOnBoundary(), p.getOnCountryBorder());
1885 nodeIdMap.put(p, coordNode);
1886 }
1887
1888 if ((p.getOnBoundary() || p.getOnCountryBorder()) && log.isInfoEnabled()) {
1889 log.info("Way", debugWayName + "'s point #" + n, "at", p.toOSMURL(), "is a boundary node");
1890 }
1891 points.set(n, coordNode);
18771892 }
18781893 }
18791894
18931908
18941909 if(trailingWay != null)
18951910 addRoadWithoutLoops(new ConvertedWay(cw, trailingWay));
1896 }
1897
1898 /**
1899 * Check if the first or last of the coords of the way has the fixme flag set
1900 * @param way the way to check
1901 * @return true if fixme flag was found
1902 */
1903 private static boolean checkFixmeCoords(Way way) {
1904 return way.getFirstPoint().isFixme() || way.getLastPoint().isFixme();
19051911 }
19061912
19071913 /**
20032009 }
20042010 }
20052011
2012 private static final String[] CONNECTION_TAGS = { "mkgmap:set_unconnected_type", "mkgmap:set_semi_connected_type" };
2013
20062014 /**
20072015 * Detect roads that do not share any node with another road.
20082016 * If such a road has the mkgmap:set_unconnected_type tag, add it as line, not as a road.
2009 */
2010 private void findUnconnectedRoads(){
2011 Map<Coord, HashSet<Way>> connectors = new IdentityHashMap<>(roads.size()*2);
2012
2017 * Detect also roads which are only connected in one point, so that they don't lead to other roads.
2018 * If such a road has the mkgmap:set_semi_connected_type tag, add it as line, not as a road.
2019 */
2020 private void findUnconnectedRoads() {
2021 Map<Coord, HashSet<Way>> connectors = new IdentityHashMap<>(roads.size() * 2);
2022
20132023 // for dead-end-check only: will contain ways with loops (also simply closed ways)
20142024 HashSet<Way> selfConnectors = new HashSet<>();
2015
2025
20162026 // collect nodes that might connect roads
20172027 long lastId = 0;
2018 for (ConvertedWay cw :roads){
2028 for (ConvertedWay cw : roads) {
20192029 Way way = cw.getWay();
20202030 if (way.getId() == lastId)
20212031 continue;
20222032 lastId = way.getId();
2023 for (Coord p:way.getPoints()){
2024 if (p.getHighwayCount() > 1){
2033 for (Coord p : way.getPoints()) {
2034 if (p.getHighwayCount() > 1) {
20252035 HashSet<Way> ways = connectors.get(p);
2026 if (ways == null){
2036 if (ways == null) {
20272037 ways = new HashSet<>();
20282038 connectors.put(p, ways);
20292039 }
20332043 }
20342044 }
20352045 }
2036
2037 // find roads that are not connected
2038 // count downwards because we are removing elements
2046
2047 /** roads with 0 .. 1 connections to other roads */
2048 Map<Long,Integer> poorlyConnectedRoads= new HashMap<>();
2049
20392050 Iterator<ConvertedWay> iter = roads.iterator();
2040 while(iter.hasNext()){
2051 while (iter.hasNext()) {
20412052 ConvertedWay cw = iter.next();
20422053 if (!cw.isValid())
20432054 continue;
20442055 Way way = cw.getWay();
2045 if(reportDeadEnds > 0){
2046 // report dead ends of oneway roads
2047 if (cw.isOneway() && !way.tagIsLikeNo("mkgmap:dead-end-check")) {
2048 List<Coord> points = way.getPoints();
2049 int[] pointsToCheck = {0, points.size()-1};
2050 if (points.get(pointsToCheck[0]) == points.get(pointsToCheck[1]))
2051 continue; // skip closed way
2052 for (int pos: pointsToCheck ){
2053 boolean isDeadEnd = true;
2054 boolean isDeadEndOfMultipleWays = true;
2055 Coord p = points.get(pos);
2056 if (bbox.contains(p) == false || p.getOnBoundary())
2057 isDeadEnd = false; // we don't know enough about possible connections
2058 else if (p.getHighwayCount() < 2){
2059 isDeadEndOfMultipleWays = false;
2060 } else {
2061 HashSet<Way> ways = connectors.get(p);
2062 if (ways.size() <= 1)
2063 isDeadEndOfMultipleWays = false;
2064 for (Way connectedWay: ways){
2065 if (!isDeadEnd)
2066 break;
2067 if (way == connectedWay){
2068 if (selfConnectors.contains(way)){
2069 // this might be a P-shaped oneway,
2070 // check if it has other exists in the loop part
2071 if (pos == 0){
2072 for (int k = pos+1; k < points.size()-1; k++){
2073 Coord pTest = points.get(k);
2074 if (pTest == p)
2075 break; // found no other exit
2076 if (pTest.getHighwayCount() > 1){
2077 isDeadEnd = false;
2078 break;
2079 }
2080 }
2081
2082 }else {
2083 for (int k = pos-1; k >= 0; k--){
2084 Coord pTest = points.get(k);
2085 if (pTest == p)
2086 break; // found no other exit
2087 if (pTest.getHighwayCount() > 1){
2088 isDeadEnd = false;
2089 break;
2090 }
2091 }
2056 if (reportDeadEnds > 0) {
2057 reportDeadEnds(cw, connectors, selfConnectors);
2058 }
2059 boolean onBoundary = false;
2060 int countCon = 0;
2061 for (Coord p : way.getPoints()) {
2062 if (p.getOnBoundary()) {
2063 onBoundary = true;
2064 break;
2065 }
2066 if (p.getHighwayCount() > 1) {
2067 HashSet<Way> ways = connectors.get(p);
2068 if (ways != null && ways.size() > 1) {
2069 ++countCon;
2070 }
2071 }
2072 }
2073 boolean remove = false;
2074 if (countCon <= 1) {
2075 remove = handlePoorConnection(countCon, cw, onBoundary);
2076 if (remove)
2077 iter.remove();
2078 if (!onBoundary)
2079 poorlyConnectedRoads.put(way.getId(), countCon);
2080 }
2081 }
2082
2083 // now check if we have to remove overlay lines
2084 Iterator<ConvertedWay> linesIter = lines.iterator();
2085 while (linesIter.hasNext()) {
2086 ConvertedWay cw = linesIter.next();
2087 if (cw.isOverlay()) {
2088 Way way = cw.getWay();
2089 Integer countCon = poorlyConnectedRoads.get(way.getId());
2090 if (countCon != null) {
2091 boolean remove = handlePoorConnection(countCon, cw, false);
2092 if (remove) {
2093 linesIter.remove();
2094 }
2095 }
2096 }
2097 }
2098 }
2099
2100 /**
2101 * When called, the way is either not connected to other roads or it is
2102 * only connected in one point. Check if special tags exist which changes the
2103 * type or tells mkgmap to remove the line.
2104 *
2105 * @param tagKey the tag key to check
2106 * @param cw the converted way (either a road or an overlay line for the road)
2107 * @param onBoundary if true, don't change anything
2108 * @return true if the road should not be added to the map
2109 */
2110 private boolean handlePoorConnection(int count, ConvertedWay cw, boolean onBoundary) {
2111 Way way = cw.getWay();
2112 String tagKey = CONNECTION_TAGS[count];
2113 String replTypeString = way.getTag(tagKey);
2114 if (replTypeString == null)
2115 return false;
2116 StringBuilder sb = new StringBuilder(100);
2117 sb.append(way.toBrowseURL()).append(' ').append(GType.formatType(cw.getGType().getType()));
2118 if (cw.isOverlay())
2119 sb.append("(Overlay)");
2120 sb.append(": ").append(count == 0 ? "road not connected" : "road doesn't go").append(" to other roads");
2121 if (onBoundary) {
2122 log.info(sb.toString(), "but is on boundary");
2123 return false;
2124 }
2125 sb.append(',');
2126 if ("none".equals(replTypeString)) {
2127 log.info(sb.toString(), "is ignored because of", tagKey + "=none");
2128 return true;
2129 }
2130 int replType = -1;
2131 try {
2132 replType = Integer.decode(replTypeString);
2133 if (GType.isRoutableLineType(replType)) {
2134 replType = -1;
2135 log.error("type value in", tagKey, "should not be a routable type:" + replTypeString);
2136 }
2137 if (!GType.checkType(FeatureKind.POLYLINE, replType)) {
2138 replType = -1;
2139 log.error("type value in", tagKey, "is not a valid line type:" + replTypeString);
2140 }
2141 } catch (NumberFormatException e) {
2142 throw new ExitException("invalid type value in style" + tagKey + "=" + replTypeString);
2143 }
2144 if (replType != -1) {
2145 log.info(sb.toString(), "added as line with type", replTypeString);
2146 addLine(way, cw.getGType(), replType);
2147 } else {
2148 log.info(sb.toString(), "but replacement type is invalid. Was dropped");
2149 }
2150 return true; // don't add road or line to map
2151 }
2152
2153 /**
2154 * Check if oneway roads don't allow to continue at the end.
2155 *
2156 * @param cw the converted way
2157 * @param connectors set of nodes where roads are connected
2158 * @param selfConnectors set of nodes where roads have loops (rings or p-shapes)
2159 */
2160 private void reportDeadEnds(ConvertedWay cw, Map<Coord, HashSet<Way>> connectors, HashSet<Way> selfConnectors) {
2161 Way way = cw.getWay();
2162 // report dead ends of oneway roads if check is not disabled
2163 if (cw.isOneway() && !way.tagIsLikeNo("mkgmap:dead-end-check")) {
2164 List<Coord> points = way.getPoints();
2165 int[] pointsToCheck = { 0, points.size() - 1 };
2166 if (points.get(pointsToCheck[0]) == points.get(pointsToCheck[1]))
2167 return; // skip closed way
2168 for (int pos : pointsToCheck) {
2169 boolean isDeadEnd = true;
2170 boolean isDeadEndOfMultipleWays = true;
2171 Coord p = points.get(pos);
2172 if (!bbox.contains(p) || p.getOnBoundary())
2173 isDeadEnd = false; // we don't know enough about possible connections
2174 else if (p.getHighwayCount() < 2) {
2175 isDeadEndOfMultipleWays = false;
2176 } else {
2177 HashSet<Way> ways = connectors.get(p);
2178 if (ways.size() <= 1)
2179 isDeadEndOfMultipleWays = false;
2180 for (Way connectedWay : ways) {
2181 if (!isDeadEnd)
2182 break;
2183 if (way == connectedWay) {
2184 if (selfConnectors.contains(way)) {
2185 // this might be a P-shaped oneway,
2186 // check if it has other exists in the loop part
2187 if (pos == 0) {
2188 for (int k = pos + 1; k < points.size() - 1; k++) {
2189 Coord pTest = points.get(k);
2190 if (pTest == p)
2191 break; // found no other exit
2192 if (pTest.getHighwayCount() > 1) {
2193 isDeadEnd = false;
2194 break;
20922195 }
20932196 }
2094 continue;
2095 }
2096 List<Coord> otherPoints = connectedWay.getPoints();
2097 Coord otherFirst = otherPoints.get(0);
2098 Coord otherLast = otherPoints.get(otherPoints.size()-1);
2099 if (otherFirst == otherLast || connectedWay.tagIsLikeYes(onewayTagKey) == false)
2100 isDeadEnd = false;
2101 else {
2102 Coord pOther;
2103 if (pos != 0)
2104 pOther = otherLast;
2105 else
2106 pOther = otherFirst;
2107 if (p != pOther){
2108 // way is connected to a point on a oneway which allows going on
2109 isDeadEnd = false;
2197 } else {
2198 for (int k = pos - 1; k >= 0; k--) {
2199 Coord pTest = points.get(k);
2200 if (pTest == p)
2201 break; // found no other exit
2202 if (pTest.getHighwayCount() > 1) {
2203 isDeadEnd = false;
2204 break;
2205 }
21102206 }
21112207 }
21122208 }
2209 continue;
21132210 }
2114
2115 if (isDeadEnd && (isDeadEndOfMultipleWays || reportDeadEnds > 1)){
2116 log.warn("Oneway road " + way.getId() + " with tags " + way.toTagString() + ((pos==0) ? " comes from":" goes to") + " nowhere at " + p.toOSMURL());
2211 List<Coord> otherPoints = connectedWay.getPoints();
2212 Coord otherFirst = otherPoints.get(0);
2213 Coord otherLast = otherPoints.get(otherPoints.size() - 1);
2214 if (otherFirst == otherLast || !connectedWay.tagIsLikeYes(onewayTagKey))
2215 isDeadEnd = false;
2216 else {
2217 Coord pOther;
2218 if (pos != 0)
2219 pOther = otherLast;
2220 else
2221 pOther = otherFirst;
2222 if (p != pOther) {
2223 // way is connected to a point on a oneway which allows going on
2224 isDeadEnd = false;
2225 }
21172226 }
21182227 }
21192228 }
2120 }
2121 String replType = way.getTag("mkgmap:set_unconnected_type");
2122 if (replType != null){
2123 boolean isConnected = false;
2124 boolean onBoundary = false;
2125 for (Coord p:way.getPoints()){
2126 if (p.getOnBoundary())
2127 onBoundary = true;
2128 if (p.getHighwayCount() > 1){
2129 HashSet<Way> ways = connectors.get(p);
2130 if (ways != null && ways.size() > 1){
2131 isConnected = true;
2132 break;
2133 }
2134 }
2135 }
2136 if (!isConnected){
2137 if (onBoundary){
2138 log.info("road not connected to other roads but is on boundary:", way.toBrowseURL());
2139 } else {
2140 if ("none".equals(replType))
2141 log.info("road not connected to other roads, is ignored:", way.toBrowseURL());
2142 else {
2143 int typeNoConnection = -1;
2144 try{
2145 typeNoConnection = Integer.decode(replType);
2146 if (GType.isRoutableLineType(typeNoConnection)){
2147 typeNoConnection = -1;
2148 log.error("type value in mkgmap:set_unconnected_type should not be a routable type:" + replType);
2149 }
2150 } catch (NumberFormatException e){
2151 log.warn("invalid type value in mkgmap:set_unconnected_type:", replType);
2152 }
2153 if (typeNoConnection != -1 ){
2154 log.info("road not connected to other roads, added as line with type", replType + ":", way.toBrowseURL());
2155 addLine(way, cw.getGType(), typeNoConnection);
2156 } else {
2157 log.warn("road not connected to other roads, but replacement type is invalid. Dropped:", way.toBrowseURL());
2158 }
2159 }
2160 iter.remove();
2161 }
2162 }
2163 }
2164 }
2165 }
2166
2229
2230 if (isDeadEnd && (isDeadEndOfMultipleWays || reportDeadEnds > 1)) {
2231 log.warn("Oneway road " + way.getId() + " with tags " + way.toTagString()
2232 + ((pos == 0) ? " comes from" : " goes to") + " nowhere at " + p.toOSMURL());
2233 }
2234 }
2235 }
2236 }
2237
21672238 /**
21682239 * Make sure that only CoordPOI which affect routing will be treated as
21692240 * nodes in the following routines.
21882259 Node node = cp.getNode();
21892260 boolean usedInThisWay = false;
21902261 byte wayAccess = cw.getAccess();
2191 if (node.getTag("mkgmap:road-class") != null
2192 || node.getTag("mkgmap:road-speed") != null ) {
2193 if (wayAccess != AccessTagsAndBits.FOOT)
2194 usedInThisWay = true;
2262 if (wayAccess != AccessTagsAndBits.FOOT && (node.getTag("mkgmap:road-class") != null
2263 || node.getTag("mkgmap:road-speed") != null)) {
2264 usedInThisWay = true;
21952265 }
21962266 byte nodeAccess = AccessTagsAndBits.evalAccessTags(node);
2197 if(nodeAccess != (byte)0xff){
2198 // barriers etc.
2199 if ((wayAccess & nodeAccess) != wayAccess){
2267 if (nodeAccess != (byte) 0xff) {
2268 // barriers etc.
2269 if ((wayAccess & nodeAccess) != wayAccess) {
22002270 // node is more restrictive
2201 if (p.getHighwayCount() >= 2 || (i != 0 && i != numPoints-1)){
2271 if (p.getHighwayCount() >= 2 || (i != 0 && i != numPoints - 1)) {
22022272 usedInThisWay = true;
22032273 cp.setConvertToViaInRouteRestriction(true);
2274 } else {
2275 log.info("POI node", node.getId(),
2276 "with access restriction is ignored, it is not connected to other routable ways");
22042277 }
2205 else {
2206 log.info("POI node", node.getId(), "with access restriction is ignored, it is not connected to other routable ways");
2207 }
2208 } else
2209 log.info("Access restriction in POI node", node.toBrowseURL(), "was ignored for way", way.toBrowseURL());
2278 } else {
2279 log.info("Access restriction in POI node", node.toBrowseURL(), "was ignored for way",
2280 way.toBrowseURL());
2281 }
22102282 }
2211 if (usedInThisWay){
2283 if (usedInThisWay) {
22122284 cp.setUsed(true);
2213 wayPOI += "["+ node.getId()+"]";
2285 wayPOI += "[" + node.getId() + "]";
22142286 }
22152287 }
22162288 }
22172289 if (wayPOI.isEmpty()) {
22182290 way.deleteTag("mkgmap:way-has-pois");
22192291 log.info("ignoring CoordPOI(s) for way", way.toBrowseURL(), "because routing is not affected.");
2220 }
2221 else {
2292 } else {
22222293 way.addTag(WAY_POI_NODE_IDS, wayPOI);
22232294 }
22242295 }
9090 throw new SyntaxException(ts, "Unrecognised type command '" + w + '\'');
9191 }
9292 }
93
93
94 int maxResLevel0 = toResolution(0);
95 if (gt.getMaxResolution() > maxResLevel0 && gt.getMinResolution() > maxResLevel0) {
96 String msg = "Cannot use type " + gt + " with level 0 at resolution " + maxResLevel0
97 + " in style file " + ts.getFileName() + ", line " + ts.getLinenumber();
98 if (performChecks) {
99 log.error(msg);
100 } else {
101 throw new SyntaxException(msg);
102 }
103 }
94104 gt.fixLevels(levels);
95105 if ("lines".equals(ts.getFileName())){
96106 if(gt.getRoadClass() < 0 || gt.getRoadClass() > 4)
5353 public class WrongAngleFixer {
5454 private static final Logger log = Logger.getLogger(WrongAngleFixer.class);
5555
56 static private final double MAX_BEARING_ERROR = 15;
57 static private final double MAX_BEARING_ERROR_HALF = MAX_BEARING_ERROR / 2;
58 static private final double MAX_DIFF_ANGLE_STRAIGHT_LINE = 3;
56 private static final double MAX_BEARING_ERROR = 15;
57 private static final double MAX_BEARING_ERROR_HALF = MAX_BEARING_ERROR / 2;
58 private static final double MAX_DIFF_ANGLE_STRAIGHT_LINE = 3;
5959
6060 private final Area bbox;
61 private final static String DEBUG_PATH = null;
61 private static final String DEBUG_PATH = null;
6262 static final int MODE_ROADS = 0;
6363 static final int MODE_LINES = 1;
6464 private int mode = MODE_ROADS;
6767
6868 public WrongAngleFixer(Area bbox) {
6969 this.bbox = bbox;
70 if (DEBUG_PATH != null && bbox != null){
71 if ((long) bbox.getWidth() * (long) bbox.getHeight() < 100000) {
72 List<Coord> grid = new ArrayList<>();
73 for (int lat = bbox.getMinLat(); lat < bbox.getMaxLat(); lat++){
74 for (int lon = bbox.getMinLong(); lon < bbox.getMaxLong(); lon++){
75 grid.add(new Coord(lat,lon));
76 }
77 }
78 GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, "grid"), bbox.toCoords(), grid);
79 }
70 if (DEBUG_PATH != null && bbox != null && (long) bbox.getWidth() * bbox.getHeight() < 100000) {
71 List<Coord> grid = new ArrayList<>();
72 for (int lat = bbox.getMinLat(); lat < bbox.getMaxLat(); lat++) {
73 for (int lon = bbox.getMinLong(); lon < bbox.getMaxLong(); lon++) {
74 grid.add(new Coord(lat, lon));
75 }
76 }
77 GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, "grid"), bbox.toCoords(), grid);
8078 }
8179 }
8280
9088 * @param deletedRoads Will be enlarged by all roads in roads that were set to null by this method
9189 * @param restrictions Map with restriction relations
9290 */
93 public void optimizeWays(List<ConvertedWay> roads, List<ConvertedWay> lines, HashMap<Long, ConvertedWay> modifiedRoads, HashSet<Long> deletedRoads, List<RestrictionRelation> restrictions ) {
91 public void optimizeWays(List<ConvertedWay> roads, List<ConvertedWay> lines, Map<Long, ConvertedWay> modifiedRoads,
92 Set<Long> deletedRoads, List<RestrictionRelation> restrictions) {
9493 printBadAngles("bad_angles_start", roads);
9594 writeOSM("roads_orig", roads);
9695 writeOSM("lines_orig", lines);
116115 * @param convertedWays
117116 * @param coordMap
118117 */
119 private void replaceDuplicateBoundaryNodes(List<ConvertedWay> convertedWays, Long2ObjectOpenHashMap<Coord> coordMap) {
118 private static void replaceDuplicateBoundaryNodes(List<ConvertedWay> convertedWays,
119 Long2ObjectOpenHashMap<Coord> coordMap) {
120120 for (ConvertedWay cw : convertedWays) {
121 if (!cw.isValid() || cw.isOverlay())
121 if (!cw.isValid() || cw.isOverlay())
122122 continue;
123123 Way way = cw.getWay();
124124 List<Coord> points = way.getPoints();
131131 coordMap.put(Utils.coord2Long(co), co);
132132 else {
133133 if (!co.isAddedByClipper() && repl.isAddedByClipper()) {
134 log.debug("check replaced original boundary node at",co);
134 log.debug("check replaced original boundary node at", co);
135135 }
136136 points.set(i, repl);
137137 }
139139 }
140140 }
141141
142 private static void replaceCoord(Coord toRepl, Coord replacement, Map<Coord, Coord> replacements) {
143 assert toRepl != replacement;
144 if (toRepl.getOnBoundary()){
145 if (replacement.equals(toRepl) == false){
146 log.error("boundary node is replaced by node with non-equal coordinates at", toRepl.toOSMURL());
147 assert false : "boundary node is replaced" ;
148 }
149 replacement.setOnBoundary(true);
150 }
151 if (toRepl.getOnCountryBorder()){
152 if (replacement.equals(toRepl) == false){
153 log.warn("country boundary node is replaced by node with non-equal coordinates at", toRepl.toOSMURL());
154 // assert false : "country boundary node is replaced" ;
155 }
156 replacement.setOnCountryBorder(true);
157 }
158 toRepl.setReplaced(true);
159 if (toRepl instanceof CoordPOI) {
160 CoordPOI cp = (CoordPOI) toRepl;
161 if (cp.isUsed()){
162 replacement = new CoordPOI(replacement);
163 ((CoordPOI) replacement).setNode(cp.getNode());
164 ((CoordPOI) replacement).setUsed(true);
165 ((CoordPOI) replacement).setConvertToViaInRouteRestriction(cp.getConvertToViaInRouteRestriction());
166 if (replacement.highPrecEquals(cp.getNode().getLocation()) == false){
167 log.error("CoordPOI node is replaced with non-equal coordinates at", toRepl.toOSMURL());
168 }
169 }
170 }
171 if (toRepl.isViaNodeOfRestriction())
172 replacement.setViaNodeOfRestriction(true);
173 replacements.put(toRepl, replacement);
174 while (toRepl.getHighwayCount() > replacement.getHighwayCount())
175 replacement.incHighwayCount();
176 if (toRepl.isEndOfWay() ){
177 replacement.setEndOfWay(true);
178 }
179 }
180
181142 /**
182143 * Common code to handle replacements of points in ways. Checks for special
183144 * cases regarding CoordPOI.
187148 * @param replacements the Map containing the replaced points
188149 * @return the replacement
189150 */
190 private static Coord getReplacement(Coord p, Way way,
191 Map<Coord, Coord> replacements) {
151 private static Coord getReplacement(Coord p, Way way, Map<Coord, Coord> replacements) {
192152 // check if this point is to be replaced because
193153 // it was previously merged into another point
194154 if (p.isReplaced()) {
204164 Node node = cp.getNode();
205165 if (cp.isUsed() && way != null && way.getId() != 0) {
206166 String wayPOI = way.getTag(StyledConverter.WAY_POI_NODE_IDS);
207 if (wayPOI != null && wayPOI.contains("["+node.getId()+"]")){
167 if (wayPOI != null && wayPOI.contains("[" + node.getId() + "]")) {
208168 if (replacement instanceof CoordPOI) {
209169 Node rNode = ((CoordPOI) replacement).getNode();
210170 if (rNode.getId() != node.getId()) {
211 if (wayPOI.contains("["+ rNode.getId() + "]")){
212 log.warn("CoordPOI", node.getId(),
213 "replaced by CoordPOI",
214 rNode.getId(), "in way",
215 way.toBrowseURL());
171 if (wayPOI.contains("[" + rNode.getId() + "]")) {
172 log.warn("CoordPOI", node.getId(), "replaced by CoordPOI", rNode.getId(),
173 "in way", way.toBrowseURL());
174 } else {
175 log.warn("CoordPOI", node.getId(), "replaced by ignored CoordPOI",
176 rNode.getId(), "in way", way.toBrowseURL());
216177 }
217 else
218 log.warn("CoordPOI", node.getId(),
219 "replaced by ignored CoordPOI",
220 rNode.getId(), "in way",
221 way.toBrowseURL());
222178 }
223 } else
224 log.warn("CoordPOI", node.getId(),
225 "replaced by simple coord in way",
179 } else {
180 log.warn("CoordPOI", node.getId(), "replaced by simple coord in way",
226181 way.toBrowseURL());
182 }
227183 }
228184 }
229185 }
230186 return replacement;
231 }
187 }
232188 log.error("replacement not found for point " + p.toOSMURL());
233
189
234190 }
235191 return p;
236192 }
244200 * @param deletedRoads set of ids of deleted routable ways (modified by this routine)
245201 * @param restrictions Map with restriction relations. The restriction relations may be modified by this routine
246202 */
247 private void removeWrongAngles(List<ConvertedWay> roads, List<ConvertedWay> lines, HashMap<Long, ConvertedWay> modifiedRoads, HashSet<Long> deletedRoads, List<RestrictionRelation> restrictions) {
203 private void removeWrongAngles(List<ConvertedWay> roads, List<ConvertedWay> lines,
204 Map<Long, ConvertedWay> modifiedRoads, Set<Long> deletedRoads, List<RestrictionRelation> restrictions) {
248205 // replacements maps those nodes that have been replaced to
249206 // the node that replaces them
250207 Map<Coord, Coord> replacements = new IdentityHashMap<>();
267224 break;
268225 anotherPassRequired = false;
269226 log.info("Removing wrong angles - PASS", pass);
270 writeOSM(((mode==MODE_LINES) ? "lines" : "roads") + "_pass_" + pass, convertedWays);
227 writeOSM(((mode == MODE_LINES) ? "lines" : "roads") + "_pass_" + pass, convertedWays);
271228
272229 // Step 1: detect points which are parts of line segments with wrong bearings
273230 lastWay = null;
274231 for (ConvertedWay cw : convertedWays) {
275 if (!cw.isValid() || cw.isOverlay())
232 if (!cw.isValid() || cw.isOverlay())
276233 continue;
277234 Way way = cw.getWay();
278 if (way.equals(lastWay))
279 continue;
280 if (pass != 1 && waysWithBearingErrors.contains(way) == false)
235 if (way.equals(lastWay))
236 continue;
237 if (pass != 1 && !waysWithBearingErrors.contains(way))
281238 continue;
282239 lastWay = way;
283240 List<Coord> points = way.getPoints();
284
241
285242 // scan through the way's points looking for line segments with big
286243 // bearing errors
287244 Coord prev = null;
288 if (points.get(0) == points.get(points.size()-1) && points.size() >= 2)
289 prev = points.get(points.size()-2);
245 if (points.get(0) == points.get(points.size() - 1) && points.size() >= 2)
246 prev = points.get(points.size() - 2);
290247 boolean hasNonEqualPoints = false;
291248 for (int i = 0; i < points.size(); ++i) {
292249 Coord p = points.get(i);
293250 if (pass == 1)
294251 p.setRemove(false);
295252 p = getReplacement(p, way, replacements);
296 if (i == 0 || i == points.size()-1){
253 if (i == 0 || i == points.size() - 1) {
297254 p.setEndOfWay(true);
298255 }
299
256
300257 if (prev != null) {
301 if (pass == 1 && p.equals(prev) == false)
258 if (pass == 1 && !p.equals(prev))
302259 hasNonEqualPoints = true;
303 double err = calcBearingError(p,prev);
304 if (err >= MAX_BEARING_ERROR){
260 double err = calcBearingError(p, prev);
261 if (err >= MAX_BEARING_ERROR) {
305262 // bearing error is big
306263 p.setPartOfBadAngle(true);
307264 prev.setPartOfBadAngle(true);
309266 }
310267 prev = p;
311268 }
312 if (pass == 1 && hasNonEqualPoints == false){
269 if (pass == 1 && !hasNonEqualPoints) {
313270 waysThatMapToOnePoint.add(way.getId());
314 log.info("all points of way",way.toBrowseURL(),"are rounded to equal map units" );
271 log.info("all points of way", way.toBrowseURL(), "are rounded to equal map units");
315272 }
316273 }
317274 // Step 2: collect the line segments that are connected to critical points
318275 IdentityHashMap<Coord, CenterOfAngle> centerMap = new IdentityHashMap<>();
319276 List<CenterOfAngle> centers = new ArrayList<>(); // needed for ordered processing
320 Map<Coord,Set<Way>> overlaps = new HashMap<>();
321
277 Map<Coord, Set<Way>> overlaps = new HashMap<>();
278
322279 lastWay = null;
323280 for (ConvertedWay cw : convertedWays) {
324 if (!cw.isValid() || cw.isOverlay())
281 if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay))
325282 continue;
326283 Way way = cw.getWay();
327 if (way.equals(lastWay))
328 continue;
329 if (pass != 1 && waysWithBearingErrors.contains(way) == false)
284 if (pass != 1 && !waysWithBearingErrors.contains(way))
330285 continue;
331286 lastWay = way;
332287
335290 // scan through the way's points looking for line segments with big
336291 // bearing errors
337292 Coord prev = null;
338 if (points.get(0) == points.get(points.size()-1) && points.size() >= 2)
339 prev = points.get(points.size()-2);
293 if (points.get(0) == points.get(points.size() - 1) && points.size() >= 2)
294 prev = points.get(points.size() - 2);
340295 for (int i = 0; i < points.size(); ++i) {
341296 Coord p = points.get(i);
342297 if (prev != null) {
343 if (p == prev){
298 if (p == prev) {
344299 points.remove(i);
345300 --i;
346301 if (mode == MODE_ROADS)
360315 // way has only two points, don't merge them
361316 coa1.addBadMergeCandidate(coa2);
362317 }
363 if (mode == MODE_ROADS){
364 if (p1.getHighwayCount() >= 2 && p2.getHighwayCount() >= 2){
365 if (cw.isRoundabout()) {
366 // avoid to merge exits of roundabouts
367 coa1.addBadMergeCandidate(coa2);
368 }
369 }
318 if (mode == MODE_ROADS && p1.getHighwayCount() >= 2 && p2.getHighwayCount() >= 2
319 && cw.isRoundabout()) {
320 // avoid to merge exits of roundabouts
321 coa1.addBadMergeCandidate(coa2);
370322 }
371323 }
372324 }
375327 if (pass == 1 && wayHasSpecialPoints)
376328 waysWithBearingErrors.add(way);
377329 }
378 markOverlaps(overlaps,centers);
330 markOverlaps(overlaps, centers);
379331 overlaps.clear();
380332 // Step 3: Update list of ways with bearing errors or points next to them
381333 lastWay = null;
382334 for (ConvertedWay cw : convertedWays) {
383 if (!cw.isValid() || cw.isOverlay())
335 if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay))
384336 continue;
385337 Way way = cw.getWay();
386 if (way.equals(lastWay))
387 continue;
388338 lastWay = way;
389 if (waysWithBearingErrors.contains(way))
390 continue;
391 List<Coord> points = way.getPoints();
339 if (!waysWithBearingErrors.contains(way)) {
340 List<Coord> points = way.getPoints();
392341 // scan through the way's points looking for line segments with big
393 // bearing errors
394 for (Coord p: points) {
395 if (p.getHighwayCount() < 2)
396 continue;
397 if (centerMap.containsKey(p)){
398 waysWithBearingErrors.add(way);
399 break;
342 // bearing errors
343 for (Coord p : points) {
344 if (p.getHighwayCount() >= 2 && centerMap.containsKey(p)) {
345 waysWithBearingErrors.add(way);
346 break;
347 }
400348 }
401349 }
402350 }
405353 // Step 4: try to correct the errors
406354 List<CenterOfAngle> checkAgainList = null;
407355 boolean tryMerge = false;
408 while (true){
356 while (true) {
409357 checkAgainList = new ArrayList<>();
410358 for (CenterOfAngle coa : centers) {
411359 coa.center.setPartOfBadAngle(false); // reset flag for next pass
412360 if (coa.getCurrentLocation(replacements) == null)
413361 continue; // removed center
414 if (coa.isOK(replacements) == false) {
362 if (!coa.isOK(replacements)) {
415363 boolean changed = coa.tryChange(replacements, tryMerge);
416 if (changed){
364 if (changed) {
417365 if (DEBUG_PATH != null)
418366 changedPlaces.add(coa.center);
419367 continue;
432380 boolean lastWayModified = false;
433381 ConvertedWay lastConvertedWay = null;
434382 for (ConvertedWay cw : convertedWays) {
435 if (!cw.isValid() || cw.isOverlay())
383 if (!cw.isValid() || cw.isOverlay() || !waysWithBearingErrors.contains(cw.getWay()))
436384 continue;
437385 Way way = cw.getWay();
438 if (waysWithBearingErrors.contains(way) == false)
439 continue;
440386 List<Coord> points = way.getPoints();
441387 if (way.equals(lastWay)) {
442 if (lastWayModified){
388 if (lastWayModified) {
443389 points.clear();
444390 points.addAll(lastWay.getPoints());
445391 if (cw.isReversed() != lastConvertedWay.isReversed())
460406 points.remove(i);
461407 anotherPassRequired = true;
462408 lastWayModified = true;
463 if (i > 0 && i < points.size()) {
409 if (i > 0 && i < points.size() && points.get(i - 1) == points.get(i)) {
464410 // special case: handle micro loop
465 if (points.get(i - 1) == points.get(i))
466 points.remove(i);
467 }
411 points.remove(i);
412 }
468413 continue;
469414 }
470415 // check if this point is to be replaced because
471416 // it was previously moved
472417 Coord replacement = getReplacement(p, way, replacements);
473 if (p == replacement)
418 if (p == replacement)
474419 continue;
475420
476 if (p.isViaNodeOfRestriction()){
421 if (p.isViaNodeOfRestriction()) {
477422 // make sure that we find the restriction with the new coord instance
478423 replacement.setViaNodeOfRestriction(true);
479424 p.setViaNodeOfRestriction(false);
491436 points.remove(i);
492437 anotherPassRequired = true;
493438 }
494 if (i -1 >= 0 && points.get(i-1) == p){
439 if (i - 1 >= 0 && points.get(i - 1) == p) {
495440 points.remove(i);
496441 anotherPassRequired = true;
497442 }
498443 }
499 if (lastWayModified && mode == MODE_ROADS){
444 if (lastWayModified && mode == MODE_ROADS) {
500445 modifiedRoads.put(way.getId(), cw);
501446 }
502447 }
532477 if (points.size() < 2) {
533478 if (log.isInfoEnabled())
534479 log.info(" Way " + way.getTag("name") + " (" + way.toBrowseURL() + ") has less than 2 points - deleting it");
535 if (mode == MODE_LINES && waysThatMapToOnePoint.contains(way.getId()) == false)
536 log.warn("non-routable way " ,way.getId(),"was removed");
480 if (mode == MODE_LINES && !waysThatMapToOnePoint.contains(way.getId())) {
481 log.warn("non-routable way ", way.getId(), "was removed");
482 }
537483
538484 if (mode == MODE_ROADS)
539485 deletedRoads.add(way.getId());
541487 continue;
542488 }
543489 if (way.equals(lastWay)) {
544 if (lastWayModified){
490 if (lastWayModified) {
545491 points.clear();
546492 points.addAll(lastWay.getPoints());
547493 if (cw.isReversed() != lastConvertedWay.isReversed())
548494 Collections.reverse(points);
549
495
550496 }
551497 continue;
552498 }
557503 // loop backwards because we may delete points
558504 for (int i = points.size() - 2; i >= 0; i--) {
559505 Coord p = points.get(i);
560 if (p == prev){
506 if (p == prev) {
561507 points.remove(i);
562508 lastWayModified = true;
563509 }
564 // if (p.equals(prev) && (p.getHighwayCount() < 2 || prev.getHighwayCount() < 2)){
565 // // not an error, but should not happen
566 // log.warn("way " + way.getId() + " still has consecutive equal points at " + p.toOSMURL());
567 // }
568510 prev = p;
569511 }
570512 }
571 if (mode == MODE_ROADS){
513 if (mode == MODE_ROADS) {
572514 // treat special case: non-routable ways may be connected to moved
573515 // points in roads
574516 for (ConvertedWay cw : lines) {
590532 }
591533 }
592534
593 for (RestrictionRelation rr: restrictions){
594 for (Coord p: rr.getViaCoords()){
535 for (RestrictionRelation rr : restrictions) {
536 for (Coord p : rr.getViaCoords()) {
595537 Coord replacement = getReplacement(p, null, replacements);
596 if (p != replacement){
538 if (p != replacement) {
597539 rr.replaceViaCoord(p, replacement);
598540 }
599541 }
607549 }
608550 if (anotherPassRequired) {
609551 log.warn("Removing wrong angles - didn't finish in " + pass + " passes, giving up!");
610 }
611 else
612 log.info("Removing wrong angles - finished in", pass, "passes (", numNodesMerged, "nodes merged,", numWaysDeleted, "ways deleted)");
613 }
614
615 private CenterOfAngle getOrCreateCenter(Coord p, Way way, IdentityHashMap<Coord, CenterOfAngle> centerMap, List<CenterOfAngle> centers, Map<Coord, Set<Way>> overlaps) {
552 } else {
553 log.info("Removing wrong angles - finished in", pass, "passes (", numNodesMerged, "nodes merged,",
554 numWaysDeleted, "ways deleted)");
555 }
556 }
557
558 private CenterOfAngle getOrCreateCenter(Coord p, Way way, IdentityHashMap<Coord, CenterOfAngle> centerMap,
559 List<CenterOfAngle> centers, Map<Coord, Set<Way>> overlaps) {
616560 CenterOfAngle coa = centerMap.get(p);
617561 if (coa == null) {
618562 coa = new CenterOfAngle(p, centerMap.size() + 1);
619563 centerMap.put(p, coa);
620564 centers.add(coa);
621565 if (mode == MODE_ROADS && pass > 1) {
622 Set<Way> set = overlaps.get(p);
623 if (set == null) {
624 set = new HashSet<>();
625 overlaps.put(p, set);
626 }
627 set.add(way);
628 }
629
566 overlaps.computeIfAbsent(p, k -> new HashSet<>()).add(way);
567 }
630568 }
631569 return coa;
632570 }
633571
634572 private void markOverlaps(Map<Coord, Set<Way>> overlaps, List<CenterOfAngle> centers) {
635 for ( Entry<Coord, Set<Way>> entry: overlaps.entrySet()) {
573 for (Entry<Coord, Set<Way>> entry : overlaps.entrySet()) {
636574 if (entry.getValue().size() > 1) {
637575 Coord p = entry.getKey();
638 // log.error("roads",mode==MODE_ROADS,"pass",pass,p,p.toDegreeString());
639576 for (CenterOfAngle coa : centers) {
640577 if (coa.center.equals(p)) {
641578 // two different centres are on the same Garmin point and they
646583 }
647584 }
648585 }
649
586
650587 /**
651588 * remove obsolete points in ways. Obsolete are points which are
652589 * very close to 180 degrees angles in the real line or wrong points.
655592 * @param convertedWays
656593 * @param modifiedRoads
657594 */
658 private void removeObsoletePoints(List<ConvertedWay> convertedWays, HashMap<Long, ConvertedWay> modifiedRoads){
595 private void removeObsoletePoints(List<ConvertedWay> convertedWays, Map<Long, ConvertedWay> modifiedRoads) {
659596 ConvertedWay lastConvertedWay = null;
660597 int numPointsRemoved = 0;
661598 boolean lastWasModified = false;
668605 continue;
669606 Way way = cw.getWay();
670607 if (lastConvertedWay != null && way.equals(lastConvertedWay.getWay())) {
671 if (lastWasModified){
608 if (lastWasModified) {
672609 List<Coord> points = way.getPoints();
673610 points.clear();
674611 points.addAll(lastConvertedWay.getPoints());
688625
689626 // scan through the way's points looking for points which are
690627 // on almost straight line and therefore obsolete
691 for (int i = 1; i+1 < points.size(); i++) {
628 for (int i = 1; i + 1 < points.size(); i++) {
692629 Coord cm = points.get(i);
693 if (allowedToRemove(cm) == false){
630 if (!allowedToRemove(cm)) {
694631 modifiedPoints.add(cm);
695632 continue;
696633 }
697 Coord c1 = modifiedPoints.get(modifiedPoints.size()-1);
698 Coord c2 = points.get(i+1);
699 if (c1 == c2){
634 Coord c1 = modifiedPoints.get(modifiedPoints.size() - 1);
635 Coord c2 = points.get(i + 1);
636 if (c1 == c2) {
700637 // loop, handled by split routine
701638 modifiedPoints.add(cm);
702 continue;
639 continue;
703640 }
704641
705642 boolean keepThis = true;
706643 double realAngle = Utils.getAngle(c1, cm, c2);
707 if (Math.abs(realAngle) < MAX_DIFF_ANGLE_STRAIGHT_LINE){
644 if (Math.abs(realAngle) < MAX_DIFF_ANGLE_STRAIGHT_LINE) {
708645 double distance = cm.distToLineSegment(c1, c2);
709 if (distance >= maxErrorDistance){
646 if (distance >= maxErrorDistance) {
710647 modifiedPoints.add(cm);
711648 continue;
712649 }
713650 keepThis = false;
714651 } else {
715652 double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
716 if (displayedAngle < 0 && realAngle > 0 || displayedAngle > 0 && realAngle < 0){
717 // straight line is closer to real angle
653 if (displayedAngle < 0 && realAngle > 0 || displayedAngle > 0 && realAngle < 0) {
654 // straight line is closer to real angle
718655 keepThis = false;
719 } else if (Math.abs(displayedAngle) < 1){
656 } else if (Math.abs(displayedAngle) < 1) {
720657 // displayed line is nearly straight
721 if (c1.getHighwayCount() < 2 && c2.getHighwayCount() < 2){
658 if (c1.getHighwayCount() < 2 && c2.getHighwayCount() < 2) {
722659 // we can remove the point
723660 keepThis = false;
724661 }
725 } else if (Math.abs(realAngle-displayedAngle) > 2 * Math.abs(realAngle) && Math.abs(realAngle) < MAX_BEARING_ERROR_HALF){
726 // displayed angle is much sharper than wanted, straight line is closer to real angle
662 } else if (Math.abs(realAngle - displayedAngle) > 2 * Math.abs(realAngle)
663 && Math.abs(realAngle) < MAX_BEARING_ERROR_HALF) {
664 // displayed angle is much sharper than wanted, straight line is closer to real
665 // angle
727666 keepThis = false;
728667 } else if (c1.equals(c2)) {
729668 // spike / overlap
730 log.debug("pass",pass,"roads=" + (mode == MODE_ROADS), "extra remove to remove spike or overlap near", cm.toDegreeString());
669 log.debug("pass", pass, "roads=" + (mode == MODE_ROADS),
670 "extra remove to remove spike or overlap near", cm.toDegreeString());
731671 keepThis = false;
732
733 }
734 }
735 if (keepThis){
672
673 }
674 }
675 if (keepThis) {
736676 modifiedPoints.add(cm);
737677 continue;
738678 }
739 if (log.isDebugEnabled())
740 log.debug("removing obsolete point on almost straight segment in way ",way.toBrowseURL(),"at",cm.toOSMURL());
741 if (DEBUG_PATH != null){
679 if (log.isDebugEnabled()) {
680 log.debug("removing obsolete point on almost straight segment in way ", way.toBrowseURL(), "at",
681 cm.toOSMURL());
682 }
683 if (DEBUG_PATH != null) {
742684 obsoletePoints.add(cm);
743685 removedInWay.add(cm);
744686 }
745687 numPointsRemoved++;
746688 lastWasModified = true;
747
748 }
749 if (lastWasModified){
750 modifiedPoints.add(points.get(points.size()-1));
689
690 }
691 if (lastWasModified) {
692 modifiedPoints.add(points.get(points.size() - 1));
751693 points.clear();
752694 points.addAll(modifiedPoints);
753695 if (mode == MODE_ROADS)
754696 modifiedRoads.put(way.getId(), cw);
755 if (DEBUG_PATH != null){
756 if (draw || cw.isRoundabout()) {
757 GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, way.getId() + "_dpmod"), points, removedInWay);
758 }
759 }
760 }
761 }
762 if (DEBUG_PATH != null){
763 GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, (mode==MODE_ROADS ? "roads" : "lines")+"_obsolete" ), bbox.toCoords(),
764 new ArrayList<>(obsoletePoints));
765
697 if (DEBUG_PATH != null && (draw || cw.isRoundabout())) {
698 GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, way.getId() + "_dpmod"), points, removedInWay);
699 }
700 }
701 }
702 if (DEBUG_PATH != null) {
703 GpxCreator.createGpx(Utils.joinPath(DEBUG_PATH, (mode == MODE_ROADS ? "roads" : "lines") + "_obsolete"),
704 bbox.toCoords(), new ArrayList<>(obsoletePoints));
766705 }
767706 log.info("Removed", numPointsRemoved, "obsolete points in lines");
768707 }
792731 // on almost straight line and therefore obsolete
793732 for (int i = points.size() - 2; i >= 1; --i) {
794733 Coord cm = points.get(i);
795 Coord c1 = points.get(i-1);
796 Coord c2 = points.get(i+1);
797 if (c1 == c2){
734 Coord c1 = points.get(i - 1);
735 Coord c2 = points.get(i + 1);
736 if (c1 == c2) {
798737 // loop, handled by split routine
799 continue;
738 continue;
800739 }
801740 double realAngle = Utils.getAngle(c1, cm, c2);
802741 double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
803 if (Math.abs(displayedAngle-realAngle) > 30){
742 if (Math.abs(displayedAngle - realAngle) > 30) {
804743 badAngles.add(cm);
805744 hasBadAngles = true;
806 // badAngles.addAll(cm.getAlternativePositions());
807 }
808
809 }
810 if (points.size() > 2){
745 }
746
747 }
748 if (points.size() > 2) {
811749 Coord p0 = points.get(0);
812 Coord plast = points.get(points.size()-1);
813 if (p0 == plast){
750 Coord plast = points.get(points.size() - 1);
751 if (p0 == plast) {
814752 Coord cm = points.get(0);
815 Coord c1 = points.get(points.size()-2);
753 Coord c1 = points.get(points.size() - 2);
816754 Coord c2 = points.get(1);
817 if (c1 == c2){
755 if (c1 == c2) {
818756 // loop, handled by split routine
819 continue;
757 continue;
820758 }
821759 double realAngle = Utils.getAngle(c1, cm, c2);
822760 double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2);
823 if (Math.abs(displayedAngle-realAngle) > 30){
761 if (Math.abs(displayedAngle - realAngle) > 30) {
824762 badAngles.add(cm);
825763 hasBadAngles = true;
826 // badAngles.addAll(cm.getAlternativePositions());
827764 }
828765 }
829766 }
844781 return false;
845782 if (mode == MODE_LINES && p.isEndOfWay())
846783 return false;
847 if (p instanceof CoordPOI){
848 if (((CoordPOI) p).isUsed()){
784 if (p instanceof CoordPOI && ((CoordPOI) p).isUsed()){
849785 return false;
850 }
851 }
852 if (p.getHighwayCount() >= 2 || p.isViaNodeOfRestriction()) {
853 return false;
854 }
855 return true;
786 }
787 return p.getHighwayCount() < 2 && !p.isViaNodeOfRestriction();
856788 }
857789
858790 /**
859791 * helper class
860792 */
861793 private class CenterOfAngle {
862 public boolean forceChange;
794 boolean forceChange;
863795 final Coord center;
864796 final List<CenterOfAngle> neighbours;
865797 final int id; // debugging aid
869801
870802 public CenterOfAngle(Coord center, int id) {
871803 this.center = center;
872 assert center.isReplaced() == false;
804 assert !center.isReplaced();
873805 this.id = id;
874806 neighbours = new ArrayList<>();
875807 }
876808
877809 @Override
878810 public String toString() {
879 return "CenterOfAngle [id=" + id + " " + center.toString() + " " + center.toDegreeString() + ", wasMerged=" + wasMerged + ", num Neighbours="+neighbours.size()+"]";
811 return "CenterOfAngle [id=" + id + " " + center.toString() + " " + center.toDegreeString() + ", wasMerged="
812 + wasMerged + ", num Neighbours=" + neighbours.size() + "]";
880813 }
881814
882815 /**
884817 * @param replacements
885818 * @return
886819 */
887 public Coord getCurrentLocation(Map<Coord, Coord> replacements){
820 public Coord getCurrentLocation(Map<Coord, Coord> replacements) {
888821 Coord c = getReplacement(center, null, replacements);
889822 if (c.isToRemove())
890823 return null;
902835 }
903836
904837 public void addNeighbour(CenterOfAngle other) {
905 if (this == other){
906 log.error("neighbour is equal" );
838 if (this == other) {
839 log.error("neighbour is equal");
907840 }
908841 boolean isNew = true;
909842 // we want only different Coord instances here
925858 public boolean isOK(Map<Coord, Coord> replacements) {
926859 if (forceChange)
927860 return false;
928 Coord c = getCurrentLocation (replacements);
861 Coord c = getCurrentLocation(replacements);
929862 if (c == null)
930863 return true; // removed center: nothing to do
931864 for (CenterOfAngle neighbour : neighbours) {
939872 return true;
940873 }
941874
875 private void replaceCoord(Coord toRepl, Coord replacement, Map<Coord, Coord> replacements) {
876 assert toRepl != replacement;
877 if (toRepl.getOnBoundary()) {
878 if (!replacement.equals(toRepl)) {
879 log.error("boundary node is replaced by node with non-equal coordinates at", toRepl.toOSMURL());
880 assert false : "boundary node is replaced";
881 }
882 replacement.setOnBoundary(true);
883 }
884 if (toRepl.getOnCountryBorder()) {
885 if (!replacement.equals(toRepl)) {
886 log.warn("country boundary node is replaced by node with non-equal coordinates at",
887 toRepl.toOSMURL());
888 }
889 replacement.setOnCountryBorder(true);
890 }
891 toRepl.setReplaced(true);
892 if (toRepl instanceof CoordPOI) {
893 CoordPOI cp = (CoordPOI) toRepl;
894 if (cp.isUsed()) {
895 replacement = new CoordPOI(replacement);
896 ((CoordPOI) replacement).setNode(cp.getNode());
897 ((CoordPOI) replacement).setUsed(true);
898 ((CoordPOI) replacement).setConvertToViaInRouteRestriction(cp.getConvertToViaInRouteRestriction());
899 if (!replacement.highPrecEquals(cp.getNode().getLocation())) {
900 log.error("CoordPOI node is replaced with non-equal coordinates at", toRepl.toOSMURL());
901 }
902 }
903 }
904 if (toRepl.isViaNodeOfRestriction())
905 replacement.setViaNodeOfRestriction(true);
906 replacements.put(toRepl, replacement);
907 while (toRepl.getHighwayCount() > replacement.getHighwayCount()) {
908 replacement.incHighwayCount();
909 }
910 if (toRepl.isEndOfWay()) {
911 replacement.setEndOfWay(true);
912 }
913 }
914
942915 /**
943916 * Try whether a move or remove or merge of this centre
944917 * fixes bearing problems.
947920 * @return true if something was changed
948921 */
949922 public boolean tryChange(Map<Coord, Coord> replacements, boolean tryAlsoMerge) {
950 if (wasMerged ) {
923 if (wasMerged) {
951924 return false;
952925 }
953926 Coord currentCenter = getCurrentLocation(replacements);
966939 if (!dupCheck.add(n)) {
967940 hasDups = true;
968941 }
969 if (currentCenter.highPrecEquals(n)){
970 if (currentCenter == n){
942 if (currentCenter.highPrecEquals(n)) {
943 if (currentCenter == n) {
971944 log.error(id + ": bad neighbour " + neighbour.id + " zero distance");
972945 }
973 if (badMergeCandidates != null && badMergeCandidates.contains(neighbour )
946 if (badMergeCandidates != null && badMergeCandidates.contains(neighbour)
974947 || neighbour.badMergeCandidates != null && neighbour.badMergeCandidates.contains(this)) {
975 //not allowed to merge
948 // not allowed to merge
976949 } else {
977950 replaceCoord(currentCenter, n, replacements);
978951 neighbour.wasMerged = wasMerged = true;
991964 if (initialMaxError < MAX_BEARING_ERROR)
992965 return false;
993966 double removeErr = calcRemoveError(replacements);
994 if (removeErr == 0){
967 if (removeErr == 0) {
995968 currentCenter.setRemove(true);
996969 return true;
997970 }
999972 if (extraPass && tryAlsoMerge && currentCenter.getHighwayCount() > 1) {
1000973 // spike
1001974 List<Coord> altPositions = currentCenter.getAlternativePositions();
1002 for (Coord altCenter : altPositions){
975 for (Coord altCenter : altPositions) {
1003976 if (dupCheck.contains(altCenter)) {
1004 log.debug("pass",pass,"roads=" + (mode == MODE_ROADS), "extra move to remove spike or overlap near", currentCenter.toDegreeString());
977 log.debug("pass", pass, "roads=" + (mode == MODE_ROADS),
978 "extra move to remove spike or overlap near", currentCenter.toDegreeString());
1005979 replaceCoord(currentCenter, altCenter, replacements);
1006980 return true;
1007981 }
1018992 double bestReplErr = initialMaxError;
1019993 Coord bestCenterReplacement = null;
1020994 List<Coord> altPositions = currentCenter.getAlternativePositions();
1021 for (Coord altCenter : altPositions){
995 for (Coord altCenter : altPositions) {
1022996 double err = calcBearingError(altCenter, worstNP);
1023997 if (err >= bestReplErr)
1024998 continue;
1025999 // alt. position is improvement, check all neighbours
10261000 double errMax = calcMaxError(replacements, currentCenter, altCenter);
1027 if (errMax >= initialMaxError)
1028 continue;
1029 bestReplErr = err;
1030 bestCenterReplacement = altCenter;
1001 if (errMax < initialMaxError) {
1002 bestReplErr = err;
1003 bestCenterReplacement = altCenter;
1004 }
10311005 }
10321006 Coord bestNeighbourReplacement = null;
10331007 // calculate effect when both this and the worst neighbour are changed.
1034 if (worstNP.hasAlternativePos()){
1035 for (Coord altCenter : altPositions){
1008 if (worstNP.hasAlternativePos()) {
1009 for (Coord altCenter : altPositions) {
10361010 replaceCoord(currentCenter, altCenter, replacements);
1037 for (Coord altN: worstNP.getAlternativePositions()){
1011 for (Coord altN : worstNP.getAlternativePositions()) {
10381012 double err = calcBearingError(altCenter, altN);
10391013 if (err >= bestReplErr)
10401014 continue;
10411015 double errNeighbour = worstNeighbour.calcMaxError(replacements, worstNP, altN);
1042 if (errNeighbour >= bestReplErr)
1043 continue;
1044 bestReplErr = err;
1045 bestCenterReplacement = altCenter;
1046 bestNeighbourReplacement = altN;
1016 if (errNeighbour < bestReplErr) {
1017 bestReplErr = err;
1018 bestCenterReplacement = altCenter;
1019 bestNeighbourReplacement = altN;
1020 }
10471021 }
10481022 replacements.remove(currentCenter);
10491023 currentCenter.setReplaced(false);
10501024 }
10511025 }
10521026 boolean specialChange = false;
1053 if (extraPass && bestReplErr >= MAX_BEARING_ERROR && bestCenterReplacement != null && bestReplErr * 2 < initialMaxError && initialMaxError < 180) {
1027 if (extraPass && bestReplErr >= MAX_BEARING_ERROR && bestCenterReplacement != null
1028 && bestReplErr * 2 < initialMaxError && initialMaxError < 180) {
10541029 specialChange = true;
10551030 }
1056 if (bestReplErr < MAX_BEARING_ERROR || specialChange){
1057 if (removeErr < bestReplErr && initialMaxError - removeErr >= MAX_BEARING_ERROR_HALF && removeErr < MAX_BEARING_ERROR_HALF){
1031 if (bestReplErr < MAX_BEARING_ERROR || specialChange) {
1032 if (removeErr < bestReplErr && initialMaxError - removeErr >= MAX_BEARING_ERROR_HALF
1033 && removeErr < MAX_BEARING_ERROR_HALF) {
10581034 bestCenterReplacement = null;
10591035 }
1060 if (bestCenterReplacement != null){
1036 if (bestCenterReplacement != null) {
10611037 replaceCoord(currentCenter, bestCenterReplacement, replacements);
10621038 if (bestNeighbourReplacement != null)
10631039 replaceCoord(worstNP, bestNeighbourReplacement, replacements);
10641040 double modifiedSumErr = calcSumOfErrors(replacements);
1065 if (modifiedSumErr < initialSumErr){
1041 if (modifiedSumErr < initialSumErr) {
10661042 if (specialChange)
1067 log.debug("pass",pass,"special repl",this);
1043 log.debug("pass", pass, "special repl", this);
10681044 return true;
10691045 }
10701046 // revert changes
10721048 currentCenter.setReplaced(false);
10731049 replacements.remove(worstNP);
10741050 worstNP.setReplaced(false);
1075 bestCenterReplacement = null;
1076 }
1077 }
1078 if (removeErr < MAX_BEARING_ERROR){
1051 }
1052 }
1053 if (removeErr < MAX_BEARING_ERROR) {
10791054 // createGPX(gpxPath+id+"_rem", replacements);
10801055 currentCenter.setRemove(true);
10811056 return true;
11001075 * @return true if merge is okay
11011076 */
11021077 private boolean tryMerge(double initialMaxError, CenterOfAngle neighbour, Map<Coord, Coord> replacements) {
1103 if (badMergeCandidates != null && badMergeCandidates.contains(neighbour )
1078 if (badMergeCandidates != null && badMergeCandidates.contains(neighbour)
11041079 || neighbour.badMergeCandidates != null && neighbour.badMergeCandidates.contains(this)) {
11051080 return false; // not allowed to merge
11061081 }
11101085 // 1) both points are via nodes
11111086 // 2) both nodes are boundary nodes with non-equal coords
11121087 // 3) one point is via node and the other is a boundary node, the result could be that the restriction is ignored.
1113 if (c.getOnBoundary()){
1114 if (n.isViaNodeOfRestriction() || n.getOnBoundary() && c.equals(n) == false)
1115 return false;
1116 }
1117 if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || n.getOnBoundary()))
1118 return false;
1119 if (c instanceof CoordPOI && (n instanceof CoordPOI || n.getOnBoundary()))
1120 return false;
1121 if (n instanceof CoordPOI && (c instanceof CoordPOI || c.getOnBoundary()))
1088 if (c.getOnBoundary() && (n.isViaNodeOfRestriction() || (n.getOnBoundary() && !c.equals(n)))
1089 || (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || n.getOnBoundary()))
1090 || (c instanceof CoordPOI && (n instanceof CoordPOI || n.getOnBoundary()))
1091 || (n instanceof CoordPOI && (c instanceof CoordPOI || c.getOnBoundary())))
11221092 return false;
11231093 Coord mergePoint;
11241094
11321102 mergePoint = c.makeBetweenPoint(n, 0.5);
11331103 double err = 0;
11341104
1135 if (c.equals(n) == false){
1105 if (!c.equals(n)) {
11361106 err = calcMergeErr(neighbour, mergePoint, replacements);
1137 if (err == Double.MAX_VALUE && initialMaxError == Double.MAX_VALUE){
1138 log.warn("still equal neighbour after merge",c.toOSMURL());
1139 } else {
1107 if (err == Double.MAX_VALUE && initialMaxError == Double.MAX_VALUE) {
1108 log.warn("still equal neighbour after merge", c.toOSMURL());
1109 } else {
11401110 if (err >= MAX_BEARING_ERROR)
11411111 return false;
1142 if (initialMaxError - err < MAX_BEARING_ERROR_HALF && err > MAX_BEARING_ERROR_HALF){
1112 if (initialMaxError - err < MAX_BEARING_ERROR_HALF && err > MAX_BEARING_ERROR_HALF) {
11431113 return false; // improvement too small
11441114 }
11451115 }
11531123 }
11541124 }
11551125 int hwc = c.getHighwayCount() + n.getHighwayCount() - 1;
1156 for (int i = mergePoint.getHighwayCount(); i < hwc; i++)
1126 for (int i = mergePoint.getHighwayCount(); i < hwc; i++) {
11571127 mergePoint.incHighwayCount();
1128 }
11581129 if (c != mergePoint)
11591130 replaceCoord(c, mergePoint, replacements);
1160 if (n != mergePoint){
1131 if (n != mergePoint) {
11611132 replaceCoord(n, mergePoint, replacements);
11621133 }
1163 // createGPX(gpxPath+id+"_merged", replacements);
1134 // createGPX(gpxPath+id+"_merged", replacements);
11641135 // neighbour.createGPX(gpxPath+neighbour.id+"_merged_w_"+id, replacements);
11651136 neighbour.wasMerged = wasMerged = true;
11661137 return true;
11741145 * @param replacements s
11751146 * @return true if a nearly straight line exists
11761147 */
1177 private boolean checkNearlyStraight(double bearing, CenterOfAngle other,
1178 Map<Coord, Coord> replacements) {
1148 private boolean checkNearlyStraight(double bearing, CenterOfAngle other, Map<Coord, Coord> replacements) {
11791149 Coord c = getCurrentLocation(replacements);
11801150 for (CenterOfAngle neighbour : neighbours) {
1181 if (neighbour == other)
1151 if (neighbour == other)
11821152 continue;
11831153 Coord n = neighbour.getCurrentLocation(replacements);
1184 if (n == null)
1185 continue;
1186 double bearing2 = c.bearingTo(n);
1187 double angle = bearing2 - (bearing - 180);
1188 while(angle > 180)
1189 angle -= 360;
1190 while(angle < -180)
1191 angle += 360;
1192 if (Math.abs(angle) < 10) // tolerate small angle
1193 return true;
1154 if (n != null) {
1155 double bearing2 = c.bearingTo(n);
1156 double angle = bearing2 - (bearing - 180);
1157 while (angle > 180)
1158 angle -= 360;
1159 while (angle < -180)
1160 angle += 360;
1161 if (Math.abs(angle) < 10) // tolerate small angle
1162 return true;
1163 }
11941164 }
11951165 return false;
11961166 }
12051175 private double calcMergeErr(CenterOfAngle other, Coord mergePoint, Map<Coord, Coord> replacements) {
12061176 double maxErr = 0;
12071177 for (CenterOfAngle neighbour : neighbours) {
1208 if (neighbour == other)
1178 if (neighbour == other)
12091179 continue;
12101180 Coord n = neighbour.getCurrentLocation(replacements);
1211 if (n != null){
1181 if (n != null) {
12121182 double err = calcBearingError(mergePoint, n);
12131183 if (err > maxErr)
12141184 maxErr = err;
12151185 }
12161186 }
12171187 for (CenterOfAngle othersNeighbour : other.neighbours) {
1218 if (othersNeighbour == this)
1188 if (othersNeighbour == this)
12191189 continue;
12201190 Coord n = othersNeighbour.getCurrentLocation(replacements);
1221 if (n != null){
1191 if (n != null) {
12221192 double err = calcBearingError(mergePoint, n);
12231193 if (err > maxErr)
12241194 maxErr = err;
12341204 * @param replacement see toRepl
12351205 * @return error [0..180] or Double.MAX_VALUE in case of equal points
12361206 */
1237 private double calcMaxError(Map<Coord, Coord> replacements,
1238 Coord toRepl, Coord replacement) {
1207 private double calcMaxError(Map<Coord, Coord> replacements, Coord toRepl, Coord replacement) {
12391208 double maxErr = 0;
12401209 Coord c = getCurrentLocation(replacements);
12411210 for (CenterOfAngle neighbour : neighbours) {
12471216 err = calcBearingError(replacement, n);
12481217 else if (n == toRepl)
12491218 err = calcBearingError(c, replacement);
1250 else
1219 else
12511220 err = calcBearingError(c, n);
12521221 if (err == Double.MAX_VALUE)
12531222 return err;
12631232 * @return
12641233 */
12651234 private double calcSumOfErrors(Map<Coord, Coord> replacements) {
1266 double SumErr = 0;
1235 double sumErr = 0;
12671236 Coord c = getCurrentLocation(replacements);
12681237 for (CenterOfAngle neighbour : neighbours) {
12691238 Coord n = neighbour.getCurrentLocation(replacements);
12721241 double err = calcBearingError(c, n);
12731242 if (err == Double.MAX_VALUE)
12741243 return err;
1275 SumErr += err;
1276 }
1277 return SumErr;
1244 sumErr += err;
1245 }
1246 return sumErr;
12781247 }
12791248
12801249 /**
12831252 * @return Double.MAX_VALUE if centre must not be deleted, else [0..180]
12841253 */
12851254 private double calcRemoveError(Map<Coord, Coord> replacements) {
1286 if (allowedToRemove(center) == false)
1255 if (!allowedToRemove(center))
12871256 return Double.MAX_VALUE;
12881257 if (neighbours.size() > 2)
12891258 return Double.MAX_VALUE;
12941263 Coord n = neighbour.getCurrentLocation(replacements);
12951264 if (n == null)
12961265 return Double.MAX_VALUE;
1297 if (c.equals(n)){
1298 if (!allowedToRemove(neighbour.center))
1266 if (c.equals(n)) {
1267 if (!allowedToRemove(neighbour.center)
1268 || c.getDistToDisplayedPoint() >= n.getDistToDisplayedPoint()) {
12991269 return 0;
1300 if (c.getDistToDisplayedPoint() >= n.getDistToDisplayedPoint()) {
1301 return 0;
13021270 }
13031271 return Double.MAX_VALUE;
13041272 }
13051273 outerPoints[i] = n;
13061274 }
1307 if (neighbours.size() < 2 )
1275 if (neighbours.size() < 2)
13081276 return Double.MAX_VALUE;
13091277
13101278 for (int i = 0; i < neighbours.size(); i++) {
13171285 if (Math.abs( dsplAngle ) < 3)
13181286 return Double.MAX_VALUE;
13191287 double realAngle = Utils.getAngle(outerPoints[0], c, outerPoints[1]);
1320 double err = Math.abs(realAngle) / 2;
1321 return err;
1288 return Math.abs(realAngle) / 2;
13221289 }
13231290
13241291 @SuppressWarnings("unused")
13351302 Coord nc = getReplacement(n.center, null, replacements);
13361303 if (nc == null)
13371304 continue; // skip removed neighbour
1338 if (i == 0 && alternatives.isEmpty() == false) {
1339 GpxCreator.createGpx(gpxName + "_" + i,
1340 Arrays.asList(c, nc), alternatives);
1341 } else
1342 GpxCreator.createGpx(gpxName + "_" + i,
1343 Arrays.asList(c, nc));
1344 }
1345 if (neighbours.isEmpty())
1346 GpxCreator.createGpx(gpxName + "_empty", Arrays.asList(c, c),
1347 alternatives);
1348 }
1349
1305 if (i == 0 && !alternatives.isEmpty()) {
1306 GpxCreator.createGpx(gpxName + "_" + i, Arrays.asList(c, nc), alternatives);
1307 } else {
1308 GpxCreator.createGpx(gpxName + "_" + i, Arrays.asList(c, nc));
1309 }
1310 }
1311 if (neighbours.isEmpty()) {
1312 GpxCreator.createGpx(gpxName + "_empty", Arrays.asList(c, c), alternatives);
1313 }
1314 }
13501315 }
13511316
1352 private void writeOSM(String name, List<ConvertedWay> convertedWays){
1317 private void writeOSM(String name, List<ConvertedWay> convertedWays) {
13531318 if (DEBUG_PATH != null) {
13541319 //TODO: comment before release
13551320 // uk.me.parabola.mkgmap.osmstyle.optional.DebugWriter.writeOSM(bbox, DEBUG_PATH, name, convertedWays);
13561321 }
13571322 }
13581323
1359 private static double calcBearingError(Coord p1, Coord p2){
1324 private static double calcBearingError(Coord p1, Coord p2) {
13601325 if (p1.equals(p2) || p1.highPrecEquals(p2)) {
13611326 return Double.MAX_VALUE;
13621327 }
13631328 double realBearing = p1.bearingTo(p2);
13641329 double displayedBearing = p1.getDisplayedCoord().bearingTo(p2.getDisplayedCoord());
13651330 double err = displayedBearing - realBearing;
1366 while(err > 180)
1331 while (err > 180)
13671332 err -= 360;
1368 while(err < -180)
1333 while (err < -180)
13691334 err += 360;
13701335 return Math.abs(err);
13711336 }
1372
13731337
13741338 /**
13751339 * Calculate the rounding error tolerance for a given point.
13761340 * The latitude error may be max higher. Maybe this should be
13771341 * @param p0
1378 * @return
1342 * @return the rounding error tolerance for a given point
13791343 */
1380 private static double calcMaxErrorDistance(Coord p0){
1381 Coord test = new Coord(p0.getLatitude(),p0.getLongitude()+1);
1382 double lonErr = p0.getDisplayedCoord().distance(test) / 2;
1383 return lonErr;
1344 private static double calcMaxErrorDistance(Coord p0) {
1345 Coord test = new Coord(p0.getLatitude(), p0.getLongitude() + 1);
1346 return p0.getDisplayedCoord().distance(test) / 2;
13841347 }
13851348
13861349 /**
13971360 int n = points.size();
13981361 // scan through the way's points looking for points which are
13991362 // on almost straight line and therefore obsolete
1400 for (int i = 0; i+1 < points.size(); i++) {
1363 for (int i = 0; i + 1 < points.size(); i++) {
14011364 Coord c1;
1402 if (modifiedPoints.size() > 0)
1403 c1 = modifiedPoints.get(modifiedPoints.size()-1);
1404 else {
1405 c1 = (i > 0) ? points.get(i-1):points.get(n-2);
1365 if (!modifiedPoints.isEmpty()) {
1366 c1 = modifiedPoints.get(modifiedPoints.size() - 1);
1367 } else {
1368 c1 = (i > 0) ? points.get(i - 1) : points.get(n - 2);
14061369 }
14071370 Coord cm = points.get(i);
1408 if (cm.highPrecEquals(c1)){
1409 if (modifiedPoints.size() > 1){
1410 modifiedPoints.remove(modifiedPoints.size()-1);
1411 c1 = modifiedPoints.get(modifiedPoints.size()-1); // might be part of spike
1371 if (cm.highPrecEquals(c1)) {
1372 if (modifiedPoints.size() > 1) {
1373 modifiedPoints.remove(modifiedPoints.size() - 1);
1374 c1 = modifiedPoints.get(modifiedPoints.size() - 1); // might be part of spike
14121375 } else {
14131376 continue;
14141377 }
14151378 }
1416 Coord c2 = points.get(i+1);
1379 Coord c2 = points.get(i + 1);
14171380 int straightTest = Utils.isHighPrecStraight(c1, cm, c2);
1418 if (straightTest == Utils.STRICTLY_STRAIGHT || straightTest == Utils.STRAIGHT_SPIKE){
1381 if (straightTest == Utils.STRICTLY_STRAIGHT || straightTest == Utils.STRAIGHT_SPIKE) {
14191382 continue;
14201383 }
14211384 double realAngle = Utils.getAngle(c1, cm, c2);
1422 if (Math.abs(realAngle) < MAX_DIFF_ANGLE_STRAIGHT_LINE){
1385 if (Math.abs(realAngle) < MAX_DIFF_ANGLE_STRAIGHT_LINE) {
14231386 double distance = cm.distToLineSegment(c1, c2);
14241387 if (distance < maxErrorDistance)
14251388 continue;
14261389 }
14271390 modifiedPoints.add(cm);
14281391 }
1429 if (modifiedPoints.size() > 1 && modifiedPoints.get(0) != modifiedPoints.get(modifiedPoints.size()-1))
1392 if (modifiedPoints.size() > 1 && modifiedPoints.get(0) != modifiedPoints.get(modifiedPoints.size() - 1))
14301393 modifiedPoints.add(modifiedPoints.get(0));
14311394 return modifiedPoints;
14321395 }
14381401 * @param convertedWays list of ways to filter
14391402 * @param modifiedRoads if ways are routable we add the modified ways to this map
14401403 */
1441 private void prepWithDouglasPeucker(List<ConvertedWay> convertedWays, HashMap<Long, ConvertedWay> modifiedRoads) {
1404 private void prepWithDouglasPeucker(List<ConvertedWay> convertedWays, Map<Long, ConvertedWay> modifiedRoads) {
14421405 // we don't want to remove end points of ways
14431406 markEndPoints(convertedWays);
14441407 double maxErrorDistance = 0.05;
14451408 Way lastWay = null;
14461409 for (ConvertedWay cw : convertedWays) {
1447 if (!cw.isValid() || cw.isOverlay())
1410 if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay))
14481411 continue;
14491412 Way way = cw.getWay();
1450 if (way.equals(lastWay))
1451 continue;
14521413 lastWay = way;
14531414 List<Coord> points = way.getPoints();
14541415 List<Coord> coords = new ArrayList<>(points);
14771438 }
14781439 }
14791440
1480 private void markEndPoints(List<ConvertedWay> convertedWays) {
1441 private static void markEndPoints(List<ConvertedWay> convertedWays) {
14811442 Way lastWay = null;
14821443 for (ConvertedWay cw : convertedWays) {
1483 if (!cw.isValid() || cw.isOverlay())
1444 if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay))
14841445 continue;
14851446 Way way = cw.getWay();
1486 if (way.equals(lastWay))
1487 continue;
14881447 lastWay = way;
14891448 way.getFirstPoint().setEndOfWay(true);
14901449 way.getLastPoint().setEndOfWay(true);
168168 */
169169 public int setNumbers(List<HousenumberMatch> housenumbers, int startSegment, int endSegment, boolean left) {
170170 int assignedNumbers = 0;
171 startInRoad = startSegment;
172 endInRoad = endSegment;
171173 if (housenumbers.isEmpty() == false) {
172174 RoadSide rs = new RoadSide();
173175 if (left)
190192 if (maxN >= 0) {
191193 assignedNumbers = maxN + 1;
192194 rs.houses = new ArrayList<>(housenumbers.subList(0, assignedNumbers));
193 startInRoad = startSegment;
194 endInRoad = endSegment;
195 assert startSegment < endSegment;
196 if (getRoad().getPoints().get(startInRoad).isNumberNode() == false || getRoad().getPoints().get(endInRoad).isNumberNode() == false){
197 log.error("internal error: start or end is not a number node", this);
198 }
195 assert startSegment < endSegment : this;
196 checkIfStartIsNumberNode();
199197 }
200198 }
201199 return assignedNumbers;
200 }
201
202 /**
203 * Log an error if start is not a number node
204 */
205 private void checkIfStartIsNumberNode() {
206 if (!getRoad().getPoints().get(startInRoad).isNumberNode()) {
207 log.error("internal error: start is not a number node", this);
208 }
202209 }
203210
204211 /**
332339
333340 /**
334341 * Return the intervals in the format used for the writer routines
335 * @return
342 * @return list of numbers, might be empty but is never null
336343 */
337344 public List<Numbers> getNumberList() {
345 List<Numbers> list = new ArrayList<>();
338346 // do we have numbers?
339 boolean foundNumbers = false;
340 for (ExtNumbers curr = this; curr != null; curr = curr.next){
341 if (curr.hasNumbers()){
342 foundNumbers = true;
343 break;
344 }
345 }
346 if (!foundNumbers)
347 return null;
347 if (!hasNumbersInChain())
348 return list;
348349
349 List<Numbers> list = new ArrayList<>();
350350 boolean headerWasReported = false;
351 for (ExtNumbers curr = this; curr != null; curr = curr.next){
352 if (curr.hasNumbers() == false)
353 continue;
354 list.add(curr.getNumbers());
351
352 for (ExtNumbers curr = this; curr != null; curr = curr.next) {
353 Numbers cn = curr.getNumbers();
354 checkIfStartIsNumberNode();
355 if (!cn.isEmpty()) {
356 list.add(cn);
357 }
355358 if (log.isInfoEnabled()) {
356 if (headerWasReported == false){
359 if (headerWasReported == false) {
357360 MapRoad road = curr.getRoad();
358361 if (road.getStreet() == null && road.getName() == null)
359362 log.info("final numbers for", road, curr.housenumberRoad.getName(), "in", road.getCity());
360 else
363 else
361364 log.info("final numbers for", road, "in", road.getCity());
362365 headerWasReported = true;
363366 }
364 Numbers cn = curr.getNumbers();
365 log.info("Left: ",cn.getLeftNumberStyle(),cn.getIndex(),"Start:",cn.getLeftStart(),"End:",cn.getLeftEnd(), "numbers "+curr.getHouses(Numbers.LEFT));
366 log.info("Right:",cn.getRightNumberStyle(),cn.getIndex(),"Start:",cn.getRightStart(),"End:",cn.getRightEnd(), "numbers "+curr.getHouses(Numbers.RIGHT));
367 log.info("Left: ", cn.getLeftNumberStyle(), cn.getIndex(), "Start:", cn.getLeftStart(), "End:",
368 cn.getLeftEnd(), "numbers " + curr.getHouses(Numbers.LEFT));
369 log.info("Right:", cn.getRightNumberStyle(), cn.getIndex(), "Start:", cn.getRightStart(), "End:",
370 cn.getRightEnd(), "numbers " + curr.getHouses(Numbers.RIGHT));
367371 }
368372 }
369373 return list;
370374 }
371375
376 private boolean hasNumbersInChain() {
377 for (ExtNumbers curr = this; curr != null; curr = curr.next){
378 if (curr.hasNumbers()){
379 return true;
380 }
381 }
382 return false;
383 }
384
372385 public ExtNumbers checkSingleChainSegments(String streetName, boolean removeGaps) {
373386 ExtNumbers curr = this;
374387 ExtNumbers head = this;
11111124 * @param startPos
11121125 */
11131126 private void increaseNodeIndexes(int startPos){
1114 if (hasNumbers() == false)
1115 return;
11161127 if (startInRoad > startPos){
11171128 startInRoad++;
11181129 endInRoad++;
18691880 return sortedByDistToLine;
18701881 }
18711882
1872 /**
1873 * Use Bresemham algorithm to get the Garmin points which are close to the line
1874 * described by c1 and c2 and the point p.
1875 * @param c1
1876 * @param c2
1877 * @param p
1878 * @return the list of points
1879 */
1880 public static List<Coord> rasterLineNearPoint3(Coord c1, Coord c2, double maxDistToLine){
1881 int x0 = c1.getLongitude();
1882 int y0 = c1.getLatitude();
1883 int x1 = c2.getLongitude();
1884 int y1 = c2.getLatitude();
1885 Coord c1Dspl = c1.getDisplayedCoord();
1886 Coord c2Dspl = c2.getDisplayedCoord();
1887 int x = x0, y = y0;
1888 int dx = Math.abs(x1-x), sx = x<x1 ? 1 : -1;
1889 int dy = -Math.abs(y1-y), sy = y<y1 ? 1 : -1;
1890 int err = dx+dy, e2; /* error value e_xy */
1891
1892 List<Coord> rendered = new ArrayList<>();
1893 for(;;){ /* loop */
1894 Coord t = new Coord(y, x);
1895 double distLine = t.distToLineSegment(c1Dspl, c2Dspl);
1896 if (distLine <= maxDistToLine)
1897 rendered.add(t);
1898 if (x==x1 && y==y1)
1899 break;
1900 e2 = 2*err;
1901 if (e2 > dy) { err += dy; x += sx; } /* e_xy+e_x > 0 */
1902 if (e2 < dx) { err += dx; y += sy; } /* e_xy+e_y < 0 */
1903 }
1904 return rendered;
1905 }
1906
19071883 private static int countOccurence(List<HousenumberMatch> houses, int num){
19081884 int count = 0;
19091885 for (HousenumberMatch house : houses){
462462 }
463463 }
464464 }
465 road.getPoints().get(0).setNumberNode(true);
466 road.getPoints().get(road.getPoints().size() - 1).setNumberNode(true);
465467 firstRoadSameOSMWay = road;
466468 String name = road.getStreet();
467 if (name != null) {
468 if (log.isDebugEnabled())
469 log.debug("Housenumber - Streetname:", name, "Way:",osmRoad.getId(),osmRoad.toTagString());
469 if (name != null && log.isDebugEnabled()) {
470 log.debug("Housenumber - Streetname:", name, "Way:",osmRoad.getId(),osmRoad.toTagString());
470471 }
471472 }
472473 }
607608
608609
609610 /**
610 *
611 * @param adder
612 * @param naxNodeId the highest nodeId used before
613 */
614 public void generate(LineAdder adder, int naxNodeId) {
611 * Calculate number nodes for the collected roads
612 * @param adder
613 */
614 public void generate(LineAdder adder) {
615615 if (numbersEnabled) {
616616 MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = findClosestRoadsToHouse();
617617 identifyServiceRoads();
2626 import java.util.Map.Entry;
2727
2828 import uk.me.parabola.imgfmt.app.Coord;
29 import uk.me.parabola.imgfmt.app.CoordNode;
3029 import uk.me.parabola.log.Logger;
3130 import uk.me.parabola.mkgmap.general.CityInfo;
3231 import uk.me.parabola.mkgmap.general.MapRoad;
111110 int prevNodePos = 0;
112111 extNumbersHead = null;
113112 ExtNumbers currNumbers = null;
113 assert road.getPoints().get(0).isNumberNode();
114114 for (Coord p : road.getPoints()) {
115 if (currNodePos == 0) {
116 if (road.skipAddToNOD() == false)
117 assert p instanceof CoordNode;
118 }
119
120115 // An ordinary point in the road.
121116 if (p.isNumberNode() == false) {
122117 currNodePos++;
1515 */
1616 package uk.me.parabola.mkgmap.reader;
1717
18 import java.util.ArrayList;
1918 import java.util.List;
2019
2120 import uk.me.parabola.imgfmt.app.Area;
22 import uk.me.parabola.imgfmt.app.Coord;
21 import uk.me.parabola.imgfmt.app.net.RoadNetwork;
2322 import uk.me.parabola.imgfmt.app.trergn.Overview;
2423 import uk.me.parabola.mkgmap.general.MapDataSource;
2524 import uk.me.parabola.mkgmap.general.MapDetails;
2625 import uk.me.parabola.mkgmap.general.MapLine;
2726 import uk.me.parabola.mkgmap.general.MapPoint;
2827 import uk.me.parabola.mkgmap.general.MapShape;
29 import uk.me.parabola.imgfmt.app.net.RoadNetwork;
3028 import uk.me.parabola.util.Configurable;
3129 import uk.me.parabola.util.EnhancedProperties;
3230
113111 }
114112
115113 public void addBoundaryLine(Area area, int type, String name) {
116 List<Coord> coords = new ArrayList<Coord>();
117 coords.add(new Coord(area.getMinLat(), area.getMinLong()));
118 coords.add(new Coord(area.getMinLat(), area.getMaxLong()));
119 coords.add(new Coord(area.getMaxLat(), area.getMaxLong()));
120 coords.add(new Coord(area.getMaxLat(), area.getMinLong()));
121 coords.add(new Coord(area.getMinLat() + 1, area.getMinLong()));
122114 MapLine boundary = new MapLine();
123115 boundary.setType(type);
124116 if(name != null)
125117 boundary.setName(name);
126118 boundary.setMinResolution(0); // On all levels
127 boundary.setPoints(coords);
119 boundary.setPoints(area.toCoords());
128120 mapper.addLine(boundary);
129121 }
130122
2626 */
2727 public class HGTConverter {
2828 private static final Logger log = Logger.getLogger(HGTConverter.class);
29 protected final static double FACTOR = 45.0d / (1<<29);
29 protected static final double FACTOR = 45.0d / (1<<29);
3030 private short[] noHeights = { HGTReader.UNDEF };
3131 private HGTReader[][] readers;
3232 private final int minLat32;
4646 private int statRdrNull;
4747 private int statRdrRes;
4848
49 private InterpolationMethod interpolationMethod = InterpolationMethod.Bicubic;
49 private InterpolationMethod interpolationMethod = InterpolationMethod.BICUBIC;
5050
5151 public enum InterpolationMethod {
5252 /** faster, smoothing, less precise */
53 Bilinear ,
53 BILINEAR,
5454 /** slower, higher precision */
55 Bicubic,
55 BICUBIC,
5656 /** bicubic for high resolution, else bilinear */
57 Automatic
58 };
57 AUTOMATIC
58 }
5959
6060 /**
6161 * Class to extract elevation information from SRTM files in hgt format.
9999 }
100100 }
101101 res = maxRes; // we use the highest available res
102 return;
103102 }
104103
105104 /**
108107 */
109108 public void setInterpolationMethod(InterpolationMethod interpolationMethod) {
110109 this.interpolationMethod = interpolationMethod;
111 useComplexInterpolation = (interpolationMethod != InterpolationMethod.Bilinear);
110 useComplexInterpolation = (interpolationMethod != InterpolationMethod.BILINEAR);
112111 }
113112
114113 /**
126125 // no reader : ocean or missing file
127126 return outsidePolygonHeight;
128127 }
129 int res = rdr.getRes();
128 int detectedRes = rdr.getRes();
130129 rdr.prepRead();
131 if (res <= 0)
130 if (detectedRes <= 0)
132131 return 0; // assumed to be an area in the ocean
133132 lastRow = row;
134133
135 double scale = res * FACTOR;
134 double scale = detectedRes * FACTOR;
136135
137 double y1 = (lat32 - minLat32) * scale - row * res;
138 double x1 = (lon32 - minLon32) * scale - col * res;
136 double y1 = (lat32 - minLat32) * scale - row * detectedRes;
137 double x1 = (lon32 - minLon32) * scale - col * detectedRes;
139138 int xLeft = (int) x1;
140139 int yBottom = (int) y1;
141140 double qx = x1 - xLeft;
182181 * can use HGTreaders near the current one
183182 */
184183 private boolean fillArray(HGTReader rdr, int row, int col, int xLeft, int yBottom) {
185 int res = rdr.getRes();
184 int detectedRes = rdr.getRes();
186185 int minX = 0;
187186 int minY = 0;
188187 int maxX = 3;
195194 return false;
196195 minX = 1;
197196 inside = false;
198 } else if (xLeft == res - 1) {
197 } else if (xLeft == detectedRes - 1) {
199198 if (col + 1 >= readers[0].length)
200199 return false;
201200 maxX = 2;
206205 return false;
207206 minY = 1;
208207 inside = false;
209 } else if (yBottom == res - 1) {
208 } else if (yBottom == detectedRes - 1) {
210209 if (row + 1 >= readers.length)
211210 return false;
212211 maxY = 2;
228227 return true;
229228
230229 // fill data from adjacent readers, down and up
231 if (xLeft > 0 && xLeft < res - 1) {
230 if (xLeft > 0 && xLeft < detectedRes - 1) {
232231 if (yBottom == 0) { // bottom edge
233 HGTReader rdrBB = prepReader(res, row - 1, col);
232 HGTReader rdrBB = prepReader(detectedRes, row - 1, col);
234233 if (rdrBB == null)
235234 return false;
236235 for (int x = 0; x <= 3; x++) {
237 h = rdrBB.ele(xLeft + x - 1, res - 1);
236 h = rdrBB.ele(xLeft + x - 1, detectedRes - 1);
238237 if (h == HGTReader.UNDEF)
239238 return false;
240239 eleArray[x][0] = h;
241240 }
242 } else if (yBottom == res - 1) { // top edge
243 HGTReader rdrTT = prepReader(res, row + 1, col);
241 } else if (yBottom == detectedRes - 1) { // top edge
242 HGTReader rdrTT = prepReader(detectedRes, row + 1, col);
244243 if (rdrTT == null)
245244 return false;
246245 for (int x = 0; x <= 3; x++) {
253252 }
254253
255254 // fill data from adjacent readers, left and right
256 if (yBottom > 0 && yBottom < res - 1) {
255 if (yBottom > 0 && yBottom < detectedRes - 1) {
257256 if (xLeft == 0) { // left edgge
258 HGTReader rdrLL = prepReader(res, row, col - 1);
257 HGTReader rdrLL = prepReader(detectedRes, row, col - 1);
259258 if (rdrLL == null)
260259 return false;
261260 for (int y = 0; y <= 3; y++) {
262 h = rdrLL.ele(res - 1, yBottom + y - 1);
261 h = rdrLL.ele(detectedRes - 1, yBottom + y - 1);
263262 if (h == HGTReader.UNDEF)
264263 return false;
265264 eleArray[0][y] = h;
266265 }
267 } else if (xLeft == res - 1) { // right edge
268 HGTReader rdrRR = prepReader(res, row, col + 1);
266 } else if (xLeft == detectedRes - 1) { // right edge
267 HGTReader rdrRR = prepReader(detectedRes, row, col + 1);
269268 if (rdrRR == null)
270269 return false;
271270 for (int y = 0; y <= 3; y++) {
280279 // fill data from adjacent readers, corners
281280 if (xLeft == 0) {
282281 if (yBottom == 0) { // left bottom corner
283 HGTReader rdrLL = prepReader(res, row, col - 1);
282 HGTReader rdrLL = prepReader(detectedRes, row, col - 1);
284283 if (rdrLL == null)
285284 return false;
286285 for (int y = 1; y <= 3; y++) {
287 h = rdrLL.ele(res - 1, yBottom + y - 1);
286 h = rdrLL.ele(detectedRes - 1, yBottom + y - 1);
288287 if (h == HGTReader.UNDEF)
289288 return false;
290289 eleArray[0][y] = h;
291290 }
292291
293 HGTReader rdrBB = prepReader(res, row - 1, col);
292 HGTReader rdrBB = prepReader(detectedRes, row - 1, col);
294293 if (rdrBB == null)
295294 return false;
296295 for (int x = 1; x <= 3; x++) {
297 h = rdrBB.ele(xLeft + x - 1, res - 1);
296 h = rdrBB.ele(xLeft + x - 1, detectedRes - 1);
298297 if (h == HGTReader.UNDEF)
299298 return false;
300299 eleArray[x][0] = h;
301300 }
302301
303 HGTReader rdrLB = prepReader(res, row - 1, col - 1);
302 HGTReader rdrLB = prepReader(detectedRes, row - 1, col - 1);
304303 if (rdrLB == null)
305304 return false;
306 h = rdrLB.ele(res - 1, res - 1);
305 h = rdrLB.ele(detectedRes - 1, detectedRes - 1);
307306 if (h == HGTReader.UNDEF)
308307 return false;
309308 eleArray[0][0] = h;
310 } else if (yBottom == res - 1) { // left top corner
311 HGTReader rdrLL = prepReader(res, row, col - 1);
309 } else if (yBottom == detectedRes - 1) { // left top corner
310 HGTReader rdrLL = prepReader(detectedRes, row, col - 1);
312311 if (rdrLL == null)
313312 return false;
314313 for (int y = 0; y <= 2; y++) {
315 h = rdrLL.ele(res - 1, yBottom + y - 1);
314 h = rdrLL.ele(detectedRes - 1, yBottom + y - 1);
316315 if (h == HGTReader.UNDEF)
317316 return false;
318317 eleArray[0][y] = h;
319318 }
320319
321 HGTReader rdrTT = prepReader(res, row + 1, col);
320 HGTReader rdrTT = prepReader(detectedRes, row + 1, col);
322321 if (rdrTT == null)
323322 return false;
324323 for (int x = 1; x <= 3; x++) {
328327 eleArray[x][3] = h;
329328 }
330329
331 HGTReader rdrLT = prepReader(res, row + 1, col - 1);
330 HGTReader rdrLT = prepReader(detectedRes, row + 1, col - 1);
332331 if (rdrLT == null)
333332 return false;
334 h = rdrLT.ele(res - 1, 1);
333 h = rdrLT.ele(detectedRes - 1, 1);
335334 if (h == HGTReader.UNDEF)
336335 return false;
337336 eleArray[0][3] = h;
338337 }
339 } else if (xLeft == res - 1) {
338 } else if (xLeft == detectedRes - 1) {
340339 if (yBottom == 0) { // right bottom corner
341 HGTReader rdrRR = prepReader(res, row, col + 1);
340 HGTReader rdrRR = prepReader(detectedRes, row, col + 1);
342341 if (rdrRR == null)
343342 return false;
344343 for (int y = 1; y <= 3; y++) {
348347 eleArray[3][y] = h;
349348 }
350349
351 HGTReader rdrBB = prepReader(res, row - 1, col);
350 HGTReader rdrBB = prepReader(detectedRes, row - 1, col);
352351 if (rdrBB == null)
353352 return false;
354353 for (int x = 0; x <= 2; x++) {
355 h = rdrBB.ele(xLeft + x - 1, res - 1);
354 h = rdrBB.ele(xLeft + x - 1, detectedRes - 1);
356355 if (h == HGTReader.UNDEF)
357356 return false;
358357 eleArray[x][0] = h;
359358 }
360359
361 HGTReader rdrRB = prepReader(res, row - 1, col + 1);
360 HGTReader rdrRB = prepReader(detectedRes, row - 1, col + 1);
362361 if (rdrRB == null)
363362 return false;
364 h = rdrRB.ele(1, res - 1);
363 h = rdrRB.ele(1, detectedRes - 1);
365364 if (h == HGTReader.UNDEF)
366365 return false;
367366 eleArray[3][0] = h;
368 } else if (yBottom == res - 1) { // right top corner
369 HGTReader rdrRR = prepReader(res, row, col + 1);
367 } else if (yBottom == detectedRes - 1) { // right top corner
368 HGTReader rdrRR = prepReader(detectedRes, row, col + 1);
370369 if (rdrRR == null)
371370 return false;
372371 for (int y = 0; y <= 2; y++) {
376375 eleArray[3][y] = h;
377376 }
378377
379 HGTReader rdrTT = prepReader(res, row + 1, col);
378 HGTReader rdrTT = prepReader(detectedRes, row + 1, col);
380379 if (rdrTT == null)
381380 return false;
382381 for (int x = 0; x <= 2; x++) {
386385 eleArray[x][3] = h;
387386 }
388387
389 HGTReader rdrRT = prepReader(res, row + 1, col + 1);
388 HGTReader rdrRT = prepReader(detectedRes, row + 1, col + 1);
390389 if (rdrRT == null)
391390 return false;
392391 h = rdrRT.ele(1, 1);
467466 return (short) Math.round((1.0D - qx) * hlt + qx * hrt);
468467 if (hrt != HGTReader.UNDEF && hrb != HGTReader.UNDEF && qx > 0.5D) //right edge
469468 return (short) Math.round((1.0D - qy) * hrb + qy * hrt);
470 //if (hlt != HGTReader.UNDEF && hrb != HGTReader.UNDEF && qx + qy > 0.5D && gx + qy < 1.5D) //diagonal
471469 // nearest value
472470 return (short)((qx < 0.5D)? ((qy < 0.5D)? hlb: hlt): ((qy < 0.5D)? hrb: hrt));
473471 }
480478 return (short) Math.round((1.0D - qx) * hlb + qx * hrb);
481479 if (hlb != HGTReader.UNDEF && hlt != HGTReader.UNDEF && qx < 0.5D) //left edge
482480 return (short) Math.round((1.0D - qy) * hlb + qy * hlt);
483 //if (hlt != HGTReader.UNDEF && hrb != HGTReader.UNDEF && qx + qy > 0.5D && gx + qy < 1.5D) //diagonal
484481 // nearest value
485482 return (short) ((qx < 0.5D) ? ((qy < 0.5D) ? hlb : hlt) : ((qy < 0.5D) ? hrb : hrt));
486483 }
493490 return (short) Math.round((1.0D - qx) * hlt + qx * hrt);
494491 if (hlt != HGTReader.UNDEF && hlb != HGTReader.UNDEF && qx < 0.5D) //left edge
495492 return (short) Math.round((1.0D - qy) * hlb + qy * hlt);
496 //if (hlb != HGTReader.UNDEF && hrt != HGTReader.UNDEF && qy > qx - 0.5D && qy < qx + 0.5D) //diagonal
497493 // nearest value
498494 return (short) ((qx < 0.5D) ? ((qy < 0.5D) ? hlb : hlt) : ((qy < 0.5D) ? hrb : hrt));
499495 }
506502 return (short) Math.round((1.0D - qx) * hlb + qx * hrb);
507503 if (hrb != HGTReader.UNDEF && hrt != HGTReader.UNDEF && qx > 0.5D) //right edge
508504 return (short) Math.round((1.0D - qy) * hrb + qy * hrt);
509 //if (hlb != HGTReader.UNDEF && hrt != HGTReader.UNDEF && qy > qx - 0.5D && qy < qx + 0.5D) //diagonal
510505 // nearest value
511506 return (short) ((qx < 0.5D) ? ((qy < 0.5D) ? hlb : hlt) : ((qy < 0.5D) ? hrb : hrt));
512507 }
520515 double hxb = (1.0D - qx)*hlb + qx*hrb;
521516 return (short) Math.round((1.0D - qy) * hxb + qy * hxt);
522517 }
523
524 // public void stats() {
525 // for (int i = 0; i < readers.length; i++) {
526 // for (int j = 0; j < readers[i].length; j++) {
527 // System.out.println(readers[i][j]);
528 // }
529 //
530 // }
531 // }
532518
533519 public int getHighestRes() {
534520 return res;
567553 clearStat();
568554 pointsDistanceLat = pointDist;
569555 pointsDistanceLon = pointDist;
570 if (InterpolationMethod.Automatic.equals(interpolationMethod)) {
556 if (InterpolationMethod.AUTOMATIC.equals(interpolationMethod)) {
571557 if (res > 0) {
572558 int distHGTx3 = (1 << 29)/(45 / 3 * res); // 3 * HGT points distance in DEM units
573559 if (distHGTx3 + 20 > pointDist) { // account for rounding of pointDist and distHGTx3
602588 * a an array with one value for each point, in the order top -> down and left -> right.
603589 */
604590 public short[] getHeights(int lat32, int lon32, int height, int width) {
605 short[] realHeights = noHeights;
606
607591 java.awt.geom.Area testArea = null;
608592 if (demArea != null) {
609593 // we have a bounding polygon
620604 }
621605 }
622606
623 realHeights = new short[width * height];
607 short[] realHeights = new short[width * height];
624608 int count = 0;
625609 int py = lat32;
626610 for (int y = 0; y < height; y++) {
1212 package uk.me.parabola.mkgmap.reader.hgt;
1313
1414 import java.io.BufferedReader;
15 import java.io.FileNotFoundException;
1615 import java.io.FileReader;
1716 import java.io.IOException;
1817 import java.io.InputStream;
2019 import java.util.BitSet;
2120 import java.util.regex.Matcher;
2221 import java.util.regex.Pattern;
22
2323 import uk.me.parabola.log.Logger;
2424
2525 /**
2929 public class HGTList {
3030 private static final Logger log = Logger.getLogger(HGTList.class);
3131
32 private final static HGTList instance = new HGTList();
33 private static BitSet knownHgt;
32 private static final HGTList instance = new HGTList();
33 private BitSet knownHgt;
3434
35 private HGTList()
36 {
35 private HGTList() {
3736 try {
3837 knownHgt = loadConfig();
3938 } catch (IOException e) {
4443 public static HGTList get() {
4544 return instance;
4645 }
46
4747 public BitSet getKnownHGT() {
4848 return knownHgt;
4949 }
5050
51 private BitSet loadConfig() throws IOException
52 {
51 private BitSet loadConfig() throws IOException {
5352 try {
5453 String name = "hgt/known-hgt.txt";
5554 BitSet bs = compileHGTList(name);
8584 * @param filename the path to the human readable file
8685 * @return the {@link BitSet}
8786 * @throws IOException
88 * @throws FileNotFoundException
8987 */
90 private static BitSet compileHGTList(String filename) throws FileNotFoundException, IOException {
88 private static BitSet compileHGTList(String filename) throws IOException {
9189 final Pattern hgtPattern = Pattern.compile("([sSnN])(\\d{2})([eEwW])(\\d{3}).*");
9290 try (BufferedReader br = new BufferedReader(new FileReader(filename))) {
9391 BitSet bs = new BitSet(180*360);
120118 }
121119 }
122120
123 private static int getBitSetPos (int lat, int lon) {
121 private static int getBitSetPos(int lat, int lon) {
124122 assert lat >= -90 && lat < 90 && lon >= -180 && lon < 180;
125123 return (90 + lat) * 360 + lon + 180;
126124 }
127
125
128126 public synchronized boolean shouldExist(int lat, int lon) {
129127 if (knownHgt != null)
130128 return knownHgt.get(getBitSetPos(lat, lon));
131129 return false;
132130 }
133
131
134132 public static void main(String[] args) throws IOException {
135133 if (args.length >= 2 && "compile".equals(args[0])) {
136134 BitSet bs = compileHGTList(args[1]);
137135 String outName = (args.length > 2) ? args[2] : "known-hgt.bin";
138
139 RandomAccessFile raf = new RandomAccessFile(outName, "rw");
140 raf.write(bs.toByteArray());
141 raf.close();
136
137 try (RandomAccessFile raf = new RandomAccessFile(outName, "rw")) {
138 raf.write(bs.toByteArray());
139 }
142140 } else {
143141 System.out.println("usage: HGTList compile hgt-list [outfile name]");
144 }
145
142 }
146143 }
147144
148145 }
4040 public class HGTReader {
4141 private static final Logger log = Logger.getLogger(HGTReader.class);
4242
43 public static final short UNDEF = Short.MIN_VALUE;
4344 private ByteBuffer buffer;
4445 private int res;
45 public final static short UNDEF = Short.MIN_VALUE;
46 public final String fileName;
47 public String path;
48 public boolean read;
46 private final String fileName;
47 private String path;
48 private boolean read;
4949 private long count;
5050
5151
52 private final static Map<String,Set<String>> missingMap = new HashMap<>();
53 private final static Set<String> badDir = new HashSet<>();
52 private static final Map<String,Set<String>> missingMap = new HashMap<>();
53 private static final Set<String> badDir = new HashSet<>();
5454
5555 /**
5656 * Class to read a single HGT file.
122122 res = -1;
123123 path = null;
124124 synchronized (missingMap){
125 Set<String> missingSet = missingMap.get(dirsWithHGT);
126 if (missingSet == null) {
127 missingSet = new HashSet<>();
128 missingMap.put(dirsWithHGT, missingSet);
129 }
130 missingSet.add(fileName);
125 missingMap.computeIfAbsent(dirsWithHGT, k-> new HashSet<>()).add(fileName);
131126 }
132127 HGTList hgtList = HGTList.get();
133128 if (hgtList != null) {
134129 if (hgtList.shouldExist(lat, lon))
135130 System.err.println(this.getClass().getSimpleName() + ": file " + fileName + " not found but it should exist. Height values will be 0.");
136 return;
137131 } else {
138132 log.warn("file " + fileName + " not found. Is expected to cover sea.");
139133 }
216210 * @return resolution (typically 1200 for 3'' or 3600 for 1'')
217211 */
218212 private int calcRes(long size, String fname) {
219 long numVals = (long) Math.sqrt(size/2);
220 if (2 * numVals*numVals == size)
213 long numVals = (long) Math.sqrt(size / 2d);
214 if (2 * numVals * numVals == size)
221215 return (int) (numVals - 1);
222216 log.error("file", fname, "has unexpected size", size, "and is ignored");
223217 return -1;
2424 import java.util.Set;
2525 import java.util.concurrent.atomic.AtomicBoolean;
2626
27 import uk.me.parabola.imgfmt.FormatException;
2827 import uk.me.parabola.imgfmt.app.Area;
2928 import uk.me.parabola.imgfmt.app.Coord;
3029 import uk.me.parabola.log.Logger;
3231
3332 public final class CoastlineFileLoader {
3433
35 private static final Logger log = Logger
36 .getLogger(CoastlineFileLoader.class);
34 private static final Logger log = Logger.getLogger(CoastlineFileLoader.class);
3735
3836 private final Set<String> coastlineFiles;
39 private final Collection<CoastlineWay> coastlines = new ArrayList<CoastlineWay>();
37 private final Collection<CoastlineWay> coastlines = new ArrayList<>();
4038
4139 private final AtomicBoolean coastlinesLoaded = new AtomicBoolean(false);
4240 private final AtomicBoolean loadingStarted = new AtomicBoolean(false);
4442 private final EnhancedProperties coastConfig;
4543
4644 private CoastlineFileLoader() {
47 this.coastlineFiles = new HashSet<String>();
45 this.coastlineFiles = new HashSet<>();
4846 this.coastConfig = new EnhancedProperties();
4947 }
5048
6866 }
6967 }
7068
71 private OsmMapDataSource loadFromFile(String name)
72 throws FileNotFoundException, FormatException {
69 private OsmMapDataSource loadFromFile(String name) throws FileNotFoundException {
7370 OsmMapDataSource src = new OsmCoastDataSource();
7471 src.config(getConfig());
7572 log.info("Started loading coastlines from", name);
7875 return src;
7976 }
8077
81 private Collection<Way> loadFile(String filename)
82 throws FileNotFoundException {
78 private Collection<Way> loadFile(String filename) throws FileNotFoundException {
8379 OsmMapDataSource src = loadFromFile(filename);
8480 return src.getElementSaver().getWays().values();
8581 }
9591 int nBefore = coastlines.size();
9692
9793 Collection<Way> loadedCoastlines = loadFile(coastlineFile);
98 log.info(loadedCoastlines.size(), "coastline ways from",
99 coastlineFile, "loaded.");
94 log.info(loadedCoastlines.size(), "coastline ways from", coastlineFile, "loaded.");
10095
10196 ArrayList<Way> ways = SeaGenerator.joinWays(loadedCoastlines);
10297 ListIterator<Way> wayIter = ways.listIterator();
10499 while (wayIter.hasNext()) {
105100 Way way = wayIter.next();
106101 wayIter.remove();
107 coastlines.add(new CoastlineWay(way.getId(), way
108 .getPoints()));
102 coastlines.add(new CoastlineWay(way.getId(), way.getPoints()));
109103 }
110104
111 log.info((coastlines.size() - nBefore),
112 "coastlines loaded from", coastlineFile);
105 log.info((coastlines.size() - nBefore), "coastlines loaded from", coastlineFile);
113106 } catch (FileNotFoundException exp) {
114107 log.error("Coastline file " + coastlineFile + " not found.");
115108 } catch (Exception exp) {
121114 }
122115
123116 public Collection<Way> getCoastlines(Area bbox) {
124 if (coastlinesLoaded.get() == false) {
117 if (!coastlinesLoaded.get()) {
125118 synchronized (this) {
126119 loadCoastlines();
127120 }
128121 }
129 Collection<Way> ways = new ArrayList<Way>();
122 Collection<Way> ways = new ArrayList<>();
130123 for (CoastlineWay w : coastlines) {
131124 if (w.getBbox().intersects(bbox)) {
132125 Way x = new Way(w.getOriginalId(), w.getPoints());
144137 public CoastlineWay(long id, List<Coord> points) {
145138 super(id, points);
146139 if (points.isEmpty()) {
147 throw new IllegalArgumentException(
148 "No support for empty ways. WayId: " + id);
140 throw new IllegalArgumentException("No support for empty ways. WayId: " + id);
149141 }
150142
151143 if (log.isDebugEnabled())
152 log.debug("Create coastline way", id, "with", points.size(),
153 "points");
144 log.debug("Create coastline way", id, "with", points.size(), "points");
154145 bbox = Area.getBBox(points);
155146 }
156147
169160 }
170161
171162 @Override
172 public Map<String, String> getTagsWithPrefix(String prefix,
173 boolean removePrefix) {
163 public Map<String, String> getTagsWithPrefix(String prefix, boolean removePrefix) {
174164 if ("natural".startsWith(prefix)) {
175165 if (removePrefix) {
176 return Collections.singletonMap(
177 "natural".substring(prefix.length()), "coastline");
166 return Collections.singletonMap("natural".substring(prefix.length()), "coastline");
178167 } else {
179168 return Collections.singletonMap("natural", "coastline");
180169 }
1515 package uk.me.parabola.mkgmap.reader.osm;
1616
1717 import java.util.Collections;
18 import java.util.Iterator;
1918 import java.util.Map;
2019
2120 import uk.me.parabola.imgfmt.app.Label;
5150 * @param val Its value.
5251 */
5352 public void addTagFromRawOSM(String key, String val) {
54 if (val != null){
55 val = val.trim();
56 if (val.isEmpty() == false){
57 // remove duplicated spaces within value
58 String squashed = Label.squashSpaces(val);
59 if (val.equals(squashed) == false) {
60 if (log.isInfoEnabled())
61 log.info(this.toBrowseURL(),"obsolete blanks removed from tag", key, " '" + val + "' -> '" + squashed + "'");
62 val = squashed;
63 }
64 squashed = Label.squashDel(val);
65 if (val.equals(squashed) == false) {
66 if (log.isInfoEnabled())
67 log.info(this.toBrowseURL(),"DEL character (0x7f) removed from tag", key, " '" + val + "' -> '" + squashed + "'");
68 val = squashed;
69 }
53 if (val == null)
54 return;
55 val = val.trim();
56 if (!val.isEmpty()){
57 // remove duplicated spaces within value
58 String squashed = Label.squashSpaces(val);
59 if (!val.equals(squashed)) {
60 if (log.isInfoEnabled())
61 log.info(this.toBrowseURL(),"obsolete blanks removed from tag", key, " '" + val + "' -> '" + squashed + "'");
62 val = squashed;
7063 }
71 addTag(key, val.intern());
64 squashed = Label.squashDel(val);
65 if (!val.equals(squashed)) {
66 if (log.isInfoEnabled())
67 log.info(this.toBrowseURL(),"DEL character (0x7f) removed from tag", key, " '" + val + "' -> '" + squashed + "'");
68 val = squashed;
69 }
7270 }
71 addTag(key, val.intern());
7372 }
7473
7574 /**
161160 * @return <code>true</code> if the tag value is a boolean tag with a "positive" value
162161 */
163162 public boolean tagIsLikeYes(short tagKey) {
164 String val = getTag(tagKey);
165 if (val == null)
166 return false;
167
168 if (val.equals("yes") || val.equals("true") || val.equals("1"))
169 return true;
170
171 return false;
163 final String val = getTag(tagKey);
164 return val != null && ("yes".equals(val) || "true".equals(val) || "1".equals(val));
172165 }
173166
174167 /**
198191 * @return <code>true</code> if the tag value is a boolean tag with a "negative" value
199192 */
200193 public boolean tagIsLikeNo(short tagKey) {
201 String val = getTag(tagKey);
202 if (val == null)
203 return false;
204
205 if (val.equals("no") || val.equals("false") || val.equals("0"))
206 return true;
207
208 return false;
194 final String val = getTag(tagKey);
195 return val != null && ("no".equals(val) || "false".equals(val) || "0".equals(val));
209196 }
210197
211198 public long getId() {
272259 }
273260
274261 /**
262 * @return a Map iterator for the key + value pairs
263 */
264 public Iterable<Map.Entry<String, String>> getTagEntryIterator() {
265 return () -> tags == null ? Collections.emptyIterator() : tags.entryIterator();
266 }
267
268 /**
275269 * @return a Map iterator for the key + value pairs
276270 */
277
278 public Iterable<Map.Entry<String, String>> getTagEntryIterator() {
279 return new Iterable<Map.Entry<String, String>>() {
280 public Iterator<Map.Entry<String, String>> iterator() {
281 if (tags == null)
282 return Collections.emptyIterator();
283 return tags.entryIterator();
284 }
285 };
286 }
287
288 /**
289 * @return a Map iterator for the key + value pairs
290 */
291271 public Iterable<Map.Entry<Short, String>> getFastTagEntryIterator() {
292 return new Iterable<Map.Entry<Short, String>>() {
293 public Iterator<Map.Entry<Short, String>> iterator() {
294 if (tags == null)
295 return Collections.emptyIterator();
296 return tags.entryShortIterator();
297 }
298 };
272 return () -> tags == null ? Collections.emptyIterator() : tags.entryShortIterator();
299273 }
300274
301275 protected String kind() {
1515 import java.util.concurrent.atomic.AtomicLong;
1616
1717 public class FakeIdGenerator {
18
18
19 private FakeIdGenerator () {
20 // private constructor to hide the implicit public one
21 }
1922 private static final long START_ID = 1L << 62;
2023
2124 private static final AtomicLong fakeId = new AtomicLong(START_ID);
3841 * @return a unique id
3942 */
4043 public static long makeFakeId() {
41 long id = fakeId.incrementAndGet();
42 // if (4611686018427394038L == id){
43 // long dd = 4;
44 // }
45 return id;
44 return fakeId.incrementAndGet();
4645 }
4746
4847 public static boolean isFakeId(long id) {
2020 import uk.me.parabola.imgfmt.ExitException;
2121 import uk.me.parabola.log.Logger;
2222 import uk.me.parabola.mkgmap.general.LevelInfo;
23 import uk.me.parabola.mkgmap.general.MapPoint;
2324
2425 /**
2526 * Holds the garmin type of an element and all the information that
5657 private boolean propogateActionsOnContinue;
5758
5859 public static boolean checkType(FeatureKind featureKind, int type) {
59 if (type >= 0x010000){
60 if (type >= 0x010000) {
6061 if ((type & 0xff) > 0x1f)
6162 return false;
6263 } else {
63 if (featureKind == FeatureKind.POLYLINE && type > 0x3f)
64 if (featureKind == FeatureKind.POLYLINE && type > 0x3f
65 || (featureKind == FeatureKind.POLYGON && (type > 0x7f || type == 0x4a))) {
6466 return false;
65 else if (featureKind == FeatureKind.POLYGON && (type> 0x7f || type == 0x4a))
66 return false;
67 else if (featureKind == FeatureKind.POINT){
68 if (type < 0x0100 || (type & 0x00ff) > 0x1f)
67 } else if (featureKind == FeatureKind.POINT) {
68 if (type < 0x0100)
6969 return false;
70 int subtype = type & 0xff;
71 if (subtype > 0x1f || MapPoint.isCityType(type) && subtype != 0) {
72 return false;
73 }
7074 }
7175 }
7276 return true;
7680 this.featureKind = featureKind;
7781 try {
7882 int t = Integer.decode(type);
79 if (featureKind == FeatureKind.POLYGON){
83 if (featureKind == FeatureKind.POLYGON && t >= 0x100 && t < 0x10000 && (t & 0xff) == 0) {
8084 // allow 0xYY00 instead of 0xYY
81 if (t >= 0x100 && t < 0x10000 && (t & 0xff) == 0)
82 t >>= 8;
85 t >>= 8;
8386 }
8487 this.type = t;
8588 } catch (NumberFormatException e) {
156159 fmt.format(" road_class=%d road_speed=%d", roadClass, roadSpeed);
157160
158161 if (continueSearch)
159 fmt.format(" continue");
162 sb.append(" continue");
160163 if (propogateActionsOnContinue)
161 fmt.format(" propagate");
164 sb.append(" propagate");
162165 sb.append(']');
163166 String res = sb.toString();
164167 fmt.close();
2929 *
3030 * Some of this would be much better done in a style file or by extending the style system.
3131 */
32 public class HighwayHooks extends OsmReadingHooksAdaptor {
32 public class HighwayHooks implements OsmReadingHooks {
3333 private static final Logger log = Logger.getLogger(HighwayHooks.class);
3434
3535 private final List<Way> motorways = new ArrayList<>();
4141
4242 private Node currentNodeInWay;
4343
44 @Override
4445 public boolean init(ElementSaver saver, EnhancedProperties props) {
4546 this.saver = saver;
4647 if(props.getProperty("make-all-cycleways", false)) {
7374 return usedTags;
7475 }
7576
77 @Override
7678 public void onAddNode(Node node) {
7779 String val = node.getTag("highway");
7880 if (val != null && (val.equals("motorway_junction") || val.equals("services"))) {
8183 }
8284 }
8385
86 @Override
8487 public void onCoordAddedToWay(Way way, long id, Coord co) {
8588 if (!linkPOIsToWays)
8689 return;
130133 }
131134 }
132135
136 @Override
133137 public void onAddWay(Way way) {
134138 String highway = way.getTag("highway");
135139 if (highway != null || "ferry".equals(way.getTag("route"))) {
136140 // if the way is a roundabout but isn't already
137141 // flagged as "oneway", flag it here
138 if ("roundabout".equals(way.getTag("junction"))) {
139 if (way.getTag("oneway") == null) {
140 way.addTag("oneway", "yes");
141 }
142 if ("roundabout".equals(way.getTag("junction")) && way.getTag("oneway") == null) {
143 way.addTag("oneway", "yes");
142144 }
143145
144146 if (makeOppositeCycleways && !"cycleway".equals(highway)){
145147 String onewayTag = way.getTag("oneway");
146148 boolean oneway = way.tagIsLikeYes("oneway");
147 if (!oneway & onewayTag != null && ("-1".equals(onewayTag) || "reverse".equals(onewayTag)))
149 if (!oneway && onewayTag != null && ("-1".equals(onewayTag) || "reverse".equals(onewayTag)))
148150 oneway = true;
149151 if (oneway){
150152 String cycleway = way.getTag("cycleway");
151 boolean addCycleWay = false;
152153 // we have a oneway street, check if it allows bicycles to travel in opposite direction
153 if ("no".equals(way.getTag("oneway:bicycle")) || "no".equals(way.getTag("bicycle:oneway"))){
154 addCycleWay = true;
154 if ("no".equals(way.getTag("oneway:bicycle"))
155 || "no".equals(way.getTag("bicycle:oneway"))
156 || "opposite".equals(cycleway) || "opposite_lane".equals(cycleway)
157 || "opposite_track".equals(cycleway)
158 || "opposite_lane".equals(way.getTag("cycleway:left"))
159 || "opposite_lane".equals(way.getTag("cycleway:right"))
160 || "opposite_track".equals(way.getTag("cycleway:left"))
161 || "opposite_track".equals(way.getTag("cycleway:right"))) {
162 way.addTag("mkgmap:make-cycle-way", "yes");
155163 }
156 else if (cycleway != null && ("opposite".equals(cycleway) || "opposite_lane".equals(cycleway) || "opposite_track".equals(cycleway))){
157 addCycleWay = true;
158 }
159 else if ("opposite_lane".equals(way.getTag("cycleway:left")) || "opposite_lane".equals(way.getTag("cycleway:right"))){
160 addCycleWay = true;
161 }
162 else if ("opposite_track".equals(way.getTag("cycleway:left")) || "opposite_track".equals(way.getTag("cycleway:right"))){
163 addCycleWay = true;
164 }
165 if (addCycleWay)
166 way.addTag("mkgmap:make-cycle-way", "yes");
167164 }
168165 }
169166 }
172169 motorways.add(way);
173170 }
174171
172 @Override
175173 public void end() {
176174 finishExits();
177175 exits.clear();
2626 * @author GerdP
2727 *
2828 */
29 public class HousenumberHooks extends OsmReadingHooksAdaptor {
29 public class HousenumberHooks implements OsmReadingHooks {
3030 private static final Logger log = Logger.getLogger(HousenumberHooks.class);
3131
3232 private ElementSaver saver;
33 private Node currentNodeInWay;
3433 private final List<Node> nodes = new ArrayList<>();
3534 private boolean clearNodes;
3635
4241 @Override
4342 public boolean init(ElementSaver saver, EnhancedProperties props) {
4443 this.saver = saver;
45 if (props.getProperty("addr-interpolation", true) == false)
44 if (!props.getProperty("addr-interpolation", true))
4645 return false;
4746 return (props.getProperty("housenumbers", false));
4847 }
6362 nodes.clear();
6463 clearNodes = false;
6564 }
66 currentNodeInWay = saver.getNode(id);
67 if (currentNodeInWay == null)
68 return;
69 if (currentNodeInWay.getTag(addrHousenumberTagKey) == null)
65 Node currentNodeInWay = saver.getNode(id);
66 if (currentNodeInWay == null || currentNodeInWay.getTag(addrHousenumberTagKey) == null)
7067 return;
7168 // this node might be part of a way that has the addr:interpolation tag
7269 nodes.add(currentNodeInWay);
4040 * destination.
4141 * @author WanMil
4242 */
43 public class LinkDestinationHook extends OsmReadingHooksAdaptor {
43 public class LinkDestinationHook implements OsmReadingHooks {
4444 private static final Logger log = Logger.getLogger(LinkDestinationHook.class);
4545
4646 private ElementSaver saver;
4747
4848 /** Maps which ways can be driven from a given Coord */
49 private IdentityHashMap<Coord, Set<Way>> adjacentWays = new IdentityHashMap<Coord, Set<Way>>();
49 private IdentityHashMap<Coord, Set<Way>> adjacentWays = new IdentityHashMap<>();
5050 /** Contains all _link ways that have to be processed */
51 private Map<Long, Way> destinationLinkWays = new LinkedHashMap<Long, Way>();
52
53 private final static Set<String> highwayTypes = new LinkedHashSet<String>(Arrays.asList(
51 private Map<Long, Way> destinationLinkWays = new LinkedHashMap<>();
52
53 private static final Set<String> highwayTypes = new LinkedHashSet<>(Arrays.asList(
5454 "motorway", "trunk", "primary", "secondary", "tertiary",
5555 "motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link"));
56 private HashSet<String> linkTypes = new HashSet<String>(Arrays.asList(
56 private HashSet<String> linkTypes = new HashSet<>(Arrays.asList(
5757 "motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link"));
5858
5959
6363 private NameFinder nameFinder;
6464
6565 /** Maps which nodes contains to which ways */
66 private IdentityHashMap<Coord, Set<Way>> wayNodes = new IdentityHashMap<Coord, Set<Way>>();
66 private IdentityHashMap<Coord, Set<Way>> wayNodes = new IdentityHashMap<>();
6767
6868 private boolean processDestinations;
6969 private boolean processExits;
7070
71 @Override
7172 public boolean init(ElementSaver saver, EnhancedProperties props) {
7273 this.saver = saver;
7374 nameFinder = new NameFinder(props);
115116 for (Coord c : points) {
116117 Set<Way> ways = adjacentWays.get(c);
117118 if (ways == null) {
118 ways = new HashSet<Way>(4);
119 ways = new HashSet<>(4);
119120 adjacentWays.put(c, ways);
120121 }
121122 ways.add(w);
136137 destLanesTag = directedDestinationLanes;
137138 destSourceTagKey += ":" + directionSuffix;
138139 }
139 if (destLanesTag != null && destLanesTag.contains("|") == false) {
140 if (destLanesTag != null && !destLanesTag.contains("|")) {
140141 // the destination:lanes tag contains no | => no lane specific information
141142 // use this tag as destination tag
142143 destinationTag = destLanesTag;
153154 }
154155
155156 }
156 if (destinationTag != null){
157 if (destinationTag != null) {
157158 w.addTag("mkgmap:dest_hint_work", destinationTag);
158 if ("destination".equals(destSourceTagKey) == false){
159 if (log.isDebugEnabled()){
160 if (destSourceTagKey.startsWith("destination:lanes"))
161 log.debug("Use",destSourceTagKey,"as destination tag because there is one lane information only. Way ",w.getId(),w.toTagString());
162 else
163 log.debug("Use",destSourceTagKey,"as destination tag. Way ",w.getId(),w.toTagString());
159 if (!"destination".equals(destSourceTagKey) && log.isDebugEnabled()) {
160 if (destSourceTagKey.startsWith("destination:lanes")) {
161 log.debug("Use", destSourceTagKey,
162 "as destination tag because there is one lane information only. Way ",
163 w.getId(), w.toTagString());
164 } else {
165 log.debug("Use", destSourceTagKey, "as destination tag. Way ", w.getId(),
166 w.toTagString());
164167 }
165168 }
166169 destinationLinkWays.put(w.getId(), w);
189192 for (Coord c : w.getPoints()) {
190193 Set<Way> ways = wayNodes.get(c);
191194 if (ways == null) {
192 ways = new HashSet<Way>(4);
195 ways = new HashSet<>(4);
193196 wayNodes.put(c, ways);
194197 }
195198 ways.add(w);
360363 }
361364
362365 /**
366 * TODO: move to Coord class?
363367 * Retrieve a list of all Coords that are the direct neighbours of
364368 * the given Coord. A neighbours latitude and longitude does not differ
365369 * more than one Garmin unit from the given Coord.
367371 * @return all neighbours of c
368372 */
369373 private List<Coord> getDirectNeighbours(Coord c) {
370 List<Coord> neighbours = new ArrayList<Coord>(8);
371 for (int dLat = -1; dLat<2; dLat++) {
374 List<Coord> neighbours = new ArrayList<>(8);
375 for (int dLat = -1; dLat < 2; dLat++) {
372376 for (int dLon = -1; dLon < 2; dLon++) {
373 if (dLat == 0 && dLon == 0) {
374 continue;
375 }
376 neighbours.add(new Coord(c.getLatitude()+dLat, c.getLongitude()+dLon));//TODO: move to Coord class?
377 if (dLat != 0 || dLon != 0) {
378 neighbours.add(new Coord(c.getLatitude() + dLat, c.getLongitude() + dLon));
379 }
377380 }
378381 }
379382 return neighbours;
387390 * motorway exit
388391 */
389392 private boolean isTaggedAsExit(Node node) {
390 if ("motorway_junction".equals(node.getTag("highway")) == false) {
391 return false;
392 }
393 return node.getTag("ref") != null ||
394 (nameFinder.getName(node) != null) ||
395 node.getTag("exit_to") != null;
393 return "motorway_junction".equals(node.getTag("highway"))
394 && (node.getTag("ref") != null || (nameFinder.getName(node) != null) || node.getTag("exit_to") != null);
396395 }
397396
398397 /**
403402 * @return a list of all coords an the connection ways
404403 */
405404 private List<Entry<Coord, Way>> getNextNodes(Coord node, boolean drivingDirection) {
406 List<Entry<Coord, Way>> nextNodes = new ArrayList<Entry<Coord, Way>>();
405 List<Entry<Coord, Way>> nextNodes = new ArrayList<>();
407406
408407 Set<Way> connectedWays = wayNodes.get(node);
409408 for (Way w : connectedWays) {
452451 private void processWays() {
453452 // remove the adjacent links from the destinationLinkWays list
454453 // to avoid duplicate dest_hints
455 Queue<Way> linksWithDestination = new ArrayDeque<Way>();
454 Queue<Way> linksWithDestination = new ArrayDeque<>();
456455 linksWithDestination.addAll(destinationLinkWays.values());
457456 log.debug(destinationLinkWays.size(),"links with destination tag");
458 while (linksWithDestination.isEmpty()== false) {
457 while (!linksWithDestination.isEmpty()) {
459458 Way linkWay = linksWithDestination.poll();
460459 String destination = linkWay.getTag("mkgmap:dest_hint_work");
461460 if (log.isDebugEnabled())
477476
478477 // remove the way from destination handling only if both ways are connected with start/end points
479478 // otherwise it is a crossroads and therefore both ways need to be handled
480 boolean startEndConnection = connectedWay.getPoints().isEmpty()==false && connectedWay.getFirstPoint().equals(c);
481 if (startEndConnection && connectedWay.equals(linkWay) == false
479 boolean startEndConnection = c.equals(connectedWay.getFirstPoint());
480 if (startEndConnection && !connectedWay.equals(linkWay)
482481 && connectedWay.getTag("highway").endsWith("_link")
483482 && destination.equals(nextDest)) {
484483 // do not use this way because there is another link before that with the same destination
605604 }
606605 if (exitNode.getTag("ref") != null)
607606 hintWay.addTag("mkgmap:exit_hint_ref", exitNode.getTag("ref"));
608 if (countMatches == 1){
609 if (exitNode.getTag("exit_to") != null){
610 hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
611 }
607 if (countMatches == 1 && exitNode.getTag("exit_to") != null) {
608 hintWay.addTag("mkgmap:exit_hint_exit_to", exitNode.getTag("exit_to"));
612609 }
613610 if (nameFinder.getName(exitNode) != null){
614611 hintWay.addTag("mkgmap:exit_hint_name", nameFinder.getName(exitNode));
625622
626623 if (processDestinations) {
627624 // use link ways only
628 while (destinationLinkWays.isEmpty() == false) {
625 while (!destinationLinkWays.isEmpty()) {
629626 Way w = destinationLinkWays.values().iterator().next();
630627 destinationLinkWays.remove(w.getId());
631628 if (isNotOneway(w)) {
715712 */
716713 private double getAngle(Coord cCenter, Coord c1, Coord c2)
717714 {
718 double dx1 = c1.getLongitude() - cCenter.getLongitude();
719 double dy1 = -(c1.getLatitude() - cCenter.getLatitude());
720
721 double dx2 = c2.getLongitude() - cCenter.getLongitude();
722 double dy2 = -(c2.getLatitude() - cCenter.getLatitude());
723
724 double inRads1 = Math.atan2(dy1,dx1);
725 double inRads2 = Math.atan2(dy2,dx2);
726
727 return Math.toDegrees(inRads2) - Math.toDegrees(inRads1);
715 int dx1 = c1.getLongitude() - cCenter.getLongitude();
716 int dy1 = -(c1.getLatitude() - cCenter.getLatitude());
717
718 int dx2 = c2.getLongitude() - cCenter.getLongitude();
719 int dy2 = -(c2.getLatitude() - cCenter.getLatitude());
720
721 double inRads1 = Math.atan2(dy1, dx1);
722 double inRads2 = Math.atan2(dy2, dx2);
723
724 return Math.toDegrees(inRads2) - Math.toDegrees(inRads1);
728725 }
729726
730727 /**
739736 nameFinder = null;
740737 }
741738
739 @Override
742740 public Set<String> getUsedTags() {
743741 if (!(processDestinations || processExits))
744742 return Collections.emptySet();
746744 // to be able to copy the value to the destination tag
747745 // Do not load destination because it makes sense only if the tag is
748746 // referenced in the style file
749 Set<String> tags = new HashSet<String>();
747 Set<String> tags = new HashSet<>();
750748 tags.add("highway");
751749 tags.add("destination");
752750 tags.add("destination:lanes");
762760 return tags;
763761 }
764762
763 @Override
765764 public void end() {
766765 log.info("LinkDestinationHook started");
767766
788787 // check if oneway is set implicitly by the highway type (motorway and motorway_link)
789788 String onewayTag = w.getTag("oneway");
790789 String highwayTag = w.getTag("highway");
791 if (onewayTag == null && highwayTag != null
792 && (highwayTag.equals("motorway") || highwayTag.equals("motorway_link"))) {
793 return true;
794 }
795 return false;
790 return onewayTag == null && highwayTag != null
791 && (highwayTag.equals("motorway") || highwayTag.equals("motorway_link"));
796792 }
797793
798794 /**
810806 * @return <code>true</code> way is not oneway
811807 */
812808 private boolean isNotOneway(Way w) {
813 return "no".equals(w.getTag("oneway")) ||
814 (isOnewayInDirection(w) == false
815 && isOnewayOppositeDirection(w) == false);
809 return "no".equals(w.getTag("oneway")) || (!isOnewayInDirection(w) && !isOnewayOppositeDirection(w));
816810 }
817811
818812 /** Private length function without caching */
819813 private LengthFunction length = new LengthFunction() {
814 @Override
820815 public boolean isCached() {
821816 return false;
822817 }
2424 import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryUtil;
2525 import uk.me.parabola.util.EnhancedProperties;
2626
27 public class LocationHook extends OsmReadingHooksAdaptor {
27 public class LocationHook implements OsmReadingHooks {
2828 private static final Logger log = Logger.getLogger(LocationHook.class);
2929 // the resulting assignments are logged with this extra logger
3030 // so that it is possible to log only the results of the location hook
5252
5353 private EnhancedProperties props;
5454
55 @Override
5556 public boolean init(ElementSaver saver, EnhancedProperties props) {
5657 boundaryDirName = props.getProperty("bounds");
5758
6970 // checking of the boundary dir is expensive
7071 // check once only and reuse the result
7172 if (boundaryDirName.equals(checkedBoundaryDirName)) {
72 if (checkBoundaryDirOk == false) {
73 if (!checkBoundaryDirOk) {
7374 log.error("Disable LocationHook because bounds directory is unusable. Dir: "+boundaryDirName);
7475 return false;
7576 }
7879 checkBoundaryDirOk = false;
7980
8081 List<String> boundaryFiles = BoundaryUtil.getBoundaryDirContent(boundaryDirName);
81 if (boundaryFiles == null || boundaryFiles.size() == 0) {
82 if (boundaryFiles == null || boundaryFiles.isEmpty()) {
8283 log.error("LocationHook is disabled because no bounds files are available. Dir: "
8384 + boundaryDirName);
8485 return false;
9192 return true;
9293 }
9394
95 @Override
9496 public void end() {
9597 long t1 = System.currentTimeMillis();
9698 log.info("Starting with location hook");
119121 private void processLocationRelevantElements() {
120122 // process all nodes that might be converted to a garmin node (tagcount > 0)
121123 for (Node node : saver.getNodes().values()) {
122 if (node.getTagCount() > 0) {
123 if (saver.getBoundingBox().contains(node.getLocation())){
124 processElem(node);
125 if (resultLog.isDebugEnabled())
126 resultLog.debug("N", node.getId(), locationTagsToString(node));
127 }
124 if (node.getTagCount() > 0 && saver.getBoundingBox().contains(node.getLocation())) {
125 processElem(node);
126 if (resultLog.isDebugEnabled())
127 resultLog.debug("N", node.getId(), locationTagsToString(node));
128128 }
129129 }
130130
161161
162162 // the inner areas of the cut point have been processed
163163 // they are no longer needed
164
164165 for (Area cutArea : cutPoint.getAreas()) {
165166 ListIterator<Area> areaIter = areaCutData.innerAreas.listIterator();
166167 while (areaIter.hasNext()) {
171172 }
172173 }
173174 }
174 // remove all does not seem to work. It removes more than the identical areas.
175 // areaCutData.innerAreas.removeAll(cutPoint.getAreas());
176175
177176 if (areaCutData.outerArea.isSingular()) {
178177 // the area is singular
575574 if (minAspectRatio == null) {
576575 // first get the left/upper cut
577576 Rectangle2D r1 = getCutRectangleForArea(outerBounds, true);
578 double s1_1 = CoordinateAxis.LATITUDE.getSizeOfSide(r1);
579 double s1_2 = CoordinateAxis.LONGITUDE.getSizeOfSide(r1);
580 double ar1 = Math.min(s1_1, s1_2) / Math.max(s1_1, s1_2);
577 double s11 = CoordinateAxis.LATITUDE.getSizeOfSide(r1);
578 double s12 = CoordinateAxis.LONGITUDE.getSizeOfSide(r1);
579 double ar1 = Math.min(s11, s12) / Math.max(s11, s12);
581580
582581 // second get the right/lower cut
583582 Rectangle2D r2 = getCutRectangleForArea(outerBounds, false);
584 double s2_1 = CoordinateAxis.LATITUDE.getSizeOfSide(r2);
585 double s2_2 = CoordinateAxis.LONGITUDE.getSizeOfSide(r2);
586 double ar2 = Math.min(s2_1, s2_2) / Math.max(s2_1, s2_2);
583 double s21 = CoordinateAxis.LATITUDE.getSizeOfSide(r2);
584 double s22 = CoordinateAxis.LONGITUDE.getSizeOfSide(r2);
585 double ar2 = Math.min(s21, s22) / Math.max(s21, s22);
587586
588587 // get the minimum
589588 minAspectRatio = Math.min(ar1, ar2);
590589 }
591590 return minAspectRatio;
592591 }
592
593593
594594 public int compareTo(CutPoint o) {
595595 if (this == o) {
596596 return 0;
597597 }
598598 // prefer a cut at the boundaries
599 if (isStartCut() && o.isStartCut() == false) {
600 return 1;
601 }
602 else if (isStartCut() == false && o.isStartCut()) {
603 return -1;
604 }
605 else if (isStopCut() && o.isStopCut() == false) {
606 return 1;
607 }
608 else if (isStopCut() == false && o.isStopCut()) {
609 return -1;
610 }
611
599 int d = Boolean.compare(isStartCut(), o.isStartCut());
600 if (d != 0)
601 return d;
602 d = Boolean.compare(isStopCut(), o.isStopCut());
603 if (d != 0)
604 return d;
612605 // handle the special case that a cut has no area
613606 if (getNumberOfAreas() == 0) {
614 if (o.getNumberOfAreas() == 0) {
615 return 0;
616 } else {
617 return -1;
618 }
607 return o.getNumberOfAreas() == 0 ? 0 : -1;
619608 } else if (o.getNumberOfAreas() == 0) {
620609 return 1;
621610 }
622
623 if (isBadCutPoint() != o.isBadCutPoint()) {
624 if (isBadCutPoint()) {
625 return -1;
626 } else
627 return 1;
628 }
629
611
612 d = Boolean.compare(o.isBadCutPoint(), isBadCutPoint()); // exchanged order!
613 if (d != 0)
614 return d;
630615 double dAR = getMinAspectRatio() - o.getMinAspectRatio();
631616 if (dAR != 0) {
632617 return (dAR > 0 ? 1 : -1);
633618 }
634
635 if (isGoodCutPoint() != o.isGoodCutPoint()) {
636 if (isGoodCutPoint())
637 return 1;
638 else
639 return -1;
640 }
641
619 d = Boolean.compare(isGoodCutPoint(), o.isGoodCutPoint());
620 if (d != 0)
621 return d;
642622 // prefer the larger area that is split
643 double ss1 = axis.getSizeOfSide(getBounds2D());
644 double ss2 = o.axis.getSizeOfSide(o.getBounds2D());
645 if (ss1-ss2 != 0)
646 return Double.compare(ss1,ss2);
647
648 int ndiff = getNumberOfAreas()-o.getNumberOfAreas();
649 return ndiff;
623 d = Double.compare(axis.getSizeOfSide(getBounds2D()), o.axis.getSizeOfSide(o.getBounds2D()));
624 if (d != 0)
625 return d;
626
627 return getNumberOfAreas() - o.getNumberOfAreas();
650628
651629 }
652630
665643 }
666644 }
667645
668 private static enum CoordinateAxis {
646 private enum CoordinateAxis {
669647 LATITUDE(false), LONGITUDE(true);
670648
671649 private CoordinateAxis(boolean useX) {
1616 import uk.me.parabola.log.Logger;
1717 import uk.me.parabola.util.EnhancedProperties;
1818
19 public class MultiPolygonFinishHook extends OsmReadingHooksAdaptor {
19 public class MultiPolygonFinishHook implements OsmReadingHooks {
2020 private static final Logger log = Logger.getLogger(MultiPolygonFinishHook.class);
2121
2222 private ElementSaver saver;
23
24 public MultiPolygonFinishHook() {
25 }
2623
24 @Override
2725 public boolean init(ElementSaver saver, EnhancedProperties props) {
2826 this.saver = saver;
2927 return true;
3028 }
3129
30 @Override
3231 public void end() {
3332 long t1 = System.currentTimeMillis();
3433 log.info("Finishing multipolygons");
2121 import java.util.BitSet;
2222 import java.util.Collection;
2323 import java.util.Collections;
24 import java.util.Comparator;
2524 import java.util.HashMap;
2625 import java.util.HashSet;
2726 import java.util.IdentityHashMap;
5554 public static final String STYLE_FILTER_LINE = "polyline";
5655 public static final String STYLE_FILTER_POLYGON = "polygon";
5756
58 /** A tag that is set with value true on each polygon that is created by the mp processing */
59 public static final String MP_CREATED_TAG = "mkgmap:mp_created";
60
57 /** A tag that is set with value true on each polygon that is created by the mp processing. */
58 public static final short MP_CREATED_TAG_KEY = TagDict.getInstance().xlate("mkgmap:mp_created");
59 private static final short MP_ROLE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:mp_role");
60 private static final short CACHE_AREA_SIZE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:cache_area_size");
6161 private final Map<Long, Way> tileWayMap;
6262 private final Map<Long, String> roleMap = new HashMap<>();
6363
192192 joinable = true;
193193 } else if (joinWay.getLastPoint() == tempWay.getFirstPoint()) {
194194 insIdx = joinWay.getPoints().size();
195 reverseTempWay = false;
196195 firstTmpIdx = 1;
197196 joinable = true;
198197 } else if (joinWay.getFirstPoint() == tempWay.getLastPoint()) {
199198 insIdx = 0;
200 reverseTempWay = false;
201199 firstTmpIdx = 0;
202200 joinable = true;
203201 } else if (joinWay.getLastPoint() == tempWay.getLastPoint()) {
237235 * @return a list of closed ways
238236 */
239237 protected ArrayList<JoinedWay> joinWays(List<Way> segments) {
240 // TODO check if the closed polygon is valid and implement a
241 // backtracking algorithm to get other combinations
238 // TODO check if the closed polygon is valid and implement a backtracking algorithm to get other combinations
242239
243240 ArrayList<JoinedWay> joinedWays = new ArrayList<>();
244241 if (segments == null || segments.isEmpty()) {
252249 JoinedWay jw = new JoinedWay(orgSegment);
253250 roleMap.put(jw.getId(), getRole(orgSegment));
254251 if (orgSegment.isClosed()) {
255 if (orgSegment.isComplete() == false) {
252 if (!orgSegment.isComplete()) {
256253 // the way is closed in planet but some points are missing in this tile
257254 // we can close it artificially
258255 if (log.isDebugEnabled())
298295 // if a role is not 'inner' or 'outer' then it is used as
299296 // universal
300297 // check if the roles of the ways are matching
301 if ((!"outer".equals(joinRole) && !"inner"
302 .equals(joinRole))
303 || (!"outer".equals(tempRole) && !"inner"
304 .equals(tempRole))
298 if ((!"outer".equals(joinRole) && !"inner".equals(joinRole))
299 || (!"outer".equals(tempRole) && !"inner".equals(tempRole))
305300 || (joinRole != null && joinRole.equals(tempRole))) {
306301 // the roles are matching => try to join both ways
307302 joined = joinWays(joinWay, tempWay, false);
313308 // role
314309 // or if the alternative way is shorter then check if
315310 // the way with the wrong role could be joined
316 if (wrongRoleWay == null
317 || wrongRoleWay.getPoints().size() < tempWay
318 .getPoints().size()) {
319 if (joinWays(joinWay, tempWay, true)) {
320 // save this way => maybe we will use it in the end
321 // if we don't find any other way
322 wrongRoleWay = tempWay;
323 }
311 if (wrongRoleWay == null || wrongRoleWay.getPoints().size() < tempWay.getPoints().size()
312 && joinWays(joinWay, tempWay, true)) {
313 // save this way => maybe we will use it in the end
314 // if we don't find any other way
315 wrongRoleWay = tempWay;
324316 }
325317 }
326318
374366 *
375367 * @param wayList
376368 * a list of ways
369 * @param maxCloseDist max distance between ends for artificial close
370 *
377371 */
378372 protected void closeWays(ArrayList<JoinedWay> wayList, double maxCloseDist) {
379373 for (JoinedWay way : wayList) {
383377 Coord p1 = way.getFirstPoint();
384378 Coord p2 = way.getLastPoint();
385379
386 if (tileBounds.insideBoundary(p1) == false
387 && tileBounds.insideBoundary(p2) == false) {
388 // both points lie outside the bbox or on the bbox
389
390 // check if both points are on the same side of the bounding box
391 if ((p1.getLatitude() <= tileBounds.getMinLat() && p2.getLatitude() <= tileBounds
392 .getMinLat())
393 || (p1.getLatitude() >= tileBounds.getMaxLat() && p2
394 .getLatitude() >= tileBounds.getMaxLat())
395 || (p1.getLongitude() <= tileBounds.getMinLong() && p2
396 .getLongitude() <= tileBounds.getMinLong())
397 || (p1.getLongitude() >= tileBounds.getMaxLong() && p2
398 .getLongitude() >= tileBounds.getMaxLong())) {
399 // they are on the same side outside of the bbox
400 // so just close them without worrying about if
401 // they intersect itself because the intersection also
402 // is outside the bbox
403 way.closeWayArtificially();
404 log.info("Endpoints of way", way,
405 "are both outside the bbox. Closing it directly.");
406 continue;
407 }
380 if (!tileBounds.insideBoundary(p1) && !tileBounds.insideBoundary(p2)
381 // both points lie outside the bbox or on the bbox
382 // check if both points are on the same side of the bounding box
383 && (p1.getLatitude() <= tileBounds.getMinLat() && p2.getLatitude() <= tileBounds.getMinLat())
384 || (p1.getLatitude() >= tileBounds.getMaxLat() && p2.getLatitude() >= tileBounds.getMaxLat())
385 || (p1.getLongitude() <= tileBounds.getMinLong() && p2.getLongitude() <= tileBounds.getMinLong())
386 || (p1.getLongitude() >= tileBounds.getMaxLong() && p2.getLongitude() >= tileBounds.getMaxLong())) {
387 // they are on the same side outside of the bbox
388 // so just close them without worrying about if
389 // they intersect itself because the intersection also
390 // is outside the bbox
391 way.closeWayArtificially();
392 log.info("Endpoints of way", way, "are both outside the bbox. Closing it directly.");
393 continue;
408394 }
409395
410396 Line2D closingLine = new Line2D.Float(p1.getLongitude(), p1
415401 // don't use the first and the last point
416402 // the closing line can intersect only in one point or complete.
417403 // Both isn't interesting for this check
418 for (Coord thisPoint : way.getPoints().subList(1,
419 way.getPoints().size() - 1)) {
420 if (lastPoint != null) {
421 if (closingLine.intersectsLine(lastPoint.getLongitude(),
422 lastPoint.getLatitude(), thisPoint.getLongitude(),
423 thisPoint.getLatitude())) {
424 intersects = true;
425 break;
426 }
404 for (Coord thisPoint : way.getPoints().subList(1, way.getPoints().size() - 1)) {
405 if (lastPoint != null && closingLine.intersectsLine(lastPoint.getLongitude(), lastPoint.getLatitude(),
406 thisPoint.getLongitude(), thisPoint.getLatitude())) {
407 intersects = true;
408 break;
427409 }
428410 lastPoint = thisPoint;
429411 }
459441 // sometimes the connection of both points cannot be done directly but with an intermediate point
460442 public Coord imC;
461443 public double distance;
462
463444 public ConnectionData() {
464445
465446 }
469450 List<JoinedWay> unclosed = new ArrayList<>();
470451
471452 for (JoinedWay w : allWays) {
472 if (w.hasIdenticalEndPoints() == false) {
453 if (!w.hasIdenticalEndPoints()) {
473454 unclosed.add(w);
474455 }
475456 }
476457 // try to connect ways lying outside or on the bbox
477458 if (unclosed.size() >= 2) {
478 log.debug("Checking",unclosed.size(),"unclosed ways for connections outside the bbox");
459 log.debug("Checking", unclosed.size(), "unclosed ways for connections outside the bbox");
479460 Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<>();
480461
481462 // check all ways for endpoints outside or on the bbox
482463 for (JoinedWay w : unclosed) {
483464 Coord c1 = w.getFirstPoint();
484465 Coord c2 = w.getLastPoint();
485 if (tileBounds.insideBoundary(c1)==false) {
486 log.debug("Point",c1,"of way",w.getId(),"outside bbox");
466 if (!tileBounds.insideBoundary(c1)) {
467 log.debug("Point", c1, "of way", w.getId(), "outside bbox");
487468 outOfBboxPoints.put(c1, w);
488469 }
489470
490 if (tileBounds.insideBoundary(c2)==false) {
491 log.debug("Point",c2,"of way",w.getId(),"outside bbox");
471 if (!tileBounds.insideBoundary(c2)) {
472 log.debug("Point", c2, "of way", w.getId(), "outside bbox");
492473 outOfBboxPoints.put(c2, w);
493474 }
494475 }
495476
496477 if (outOfBboxPoints.size() < 2) {
497 log.debug(outOfBboxPoints.size(),"point outside the bbox. No connection possible.");
478 log.debug(outOfBboxPoints.size(), "point outside the bbox. No connection possible.");
498479 return false;
499480 }
500481
508489 cd.w1 = outOfBboxPoints.get(cd.c1);
509490 cd.w2 = outOfBboxPoints.get(cd.c2);
510491
511 if (lineCutsBbox(cd.c1, cd.c2 )) {
492 if (lineCutsBbox(cd.c1, cd.c2)) {
512493 // Check if the way can be closed with one additional point
513494 // outside the bounding box.
514495 // The additional point is combination of the coords of both endpoints.
516497 // not cut the bounding box.
517498 // This can be removed when the splitter guarantees to provide logical complete
518499 // multi-polygons.
519 Coord edgePoint1 = new Coord(cd.c1.getLatitude(), cd.c2
520 .getLongitude());
521 Coord edgePoint2 = new Coord(cd.c2.getLatitude(), cd.c1
522 .getLongitude());
523
524 if (lineCutsBbox(cd.c1, edgePoint1) == false
525 && lineCutsBbox(edgePoint1, cd.c2) == false) {
500 Coord edgePoint1 = new Coord(cd.c1.getLatitude(), cd.c2.getLongitude());
501 Coord edgePoint2 = new Coord(cd.c2.getLatitude(), cd.c1.getLongitude());
502
503 if (!lineCutsBbox(cd.c1, edgePoint1) && !lineCutsBbox(edgePoint1, cd.c2)) {
526504 cd.imC = edgePoint1;
527 } else if (lineCutsBbox(cd.c1, edgePoint2) == false
528 && lineCutsBbox(edgePoint2, cd.c2) == false) {
505 } else if (!lineCutsBbox(cd.c1, edgePoint2) && !lineCutsBbox(edgePoint2, cd.c2)) {
529506 cd.imC = edgePoint1;
530507 } else {
531508 // both endpoints are on opposite sides of the bounding box
549526 (o1, o2) -> Double.compare(o1.distance, o2.distance));
550527
551528 if (minCon.w1 == minCon.w2) {
552 log.debug("Close a gap in way",minCon.w1);
529 log.debug("Close a gap in way", minCon.w1);
553530 if (minCon.imC != null)
554531 minCon.w1.getPoints().add(minCon.imC);
555532 minCon.w1.closeWayArtificially();
575552
576553
577554 /**
578 * Removes all ways non closed ways from the given list (
555 * Removes all non closed ways from the given list.
579556 * <code>{@link Way#hasIdenticalEndPoints()} == false</code>)
580557 *
581558 * @param wayList
632609 }
633610 }
634611
635 if (remove) {
636 // check if the polygon contains the complete bounding box
637 if (w.getBounds().contains(tileArea.getBounds())) {
638 remove = false;
639 }
612 // check if the polygon contains the complete bounding box
613 if (remove && w.getBounds().contains(tileArea.getBounds())) {
614 remove = false;
640615 }
641616
642617 if (remove) {
719694 }
720695 // sort by role and then by number of points, this improves performance
721696 // in the routines which add the polygons to areas
722 if (polygonStatusList.size() > 2){
697 if (polygonStatusList.size() > 2) {
723698 polygonStatusList.sort((o1, o2) -> {
724699 if (o1.outer != o2.outer)
725700 return (o1.outer) ? -1 : 1;
736711 protected List<Way> getSourceWays() {
737712 ArrayList<Way> allWays = new ArrayList<>();
738713
739 for (Map.Entry<String, Element> r_e : getElements()) {
740 if (r_e.getValue() instanceof Way) {
741 if (((Way)r_e.getValue()).getPoints().isEmpty()) {
742 log.warn("Way",r_e.getValue(),"has no points and cannot be used for the multipolygon",toBrowseURL());
714 for (Map.Entry<String, Element> entry : getElements()) {
715 if (entry.getValue() instanceof Way) {
716 if (((Way) entry.getValue()).getPoints().isEmpty()) {
717 log.warn("Way", entry.getValue(), "has no points and cannot be used for the multipolygon",
718 toBrowseURL());
743719 } else {
744 allWays.add((Way) r_e.getValue());
745 }
746 } else if (r_e.getValue() instanceof Node == false ||
747 ("admin_centre".equals(r_e.getKey()) == false && "label".equals(r_e.getKey()) == false)) {
748 log.warn("Non way member in role", r_e.getKey(), r_e.getValue().toBrowseURL(),
720 allWays.add((Way) entry.getValue());
721 }
722 } else if (!(entry.getValue() instanceof Node)
723 || (!"admin_centre".equals(entry.getKey()) && !"label".equals(entry.getKey()))) {
724 log.warn("Non way member in role", entry.getKey(), entry.getValue().toBrowseURL(),
749725 "in multipolygon", toBrowseURL(), toTagString());
750726 }
751727 }
772748 List<Way> allWays = getSourceWays();
773749
774750 // check if it makes sense to process the mp
775 if (isMpProcessable(allWays) == false) {
776 log.info("Do not process multipolygon",getId(),"because it has no style relevant tags.");
751 if (!isMpProcessable(allWays)) {
752 log.info("Do not process multipolygon", getId(), "because it has no style relevant tags.");
777753 return;
778754 }
779755
800776 if (polygons.isEmpty()) {
801777 // do nothing
802778 if (log.isInfoEnabled()) {
803 if (hasPolygons)
804 log.info("Multipolygon", toBrowseURL(),
805 "is completely outside the bounding box. It is not processed.");
806 else
807 log.info("Multipolygon " + toBrowseURL() + " does not contain a closed polygon.");
779 log.info("Multipolygon", toBrowseURL(),
780 hasPolygons ? "is completely outside the bounding box. It is not processed."
781 : "does not contain a closed polygon.");
808782 }
809783 tagOuterWays();
810784 cleanup();
939913 }
940914 } while (!holesOk);
941915
942 ArrayList<PolygonStatus> holes = getPolygonStatus(holeIndexes,
943 (currentPolygon.outer ? "inner" : "outer"));
916 ArrayList<PolygonStatus> holes = getPolygonStatus(holeIndexes, (currentPolygon.outer ? "inner" : "outer"));
944917
945918 // these polygons must all be checked for holes
946919 polygonWorkingQueue.addAll(holes);
967940
968941 // check if the polygon is an outer polygon or
969942 // if there are some holes
970 boolean processPolygon = currentPolygon.outer
971 || (holes.isEmpty()==false);
943 boolean processPolygon = currentPolygon.outer || (!holes.isEmpty());
972944
973945 if (processPolygon) {
974946 List<Way> singularOuterPolygons;
985957 singularOuterPolygons = cutter.cutOutInnerPolygons(currentPolygon.polygon, innerWays);
986958 }
987959
988 if (singularOuterPolygons.isEmpty()==false) {
960 if (!singularOuterPolygons.isEmpty()) {
989961 // handle the tagging
990962 if (currentPolygon.outer && hasStyleRelevantTags(this)) {
991963 // use the tags of the multipolygon
1024996 // put the cut out polygons to the
1025997 // final way map
1026998 if (log.isDebugEnabled())
1027 log.debug(mpWay.getId(),mpWay.toTagString());
999 log.debug(mpWay.getId(), mpWay.toTagString());
10281000
10291001 mpWay.setFullArea(fullArea);
10301002 // mark this polygons so that only polygon style rules are applied
10311003 mpWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_POLYGON);
1032 mpWay.addTag(MP_CREATED_TAG, "true");
1004 mpWay.addTag(MP_CREATED_TAG_KEY, "true");
10331005
10341006 if (currentPolygon.outer) {
1035 mpWay.addTag("mkgmap:mp_role", "outer");
1007 mpWay.addTag(MP_ROLE_TAG_KEY, "outer");
10361008 if (isAreaSizeCalculated())
10371009 mpAreaSize += calcAreaSize(mpWay.getPoints());
10381010 } else {
1039 mpWay.addTag("mkgmap:mp_role", "inner");
1011 mpWay.addTag(MP_ROLE_TAG_KEY, "inner");
10401012 }
10411013
10421014 getMpPolygons().put(mpWay.getId(), mpWay);
10451017 }
10461018 }
10471019
1048 if (log.isLoggable(Level.WARNING) &&
1049 (outmostInnerPolygons.cardinality()+unfinishedPolygons.cardinality()+nestedOuterPolygons.cardinality()+nestedInnerPolygons.cardinality() >= 1)) {
1020 if (log.isLoggable(Level.WARNING) && (outmostInnerPolygons.cardinality() + unfinishedPolygons.cardinality()
1021 + nestedOuterPolygons.cardinality() + nestedInnerPolygons.cardinality() >= 1)) {
10501022 log.warn("Multipolygon", toBrowseURL(), toTagString(), "contains errors.");
10511023
10521024 BitSet outerUnusedPolys = new BitSet();
10791051 }
10801052 }
10811053
1082 if (hasStyleRelevantTags(this) == false) {
1054 if (!hasStyleRelevantTags(this)) {
10831055 // add tags to the multipolygon that are taken from the outer ways
10841056 // they may be required by some hooks (e.g. Area2POIHook)
10851057 for (Entry<String, String> tags : outerTags.entrySet()) {
10991071 Way lineTagWay = new Way(getOriginalId(), orgOuterWay.getPoints());
11001072 lineTagWay.setFakeId();
11011073 lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE);
1102 lineTagWay.addTag(MP_CREATED_TAG, "true");
1074 lineTagWay.addTag(MP_CREATED_TAG_KEY, "true");
11031075 if (mpAreaSizeStr != null) {
11041076 // assign the area size of the whole multipolygon to all outer polygons
1105 lineTagWay.addTag("mkgmap:cache_area_size", mpAreaSizeStr);
1077 lineTagWay.addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr);
11061078 }
11071079 for (Entry<String,String> tag : outerTags.entrySet()) {
11081080 lineTagWay.addTag(tag.getKey(), tag.getValue());
11321104 if (isAreaSizeCalculated()) {
11331105 // assign the area size of the whole multipolygon to all outer polygons
11341106 String mpAreaSizeStr = String.format(Locale.US, "%.3f", mpAreaSize);
1135 addTag("mkgmap:cache_area_size", mpAreaSizeStr);
1107 addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr);
11361108 for (Way w : mpPolygons.values()) {
1137 if ("outer".equals(w.getTag("mkgmap:mp_role"))) {
1138 w.addTag("mkgmap:cache_area_size", mpAreaSizeStr);
1109 if ("outer".equals(w.getTag(MP_ROLE_TAG_KEY))) {
1110 w.addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr);
11391111 }
11401112 }
11411113 }
11481120
11491121 if (largestOuterPolygon != null) {
11501122 // check if the mp contains a node with role "label"
1151 for (Map.Entry<String, Element> r_e : getElements()) {
1152 if (r_e.getValue() instanceof Node && "label".equals(r_e.getKey())) {
1123 for (Map.Entry<String, Element> entry : getElements()) {
1124 if (entry.getValue() instanceof Node && "label".equals(entry.getKey())) {
11531125 // yes => use the label node as reference point
1154 cOfG = ((Node)r_e.getValue()).getLocation();
1126 cOfG = ((Node) entry.getValue()).getLocation();
11551127 break;
1156 }
1128 }
11571129 }
11581130
11591131 if (cOfG == null) {
12311203 }
12321204 }
12331205
1234 private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons,
1235 BitSet innerPolygons) {
1206 private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons, BitSet innerPolygons) {
12361207 // find all unfinished inner polygons that are not contained by any
12371208 BitSet wrongInnerPolygons = findOutmostPolygons(unfinishedPolygons, innerPolygons);
12381209 if (log.isDebugEnabled()) {
13031274 * @return <code>true</code> has style relevant tags
13041275 */
13051276 protected boolean hasStyleRelevantTags(Element element) {
1306 if (element instanceof MultiPolygonRelation) {
1307 if (((MultiPolygonRelation) element).getTagsIncomplete()) {
1308 return true;
1309 }
1277 if (element instanceof MultiPolygonRelation && ((MultiPolygonRelation) element).getTagsIncomplete()) {
1278 return true;
13101279 }
13111280
13121281 for (Map.Entry<String, String> tagEntry : element.getTagEntryIterator()) {
13131282 String tagName = tagEntry.getKey();
13141283 // all tags are style relevant
13151284 // except: type (for relations), mkgmap:*
1316 boolean isStyleRelevant = (element instanceof Relation && tagName.equals("type")) == false
1317 && tagName.startsWith("mkgmap:") == false;
1285 boolean isStyleRelevant = !(element instanceof Relation && "typ".equals(tagName))
1286 && !tagName.startsWith("mkgmap:");
13181287 if (isStyleRelevant) {
13191288 return true;
13201289 }
14301399 int i = 0;
14311400 boolean noContained = true;
14321401 for (BitSet b : containsMatrix) {
1433 if (b.isEmpty()==false) {
1402 if (!b.isEmpty()) {
14341403 log.debug(i,"contains",b);
14351404 noContained = false;
14361405 }
15111480 allOnLine = false;
15121481 break;
15131482 }
1514 } else if (tileBounds.contains(px)) {
1515 // we have to check if the point is on one line of the polygon1
1516
1517 if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
1518 // there's one point that is not in polygon1 but inside the
1519 // bounding box => polygon1 does not contain polygon2
1520 //allOnLine = false;
1521 return false;
1522 }
1483 } else if (tileBounds.contains(px) && !locatedOnLine(px, polygon1.getWay().getPoints())) {
1484 // there's one point that is not in polygon1 but inside the
1485 // bounding box => polygon1 does not contain polygon2
1486 return false;
15231487 }
15241488 }
15251489
15431507 // box => polygon1 may contain polygon2
15441508 onePointContained = true;
15451509 break;
1546 } else if (tileBounds.contains(px)) {
1547 // we have to check if the point is on one line of the polygon1
1548
1549 if (!locatedOnLine(px, polygon1.getWay().getPoints())) {
1550 // there's one point that is not in polygon1 but inside the
1551 // bounding box => polygon1 does not contain polygon2
1552 return false;
1553 }
1510 } else if (tileBounds.contains(px) && !locatedOnLine(px, polygon1.getWay().getPoints())) {
1511 // there's one point that is not in polygon1 but inside the
1512 // bounding box => polygon1 does not contain polygon2
1513 return false;
15541514 }
15551515 }
15561516 }
15611521 }
15621522
15631523 Iterator<Coord> it1 = polygon1.getWay().getPoints().iterator();
1564 Coord p1_1 = it1.next();
1524 Coord p11 = it1.next();
15651525
15661526 while (it1.hasNext()) {
1567 Coord p1_2 = p1_1;
1568 p1_1 = it1.next();
1569
1570 if (!polygon2.linePossiblyIntersectsWay(p1_1, p1_2)) {
1527 Coord p12 = p11;
1528 p11 = it1.next();
1529
1530 if (!polygon2.linePossiblyIntersectsWay(p11, p12)) {
15711531 // don't check it - this segment of the outer polygon
15721532 // definitely does not intersect the way
15731533 continue;
15741534 }
15751535
1576 int lonMin = Math.min(p1_1.getLongitude(), p1_2.getLongitude());
1577 int lonMax = Math.max(p1_1.getLongitude(), p1_2.getLongitude());
1578 int latMin = Math.min(p1_1.getLatitude(), p1_2.getLatitude());
1579 int latMax = Math.max(p1_1.getLatitude(), p1_2.getLatitude());
1536 int lonMin = Math.min(p11.getLongitude(), p12.getLongitude());
1537 int lonMax = Math.max(p11.getLongitude(), p12.getLongitude());
1538 int latMin = Math.min(p11.getLatitude(), p12.getLatitude());
1539 int latMax = Math.max(p11.getLatitude(), p12.getLatitude());
15801540
15811541 // check all lines of way1 and way2 for intersections
15821542 Iterator<Coord> it2 = polygon2.getPoints().iterator();
1583 Coord p2_1 = it2.next();
1543 Coord p21 = it2.next();
15841544
15851545 // for speedup we divide the area around the second line into
15861546 // a 3x3 matrix with lon(-1,0,1) and lat(-1,0,1).
15871547 // -1 means below min lon/lat of bbox line p1_1-p1_2
15881548 // 0 means inside the bounding box of the line p1_1-p1_2
15891549 // 1 means above max lon/lat of bbox line p1_1-p1_2
1590 int lonField = p2_1.getLongitude() < lonMin ? -1 : p2_1
1591 .getLongitude() > lonMax ? 1 : 0;
1592 int latField = p2_1.getLatitude() < latMin ? -1 : p2_1
1593 .getLatitude() > latMax ? 1 : 0;
1550 int lonField = p21.getLongitude() < lonMin ? -1 : p21.getLongitude() > lonMax ? 1 : 0;
1551 int latField = p21.getLatitude() < latMin ? -1 : p21.getLatitude() > latMax ? 1 : 0;
15941552
15951553 int prevLonField = lonField;
15961554 int prevLatField = latField;
15971555
15981556 while (it2.hasNext()) {
1599 Coord p2_2 = p2_1;
1600 p2_1 = it2.next();
1557 Coord p22 = p21;
1558 p21 = it2.next();
16011559
16021560 int changes = 0;
16031561 // check if the field of the 3x3 matrix has changed
1604 if ((lonField >= 0 && p1_1.getLongitude() < lonMin)
1605 || (lonField <= 0 && p1_1.getLongitude() > lonMax)) {
1562 if ((lonField >= 0 && p11.getLongitude() < lonMin)
1563 || (lonField <= 0 && p11.getLongitude() > lonMax)) {
16061564 changes++;
1607 lonField = p1_1.getLongitude() < lonMin ? -1 : p1_1
1608 .getLongitude() > lonMax ? 1 : 0;
1609 }
1610 if ((latField >= 0 && p1_1.getLatitude() < latMin)
1611 || (latField <= 0 && p1_1.getLatitude() > latMax)) {
1565 lonField = p11.getLongitude() < lonMin ? -1 : p11.getLongitude() > lonMax ? 1 : 0;
1566 }
1567 if ((latField >= 0 && p11.getLatitude() < latMin)
1568 || (latField <= 0 && p11.getLatitude() > latMax)) {
16121569 changes++;
1613 latField = p1_1.getLatitude() < latMin ? -1 : p1_1
1614 .getLatitude() > latMax ? 1 : 0;
1570 latField = p11.getLatitude() < latMin ? -1 : p11.getLatitude() > latMax ? 1 : 0;
16151571 }
16161572
16171573 // an intersection is possible if
16211577 || (latField == 0 && lonField == 0)
16221578 || (prevLatField == 0 && prevLonField == 0);
16231579
1624 boolean intersects = intersectionPossible
1625 && linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
1580 boolean intersects = intersectionPossible && linesCutEachOther(p11, p12, p21, p22);
16261581
16271582 if (intersects) {
16281583 if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext())
16321587 // closing segment causes the intersection
16331588 log.info("Polygon", polygon1, "may contain polygon", polygon2,
16341589 ". Ignoring artificial generated intersection.");
1635 } else if ((!tileBounds.contains(p1_1))
1636 || (!tileBounds.contains(p1_2))
1637 || (!tileBounds.contains(p2_1))
1638 || (!tileBounds.contains(p2_2))) {
1590 } else if ((!tileBounds.contains(p11))
1591 || (!tileBounds.contains(p12))
1592 || (!tileBounds.contains(p21))
1593 || (!tileBounds.contains(p22))) {
16391594 // at least one point is outside the bounding box
16401595 // we ignore the intersection because the ways may not
16411596 // be complete
16761631 }
16771632
16781633 try {
1679 if (cp1 == null) {
1680 // first init
1681 continue;
1682 }
1683
1684 if (p.getHighPrecLon() < Math.min(cp1.getHighPrecLon(), cp2.getHighPrecLon())) {
1685 continue;
1686 }
1687 if (p.getHighPrecLon() > Math.max(cp1.getHighPrecLon(), cp2.getHighPrecLon())) {
1688 continue;
1689 }
1690 if (p.getHighPrecLat() < Math.min(cp1.getHighPrecLat(), cp2.getHighPrecLat())) {
1691 continue;
1692 }
1693 if (p.getHighPrecLat() > Math.max(cp1.getHighPrecLat(), cp2.getHighPrecLat())) {
1634 if (cp1 == null // first init
1635 || p.getHighPrecLon() < Math.min(cp1.getHighPrecLon(), cp2.getHighPrecLon())
1636 || p.getHighPrecLon() > Math.max(cp1.getHighPrecLon(), cp2.getHighPrecLon())
1637 || p.getHighPrecLat() < Math.min(cp1.getHighPrecLat(), cp2.getHighPrecLat())
1638 || p.getHighPrecLat() > Math.max(cp1.getHighPrecLat(), cp2.getHighPrecLat())) {
16941639 continue;
16951640 }
16961641
17101655 return false;
17111656 }
17121657
1713 private boolean lineCutsBbox(Coord p1_1, Coord p1_2) {
1658 private boolean lineCutsBbox(Coord p1, Coord p2) {
17141659 Coord nw = new Coord(tileBounds.getMaxLat(), tileBounds.getMinLong());
17151660 Coord sw = new Coord(tileBounds.getMinLat(), tileBounds.getMinLong());
17161661 Coord se = new Coord(tileBounds.getMinLat(), tileBounds.getMaxLong());
17171662 Coord ne = new Coord(tileBounds.getMaxLat(), tileBounds.getMaxLong());
1718 return linesCutEachOther(nw, sw, p1_1, p1_2)
1719 || linesCutEachOther(sw, se, p1_1, p1_2)
1720 || linesCutEachOther(se, ne, p1_1, p1_2)
1721 || linesCutEachOther(ne, nw, p1_1, p1_2);
1663 return linesCutEachOther(nw, sw, p1, p2)
1664 || linesCutEachOther(sw, se, p1, p2)
1665 || linesCutEachOther(se, ne, p1, p2)
1666 || linesCutEachOther(ne, nw, p1, p2);
17221667 }
17231668
17241669 /**
17261671 * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa.
17271672 * This is a form of intersection check where it is allowed that one line ends on the
17281673 * other line or that the two lines overlap.
1729 * @param p1_1 first point of line 1
1730 * @param p1_2 second point of line 1
1731 * @param p2_1 first point of line 2
1732 * @param p2_2 second point of line 2
1674 * @param p11 first point of line 1
1675 * @param p12 second point of line 1
1676 * @param p21 first point of line 2
1677 * @param p22 second point of line 2
17331678 * @return true if both lines intersect somewhere in the middle of each other
17341679 */
1735 private static boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) {
1736 long width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon();
1737 long width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon();
1738
1739 long height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat();
1740 long height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat();
1680 private static boolean linesCutEachOther(Coord p11, Coord p12, Coord p21, Coord p22) {
1681 long width1 = (long) p12.getHighPrecLon() - p11.getHighPrecLon();
1682 long width2 = (long) p22.getHighPrecLon() - p21.getHighPrecLon();
1683
1684 long height1 = (long) p12.getHighPrecLat() - p11.getHighPrecLat();
1685 long height2 = (long) p22.getHighPrecLat() - p21.getHighPrecLat();
17411686
17421687 long denominator = ((height2 * width1) - (width2 * height1));
17431688 if (denominator == 0) {
17461691 return false;
17471692 }
17481693
1749 long x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon();
1750 long y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat();
1751
1752 double isx = (double)((width2 * y1My3) - (height2 * x1Mx3))
1753 / denominator;
1694 long x1Mx3 = (long) p11.getHighPrecLon() - p21.getHighPrecLon();
1695 long y1My3 = (long) p11.getHighPrecLat() - p21.getHighPrecLat();
1696
1697 double isx = (double) ((width2 * y1My3) - (height2 * x1Mx3)) / denominator;
17541698 if (isx <= 0 || isx >= 1) {
17551699 return false;
17561700 }
17571701
1758 double isy = (double)((width1 * y1My3) - (height1 * x1Mx3))
1759 / denominator;
1760
1761 if (isy <= 0 || isy >= 1) {
1762 return false;
1763 }
1764 return true;
1702 double isy = (double) ((width1 * y1My3) - (height1 * x1Mx3)) / denominator;
1703
1704 return (isy > 0 && isy < 1);
17651705 }
17661706
17671707 private List<JoinedWay> getWaysFromPolygonList(BitSet selection) {
18081748 * @param fakeWay a way composed by other ways with faked ids
18091749 */
18101750 private void logFakeWayDetails(Level logLevel, JoinedWay fakeWay) {
1811 if (log.isLoggable(logLevel) == false) {
1751 if (!log.isLoggable(logLevel)) {
18121752 return;
18131753 }
18141754
18151755 // only log if this is an artificial multipolygon
1816 if (FakeIdGenerator.isFakeId(getId()) == false) {
1756 if (!FakeIdGenerator.isFakeId(getId())) {
18171757 return;
18181758 }
18191759
18241764 }
18251765 }
18261766
1827 if (containsOrgFakeWay == false) {
1767 if (!containsOrgFakeWay) {
18281768 return;
18291769 }
18301770
18671807 Way lineTagWay = new Way(getOriginalId(), orgOuterWay.getPoints());
18681808 lineTagWay.setFakeId();
18691809 lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE);
1870 lineTagWay.addTag(MP_CREATED_TAG, "true");
1871 for (Entry<String,String> tag : tags.entrySet()) {
1810 lineTagWay.addTag(MP_CREATED_TAG_KEY, "true");
1811 for (Entry<String, String> tag : tags.entrySet()) {
18721812 lineTagWay.addTag(tag.getKey(), tag.getValue());
18731813
18741814 // remove the tag from the original way if it has the same value
19091849 * @param tagvalue
19101850 * the value of the tag to be removed
19111851 */
1912 private void removeTagInOrgWays(JoinedWay way, String tagname,
1913 String tagvalue) {
1852 private void removeTagInOrgWays(JoinedWay way, String tagname, String tagvalue) {
19141853 for (Way w : way.getOriginalWays()) {
19151854 if (w instanceof JoinedWay) {
19161855 // remove the tags recursively
19771916 * @return the size of the area (unitless)
19781917 */
19791918 public static double calcAreaSize(List<Coord> polygon) {
1980 if (polygon.size() < 4 || polygon.get(0) != polygon.get(polygon.size()-1)) {
1919 if (polygon.size() < 4 || polygon.get(0) != polygon.get(polygon.size() - 1)) {
19811920 return 0; // line or not closed
19821921 }
19831922 long area = 0;
19901929 * (c1.getHighPrecLat() - c2.getHighPrecLat());
19911930 }
19921931 // convert from high prec to value in map units
1993 double areaSize = (double) area / (2 * (1<<Coord.DELTA_SHIFT) * (1<<Coord.DELTA_SHIFT));
1932 double areaSize = (double) area / (2 * (1 << Coord.DELTA_SHIFT) * (1 << Coord.DELTA_SHIFT));
19941933 return Math.abs(areaSize);
19951934 }
19961935
20281967 updateBounds(point);
20291968 }
20301969
1970 @Override
20311971 public void addPoint(Coord point) {
20321972 super.addPoint(point);
20331973 updateBounds(point);
20351975
20361976 private void updateBounds(List<Coord> pointList) {
20371977 for (Coord c : pointList) {
2038 updateBounds(c.getLatitude(),c.getLongitude());
2039 }
2040 }
2041
2042 private void updateBounds (JoinedWay other){
1978 updateBounds(c.getLatitude(), c.getLongitude());
1979 }
1980 }
1981
1982 private void updateBounds(JoinedWay other) {
20431983 updateBounds(other.minLat,other.minLon);
20441984 updateBounds(other.maxLat,other.maxLon);
20451985 }
20772017 * bounding box; <code>false</code> else
20782018 */
20792019 public boolean intersects(uk.me.parabola.imgfmt.app.Area bbox) {
2080 return (maxLat >= bbox.getMinLat() &&
2081 minLat <= bbox.getMaxLat() &&
2082 maxLon >= bbox.getMinLong() &&
2083 minLon <= bbox.getMaxLong());
2020 return (maxLat >= bbox.getMinLat()
2021 && minLat <= bbox.getMaxLat()
2022 && maxLon >= bbox.getMinLong()
2023 && minLon <= bbox.getMaxLong());
20842024 }
20852025
20862026 public Rectangle getBounds() {
21052045 for (Way w : ((JoinedWay) way).getOriginalWays()) {
21062046 addWay(w);
21072047 }
2108 updateBounds((JoinedWay)way);
2048 updateBounds((JoinedWay) way);
21092049 } else {
21102050 if (log.isDebugEnabled()) {
21112051 log.debug("Joined", this.getId(), "with", way.getId());
21232063 return closedArtificially;
21242064 }
21252065
2126 public static Map<String,String> getMergedTags(Collection<Way> ways) {
2127 Map<String,String> mergedTags = new HashMap<>();
2066 public static Map<String, String> getMergedTags(Collection<Way> ways) {
2067 Map<String, String> mergedTags = new HashMap<>();
21282068 boolean first = true;
21292069 for (Way way : ways) {
21302070 if (first) {
21382078 ArrayList<String> tagsToRemove = null;
21392079 for (Map.Entry<String, String> tag : mergedTags.entrySet()) {
21402080 String wayTagValue = way.getTag(tag.getKey());
2141 if (!tag.getValue().equals(wayTagValue)) {
2081 if (wayTagValue != null && !tag.getValue().equals(wayTagValue)) {
21422082 // the tags are different
2143 if (wayTagValue!= null) {
2144 if (tagsToRemove == null) {
2145 tagsToRemove=new ArrayList<>();
2146 }
2147 tagsToRemove.add(tag.getKey());
2083 if (tagsToRemove == null) {
2084 tagsToRemove = new ArrayList<>();
21482085 }
2086 tagsToRemove.add(tag.getKey());
21492087 }
21502088 }
2151 if (tagsToRemove!=null) {
2089 if (tagsToRemove != null) {
21522090 for (String tag : tagsToRemove) {
21532091 mergedTags.remove(tag);
21542092 }
21632101 */
21642102 public void mergeTagsFromOrgWays() {
21652103 if (log.isDebugEnabled()) {
2166 log.debug("Way",getId(),"merge tags from",getOriginalWays().size(),"ways");
2104 log.debug("Way", getId(), "merge tags from", getOriginalWays().size(), "ways");
21672105 }
21682106 removeAllTags();
21692107
2170 Map<String,String> mergedTags = getMergedTags(getOriginalWays());
2171 for (Entry<String,String> tag : mergedTags.entrySet()) {
2172 addTag(tag.getKey(),tag.getValue());
2108 Map<String, String> mergedTags = getMergedTags(getOriginalWays());
2109 for (Entry<String, String> tag : mergedTags.entrySet()) {
2110 addTag(tag.getKey(), tag.getValue());
21732111 }
21742112 }
21752113
21872125 return MultiPolygonRelation.calcAreaSize(getPoints());
21882126 }
21892127
2128 @Override
21902129 public String toString() {
21912130 StringBuilder sb = new StringBuilder(200);
21922131 sb.append(getId());
22222161 }
22232162
22242163 public String toString() {
2225 return polygon+"_"+outer;
2164 return polygon + "_" + outer;
22262165 }
22272166 }
22282167 }
3939 return "NODE: " + getId() + " @ " + location.toDegreeString();
4040 }
4141
42 @Override
4243 public String kind() {
4344 return "node";
4445 }
4546
47 @Override
4648 public Node copy() {
4749 Node dup = new Node(getId(), location);
4850 dup.copyIds(this);
3535 import java.util.function.UnaryOperator;
3636
3737 import uk.me.parabola.imgfmt.ExitException;
38 import uk.me.parabola.imgfmt.FormatException;
3938 import uk.me.parabola.imgfmt.Utils;
4039 import uk.me.parabola.log.Logger;
4140 import uk.me.parabola.mkgmap.Version;
136135 // First try command line, then style, then our default.
137136 String levelSpec = getConfig().getProperty(optionName);
138137 log.debug(optionName, levelSpec, ", ", ((levelSpec!=null)?levelSpec.length():""));
139 if (levelSpec == null || levelSpec.length() < 2) {
140 if (style != null) {
141 levelSpec = style.getOption(optionName);
142 log.debug("getting " + optionName + " from style:", levelSpec);
143 }
138 if ((levelSpec == null || levelSpec.length() < 2) && style != null) {
139 levelSpec = style.getOption(optionName);
140 log.debug("getting " + optionName + " from style:", levelSpec);
144141 }
145142 return levelSpec;
146143 }
147144
148145 @Override
149 public void load(String name, boolean addBackground) throws FileNotFoundException, FormatException {
146 public void load(String name, boolean addBackground) throws FileNotFoundException {
150147 InputStream is = Utils.openFile(name);
151148 parse(is, name);
152149 elementSaver.finishLoading();
196193 catch (Exception e) {
197194 throw new ExitException("Error reading copyright file " + copyrightFileName, e);
198195 }
199 if ((copyrightArray.size() > 0) && copyrightArray.get(0).startsWith("\ufeff"))
196 if (!copyrightArray.isEmpty() && copyrightArray.get(0).startsWith("\ufeff"))
200197 copyrightArray.set(0, copyrightArray.get(0).substring(1));
201198 UnaryOperator<String> replaceVariables = s -> s.replace("$MKGMAP_VERSION$", Version.VERSION)
202199 .replace("$JAVA_VERSION$", System.getProperty("java.version"))
270267 OsmReadingHooks hooks;
271268 switch (plugins.size()) {
272269 case 0:
273 hooks = new OsmReadingHooksAdaptor();
270 hooks = new NullHook();
274271 break;
275272 case 1:
276273 hooks = plugins.get(0);
285282 usedTags.addAll(hooks.getUsedTags());
286283 return hooks;
287284 }
285
286 /** do nothing hook */
287 private class NullHook implements OsmReadingHooks {}
288288
289289 private static Map<String, Set<String>> readDeleteTagsFile(String fileName) {
290290 Map<String, Set<String>> deletedTags = new HashMap<>();
331331 */
332332 protected void createConverter() {
333333 EnhancedProperties props = getConfig();
334 Style style = StyleImpl.readStyle(props);
335 setStyle(style);
334 setStyle(StyleImpl.readStyle(props));
336335
337336 usedTags.addAll(style.getUsedTags());
338337 usedTags.addAll(NameFinder.getNameTags(props));
1111 */
1212 package uk.me.parabola.mkgmap.reader.osm;
1313
14 import java.util.Collections;
1415 import java.util.Set;
1516
1617 import uk.me.parabola.imgfmt.app.Coord;
5152 * @return If you return false then this set of hooks will not be used. So if they
5253 * are not needed based on the options supplied you can disable it.
5354 */
54 public boolean init(ElementSaver saver, EnhancedProperties props);
55 public default boolean init(ElementSaver saver, EnhancedProperties props) {
56 return true;
57 }
5558
5659 /**
5760 * Retrieves the tags that are used by this hook. Tags that are used only if they are referenced
5962 *
6063 * @return the tag names used by this hook
6164 */
62 public Set<String> getUsedTags();
65 public default Set<String> getUsedTags() {
66 return Collections.emptySet();
67 }
6368
6469 /**
6570 * Called on adding a node to the saver and just before it is added. You can modify
6772 *
6873 * @param node The node to be added.
6974 */
70 void onAddNode(Node node);
75 default void onAddNode(Node node) {}
7176
7277 /**
7378 * Add the given way. The way must be complete, call after the end tag
7580 *
7681 * @param way The osm way.
7782 */
78 public void onAddWay(Way way);
83 public default void onAddWay(Way way) {}
7984
8085 /**
8186 * This is called whenever a node is added to a way. A node is something with tags, not just a Coord.
8691 * @param coordId The coordinate id of the node that is being added.
8792 * @param co The coordinate.
8893 */
89 public void onCoordAddedToWay(Way way, long coordId, Coord co);
94 public default void onCoordAddedToWay(Way way, long coordId, Coord co) {}
9095
9196 /**
9297 * Called after the file has been read. Can be used to add more elements to the saver
9398 * based on information stored up.
9499 */
95 public void end();
100 public default void end() {}
101
96102 }
+0
-48
src/uk/me/parabola/mkgmap/reader/osm/OsmReadingHooksAdaptor.java less more
0 /*
1 * Copyright (C) 2010.
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License version 3 or
5 * version 2 as published by the Free Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
11 */
12 package uk.me.parabola.mkgmap.reader.osm;
13
14 import java.util.Collections;
15 import java.util.Set;
16
17 import uk.me.parabola.imgfmt.app.Coord;
18 import uk.me.parabola.util.EnhancedProperties;
19
20 /**
21 * Provides empty implementation of all methods so that subclass that only
22 * need a few can just implement the ones they want.
23 *
24 * @author Steve Ratcliffe
25 */
26 public class OsmReadingHooksAdaptor implements OsmReadingHooks {
27
28 public boolean init(ElementSaver saver, EnhancedProperties props) {
29 return true;
30 }
31
32 public Set<String> getUsedTags() {
33 return Collections.emptySet();
34 }
35
36 public void onAddNode(Node node) {
37 }
38
39 public void onAddWay(Way way) {
40 }
41
42 public void onCoordAddedToWay(Way way, long coordId, Coord co) {
43 }
44
45 public void end() {
46 }
47 }
1111 */
1212 package uk.me.parabola.mkgmap.reader.osm;
1313
14 import java.util.ArrayList;
1514 import java.util.Arrays;
1615 import java.util.HashSet;
17 import java.util.List;
1816 import java.util.Set;
1917
2018 import uk.me.parabola.imgfmt.app.Coord;
3028 */
3129 public class OsmReadingHooksChain implements OsmReadingHooks {
3230
33 private static final OsmReadingHooks[] NO_HOOKS = new OsmReadingHooks[0];
34
35 private OsmReadingHooks[] readingHooks = NO_HOOKS;
31 private OsmReadingHooks[] readingHooks = {}; // no default hooks
3632
3733 /**
38 * Add a new set of hooks.
39 * @param hooks The reading hooks.
34 * Add a new hook at the end of the chain.
35 * @param hook The reading hook.
4036 */
41 public void add(OsmReadingHooks hooks) {
42 List<OsmReadingHooks> readingHooksList = new ArrayList<OsmReadingHooks>(Arrays.asList(readingHooks));
43 readingHooksList.add(hooks);
44 readingHooks = readingHooksList.toArray(new OsmReadingHooks[readingHooksList.size()]);
37 public void add(OsmReadingHooks hook) {
38 readingHooks = Arrays.copyOfRange(readingHooks, 0, readingHooks.length + 1);
39 readingHooks[readingHooks.length - 1] = hook;
4540 }
4641
42 @Override
4743 public Set<String> getUsedTags() {
48 HashSet<String> usedTags = new HashSet<String>();
44 HashSet<String> usedTags = new HashSet<>();
4945 for (int i = 0; i < readingHooks.length; i++)
5046 usedTags.addAll(readingHooks[i].getUsedTags());
5147 return usedTags;
5248 }
5349
50 @Override
5451 public boolean init(ElementSaver saver, EnhancedProperties props) {
5552 for (int i = 0; i < readingHooks.length; i++)
5653 readingHooks[i].init(saver, props);
5754 return true;
5855 }
5956
57 @Override
6058 public void onAddNode(Node node) {
6159 for (int i = 0; i < readingHooks.length; i++)
6260 readingHooks[i].onAddNode(node);
6361 }
6462
63 @Override
6564 public void onCoordAddedToWay(Way way, long coordId, Coord co) {
6665 for (int i = 0; i < readingHooks.length; i++)
6766 readingHooks[i].onCoordAddedToWay(way, coordId, co);
6867 }
6968
69 @Override
7070 public void onAddWay(Way way) {
7171 for (int i = 0; i < readingHooks.length; i++)
7272 readingHooks[i].onAddWay(way);
7373 }
7474
75 @Override
7576 public void end() {
7677 for (int i = 0; i < readingHooks.length; i++)
7778 readingHooks[i].end();
5959 * </ul>
6060 * @author WanMil
6161 */
62 public class POIGeneratorHook extends OsmReadingHooksAdaptor {
62 public class POIGeneratorHook implements OsmReadingHooks {
6363 private static final Logger log = Logger.getLogger(POIGeneratorHook.class);
6464
6565 private List<Entry<String,String>> poiPlacementTags;
208208 }
209209
210210 // do not add POIs for polygons created by multipolygon processing
211 if (w.tagIsLikeYes(MultiPolygonRelation.MP_CREATED_TAG)) {
211 if (w.tagIsLikeYes(MultiPolygonRelation.MP_CREATED_TAG_KEY)) {
212212 if (log.isDebugEnabled())
213213 log.debug("MP processed: Do not create POI for", w.toTagString());
214214 continue;
1010 * @author Rene_A
1111 */
1212 public abstract class Relation extends Element {
13 private final List<Map.Entry<String,Element>> elements = new ArrayList<Map.Entry<String,Element>>();
13 private final List<Map.Entry<String,Element>> elements = new ArrayList<>();
1414 // if set, one or more tags were ignored because they are not used in the style or in mkgmap
1515 private boolean tagsIncomplete;
1616
3535 return elements;
3636 }
3737
38 @Override
3839 public String kind() {
3940 return "relation";
4041 }
1919 * This hook applies the relation rules of the style system.
2020 * @author WanMil
2121 */
22 public class RelationStyleHook extends OsmReadingHooksAdaptor {
22 public class RelationStyleHook implements OsmReadingHooks {
2323
2424 private Style style;
2525 private ElementSaver saver;
2626 private NameFinder nameFinder;
2727
28 public RelationStyleHook() {
29 }
30
3128 public boolean init(ElementSaver saver, EnhancedProperties props) {
3229 this.saver = saver;
3330 nameFinder = new NameFinder(props);
34 return super.init(saver, props);
31 return true;
3532 }
3633
3734 public void setStyle(Style style){
4744 ((RestrictionRelation) rel).eval(saver.getBoundingBox());
4845 }
4946 }
50 super.end();
5147 }
52
53
5448
5549 }
3030 * @author Gerd Petermann
3131 *
3232 */
33 public class ResidentialHook extends OsmReadingHooksAdaptor {
33 public class ResidentialHook implements OsmReadingHooks {
3434 private static final Logger log = Logger.getLogger(ResidentialHook.class);
3535
3636
3939 private ElementSaver saver;
4040 private NameFinder nameFinder;
4141
42 @Override
4243 public boolean init(ElementSaver saver, EnhancedProperties props) {
43 if (props.getProperty("residential-hook", true) == false)
44 if (!props.getProperty("residential-hook", true))
4445 return false;
4546 this.nameFinder = new NameFinder(props);
4647 this.saver = saver;
4748 return true;
4849 }
4950
51 @Override
5052 public void end() {
5153 log.info("Starting with residential hook");
5254
5759
5860 // process all nodes that might be converted to a garmin node (tagcount > 0)
5961 for (Node node : saver.getNodes().values()) {
60 if (node.getTagCount() > 0) {
61 if (saver.getBoundingBox().contains(node.getLocation())){
62 processElem(node);
63 }
62 if (node.getTagCount() > 0 && saver.getBoundingBox().contains(node.getLocation())) {
63 processElem(node);
6464 }
6565 }
6666
2525 * @author WanMil
2626 *
2727 */
28 public class RoutingHook extends OsmReadingHooksAdaptor {
28 public class RoutingHook implements OsmReadingHooks {
2929
3030 private final Set<String> usedTags;
3131
3232 public RoutingHook() {
33 usedTags = new HashSet<String>();
33 usedTags = new HashSet<>();
3434 usedTags.add("except");
3535 usedTags.add("restriction");
3636 usedTags.add("restriction:foot");
7373 return props.containsKey("route");
7474 }
7575
76
76 @Override
7777 public Set<String> getUsedTags() {
7878 return usedTags;
7979 }
80
8180
8281 }
5656 * Should pick one that works well and make it the default.
5757 *
5858 */
59 public class SeaGenerator extends OsmReadingHooksAdaptor {
59 public class SeaGenerator implements OsmReadingHooks {
6060 private static final Logger log = Logger.getLogger(SeaGenerator.class);
6161
6262 private boolean generateSeaUsingMP = true;
7070 private void fillQuadTrees() {
7171 final AtomicBoolean isLand = new AtomicBoolean(false);
7272 final AtomicBoolean isSea = new AtomicBoolean(false);
73 TypeResult fakedType = new TypeResult() {
74 @Override
75 public void add(Element el, GType type) {
76 if (log.isDebugEnabled())
77 log.debug(el.getId(),type);
78 if (type.getType() == 0x01) {
79 isLand.set(true);
80 } else if (type.getType() == 0x02) {
81 isSea.set(true);
82 }
73 TypeResult fakedType = (el, type) -> {
74 if (log.isDebugEnabled())
75 log.debug(el.getId(), type);
76 if (type.getType() == 0x01) {
77 isLand.set(true);
78 } else if (type.getType() == 0x02) {
79 isSea.set(true);
8380 }
8481 };
8582 for (Way way : getTileWayMap().values()) {
2424 *
2525 */
2626 public class TagDict{
27 private final static TagDict INSTANCE = new TagDict();
27 private static final TagDict INSTANCE = new TagDict();
2828 private final HashMap<String,Short> map = new HashMap<>();
2929 private final ArrayList<String> list = new ArrayList<>();
3030
6262 }
6363 String s = keyString;
6464 map.put(s, size);
65 //System.out.println(""+x + ":" + s);
6665 list.add(s);
6766 return size;
6867 }
3030 * Use this if you don't want to save the results. Only likely to be
3131 * used for the test cases.
3232 */
33 public static TypeResult NULL_RESULT = new TypeResult() {
34 public void add(Element el, GType type) {
35 }
33 public static TypeResult NULL_RESULT = (el, type) -> {
34 // do nothing
3635 };
3736 }
2929 *
3030 * @author WanMil
3131 */
32 public class UnusedElementsRemoverHook extends OsmReadingHooksAdaptor {
32 public class UnusedElementsRemoverHook implements OsmReadingHooks {
3333 private static final Logger log = Logger.getLogger(UnusedElementsRemoverHook.class);
3434
3535 private ElementSaver saver;
3737 /** node with tags of this list must not be removed */
3838 private List<Entry<String,String>> areasToPoiNodeTags;
3939
40 public UnusedElementsRemoverHook() {
41 }
42
40 @Override
4341 public boolean init(ElementSaver saver, EnhancedProperties props) {
4442 this.saver = saver;
4543
5452 return true;
5553 }
5654
55 @Override
5756 public void end() {
5857 long t1 = System.currentTimeMillis();
5958 log.info("Removing unused elements");
7271 }
7372
7473 // check if the node is within the tile bounding box
75 if (bbox.contains(node.getLocation()) == false) {
74 if (!bbox.contains(node.getLocation())) {
7675 boolean removeNode = true;
7776
7877 for (Entry<String, String> tag : areasToPoiNodeTags) {
7978 // check if the node has a tag used by the POIGeneratorHook
8079 String val = node.getTag(tag.getKey());
81 if (val != null) {
82 if (tag.getValue() == null || val.equals(tag.getValue())) {
83 // the node contains one tag that might be
84 // interesting for the POIGeneratorHook
85 // do not remove it
86 removeNode = false;
87 break;
88 }
80 if (val != null && (tag.getValue() == null || val.equals(tag.getValue()))) {
81 // the node contains one tag that might be
82 // interesting for the POIGeneratorHook
83 // do not remove it
84 removeNode = false;
85 break;
8986 }
9087 }
9188 if (removeNode) {
125122 if (bbox.contains(c)) {
126123 coordInBbox = true;
127124 break;
128 } else if (prevC != null) {
129 // check if the line intersects the bounding box
130 if (bboxRect.intersectsLine(prevC.getLongitude(), prevC.getLatitude(), c.getLongitude(), c.getLatitude())) {
131 if (log.isDebugEnabled()) {
132 log.debug("Intersection!");
133 log.debug("Bbox:", bbox);
134 log.debug("Way coords:", prevC, c);
135 }
136 coordInBbox = true;
137 break;
125 } else if (prevC != null &&
126 bboxRect.intersectsLine(prevC.getLongitude(), prevC.getLatitude(), c.getLongitude(), c.getLatitude())) {
127 // the line intersects the bounding box
128 if (log.isDebugEnabled()) {
129 log.debug("Intersection!");
130 log.debug("Bbox:", bbox);
131 log.debug("Way coords:", prevC, c);
138132 }
133 coordInBbox = true;
134 break;
139135 }
140136
141137 prevC = c;
142138 }
143 if (coordInBbox==false) {
139 if (!coordInBbox) {
144140 // no coord of the way is within the bounding box
145141 // check if the way possibly covers the bounding box completely
146142 Area wayBbox = Area.getBBox(way.getPoints());
233233
234234 if(points.size() < 3 || !points.get(0).equals(points.get(points.size() - 1)))
235235 return false;
236 if (points.get(0).highPrecEquals(points.get(points.size() - 1)) == false){
236 if (!points.get(0).highPrecEquals(points.get(points.size() - 1))) {
237237 log.error("Way.clockwise was called for way that is not closed in high precision");
238238 }
239239
2020 private String id; // the id of the OSM relation (was kept in tag "mkgmap:boundaryid")
2121
2222 private final Tags tags;
23 private transient Area area;
23 private Area area;
2424
2525 public Boundary(Area area, Tags tags, String id) {
2626 this.area = new Area(area);
7171 private final Node root;
7272 // the bounding box of the quadtree
7373 private final Rectangle bbox;
74 private final String bbox_key;
74 private final String bboxKey;
7575
7676 // tags that can be returned in the get method
77 public final static String[] mkgmapTagsArray = {
77 public static final String[] mkgmapTagsArray = {
7878 "mkgmap:admin_level1",
7979 "mkgmap:admin_level2",
8080 "mkgmap:admin_level3",
9090 "mkgmap:other" // for use in residential hook
9191 };
9292 // 11: the position of "mkgmap:postcode" in the above array
93 public final static short POSTCODE_ONLY = 1 << 11;
93 public static final short POSTCODE_ONLY = 1 << 11;
9494 // 12: the position of "mkgmap:other" in the above array
95 public final static short NAME_ONLY = 1 << 12;
95 public static final short NAME_ONLY = 1 << 12;
9696
9797 /**
9898 * Create a quadtree with the data in an open stream.
111111 this.bbox = new Rectangle(fileBbox.getMinLong(), fileBbox.getMinLat(),
112112 fileBbox.getMaxLong() - fileBbox.getMinLong(), fileBbox.getMaxLat()
113113 - fileBbox.getMinLat());
114 this.bbox_key = BoundaryUtil.getKey(this.bbox.y, this.bbox.x);
114 this.bboxKey = BoundaryUtil.getKey(this.bbox.y, this.bbox.x);
115115 root = new Node(this.bbox);
116116
117117 readStreamQuadTreeFormat(inpStream,searchBbox);
132132 this.bbox = new Rectangle(givenBbox.getMinLong(), givenBbox.getMinLat(),
133133 givenBbox.getMaxLong() - givenBbox.getMinLong(),
134134 givenBbox.getMaxLat() - givenBbox.getMinLat());
135 this.bbox_key = BoundaryUtil.getKey(this.bbox.y, this.bbox.x);
135 this.bboxKey = BoundaryUtil.getKey(this.bbox.y, this.bbox.x);
136136
137137 root = new Node(this.bbox);
138138 // extract the location relevant tags
139139 preparedLocationInfo = preparer.getPreparedLocationInfo(boundaries);
140 if (boundaries == null || boundaries.size() == 0)
140 if (boundaries == null || boundaries.isEmpty())
141141 return;
142142
143143
210210 * @param other the other instance of BoundaryQuadTree
211211 */
212212 public void merge(BoundaryQuadTree other){
213 if (bbox.equals(other.bbox) == false){
213 if (!bbox.equals(other.bbox)){
214214 log.error("Cannot merge tree with different bounding box");
215215 return;
216216 }
217217 for (Entry <String, BoundaryLocationInfo> entry : other.preparedLocationInfo.entrySet()){
218 if (this.preparedLocationInfo.containsKey(entry.getKey()) == false){
218 if (!this.preparedLocationInfo.containsKey(entry.getKey())){
219219 this.preparedLocationInfo.put(entry.getKey(),entry.getValue());
220220 }
221221 }
222222 // add the others tags
223223 for (Entry <String, Tags> entry : other.boundaryTags.entrySet()){
224 if (this.boundaryTags.containsKey(entry.getKey()) == false){
224 if (!this.boundaryTags.containsKey(entry.getKey())){
225225 this.boundaryTags.put(entry.getKey(),entry.getValue());
226226 }
227227 }
362362 refs = null;
363363 Area area = BoundaryUtil.readAreaAsPath(inpStream);
364364
365 if (area != null && area.isEmpty() == false)
365 if (area != null && !area.isEmpty())
366366 root.add(area, refs, id, treePath);
367367 else {
368368 log.warn(refs,id,treePath,"invalid or empty or too small area");
378378 }
379379 } catch (EOFException exp) {
380380 // it's always thrown at the end of the file
381 // log.error("Got EOF at the end of the file");
382381 }
383382 }
384383
464463 * Sample: r1184826;6:r62579;4:r62372;2:r51477
465464 */
466465 private String getBoundaryNames(Coord co) {
467 if (this.bounds.contains(co) == false)
466 if (!this.bounds.contains(co))
468467 return null;
469468 if (isLeaf){
470 if (nodes == null || nodes.size() == 0)
469 if (nodes == null || nodes.isEmpty())
471470 return null;
472471 int lon = co.getLongitude();
473472 int lat = co.getLatitude();
474 for (NodeElem nodeElem: nodes){
475 if (nodeElem.tagMask > 0){
476 if (nodeElem.getArea().contains(lon,lat)){
477 String res = new String (nodeElem.boundaryId);
478 if (nodeElem.locationDataSrc != null)
479 res += ";" + nodeElem.locationDataSrc;
480 return res;
481 }
473 for (NodeElem nodeElem : nodes) {
474 if (nodeElem.tagMask > 0 && nodeElem.getArea().contains(lon, lat)) {
475 if (nodeElem.locationDataSrc != null)
476 return nodeElem.boundaryId + ";" + nodeElem.locationDataSrc;
477 return nodeElem.boundaryId;
482478 }
483479 }
484480 }
499495 * The returned Tags must not be modified by the caller.
500496 */
501497 private Tags get(Coord co/*, String treePath*/){
502 if (this.bounds.contains(co) == false)
498 if (!this.bounds.contains(co))
503499 return null;
504500 if (isLeaf){
505 if (nodes == null || nodes.size() == 0)
501 if (nodes == null || nodes.isEmpty())
506502 return null;
507503 int lon = co.getLongitude();
508504 int lat = co.getLatitude();
509 for (NodeElem nodeElem: nodes){
510 if (nodeElem.tagMask > 0){
511 if (nodeElem.getArea().contains(lon,lat)){
512 return nodeElem.locTags;
513 }
505 for (NodeElem nodeElem : nodes) {
506 if (nodeElem.tagMask > 0 && nodeElem.getArea().contains(lon, lat)) {
507 return nodeElem.locTags;
514508 }
515509 }
516510 }
535529 if (treePath.equals(DEBUG_TREEPATH)){
536530 nodeElem.saveGPX(prefix,treePath);
537531 }
538 String res = new String();
539 for (int i = mkgmapTagsArray.length-1; i >= 0 ; --i){
540 String tagVal = nodeElem.locTags.get(mkgmapTagsArray[i] );
541 if (tagVal != null){
542 res += i+1 + "=" + tagVal + ";";
543 }
544 }
545 System.out.println(prefix + " " + treePath + " " + n + ":" + nodeElem.boundaryId + " " + nodeElem.tagMask + " " + res );
532 StringBuilder sb = new StringBuilder();
533 for (int i = mkgmapTagsArray.length - 1; i >= 0; --i) {
534 String tagVal = nodeElem.locTags.get(mkgmapTagsArray[i]);
535 if (tagVal != null) {
536 sb.append(i + 1).append('=').append(tagVal).append(';');
537 }
538 }
539 System.out.println(prefix + " " + treePath + " " + n + ":" + nodeElem.boundaryId + " "
540 + nodeElem.tagMask + " " + sb.toString());
546541 ++n;
547542 }
548543 }
579574 tmpNodeElem.saveGPX("intersection_rect",treePath);
580575 }
581576 }
582 if (DEBUG){
583 if (!ok){
584 for (NodeElem nodeElem: nodes){
585 nodeElem.saveGPX("not_distinct",treePath);
586 }
577 if (DEBUG) {
578 if (!ok) {
579 for (NodeElem nodeElem : nodes) {
580 nodeElem.saveGPX("not_distinct", treePath);
581 }
587582 }
588583 }
589584 return ok;
600595 private void add(Area area, String refs, String boundaryId, String treePath){
601596 Node node = this;
602597 String path = treePath;
603 while(path.isEmpty() == false){
598 while (!path.isEmpty()) {
604599 int idx = Integer.parseInt(path.substring(0, 1));
605600 path = path.substring(1);
606601 if (node.childs == null)
686681 private Area getCoveredArea(Integer admLevel, String treePath){
687682 HashMap<String,List<Area>> areas = new HashMap<>();
688683 this.getAreas(areas, treePath, admLevel);
689 if (areas.isEmpty() == false){
684 if (!areas.isEmpty()) {
690685 Path2D.Double path = new Path2D.Double(PathIterator.WIND_NON_ZERO, 1024 * 1024);
691686 for (Entry <String, List<Area>> entry : areas.entrySet()){
692687 for (Area area: entry.getValue()){
693688 path.append(area, false);
694689 }
695690 }
696 Area combinedArea = new Area(path);
697 return combinedArea;
691 return new Area(path);
698692 }
699693 return new Area();
700694 }
707701 */
708702 private void getAreas(Map<String, List<Area>> areas, String treePath, Integer admLevel){
709703 if (!this.isLeaf ){
710 for (int i = 0; i < 4; i++){
711 childs[i].getAreas(areas, treePath+i, admLevel);
704 for (int i = 0; i < 4; i++) {
705 childs[i].getAreas(areas, treePath + i, admLevel);
712706 }
713707 return;
714708 }
715 if (nodes == null || nodes.size() == 0)
709 if (nodes == null || nodes.isEmpty())
716710 return;
717711
718712 Short testMask = null;
722716 String id = nodeElem.boundaryId;
723717 if (testMask != null && (nodeElem.tagMask & testMask) == 0)
724718 continue;
725 List<Area> aList = areas.get(id);
726719 Area a = new Area(nodeElem.getArea());
727 if (aList == null){
728 aList = new ArrayList<>(4);
729 areas.put(id, aList);
730 }
731 aList.add(a);
720 areas.computeIfAbsent(id, k -> new ArrayList<>(4)).add(a);
732721 if (testMask != null)
733722 continue;
734723
742731 continue;
743732 }
744733 id = relParts[1];
745 aList = areas.get(id);
746734 a = new Area(nodeElem.getArea());
747 if (aList == null){
748 aList = new ArrayList<>(4);
749 areas.put(id, aList);
750 }
751 aList.add(a);
735 areas.computeIfAbsent(id, k -> new ArrayList<>(4)).add(a);
752736 }
753737 }
754738 }
763747 * @param treePath Identifies the position in the tree
764748 */
765749 private void makeDistinct(String treePath){
766 if (isLeaf == false || nodes == null || nodes.size() <= 1)
750 if (!isLeaf || nodes == null || nodes.size() <= 1)
767751 return;
768752 if (DEBUG){
769753 printNodes("start", treePath);
787771 // detect intersection of areas, merge tag info
788772 for (int i=0; i < nodes.size(); i++){
789773 NodeElem toAdd = nodes.get(i);
790 if (DEBUG){
791 if (treePath.equals(DEBUG_TREEPATH) || DEBUG_TREEPATH.equals("all")){
792 for (NodeElem nodeElem: reworked){
793 nodeElem.saveGPX("debug"+i,treePath);
794 }
795 }
796 }
797 for (int j=0; j < reworked.size(); j++){
798 if (toAdd.isValid() == false)
774 if (DEBUG) {
775 if (treePath.equals(DEBUG_TREEPATH) || DEBUG_TREEPATH.equals("all")) {
776 for (NodeElem nodeElem : reworked) {
777 nodeElem.saveGPX("debug" + i, treePath);
778 }
779 }
780 }
781 for (int j = 0; j < reworked.size(); j++) {
782 if (!toAdd.isValid())
799783 break;
800784 NodeElem currElem = reworked.get(j);
801785 if (currElem.srcPos == i || currElem.getArea().isEmpty())
818802 Area toAddMinusCurr = new Area(toAdd.getArea());
819803 toAddMinusCurr.subtract(currElem.getArea());
820804
821 if (toAddMinusCurr.isEmpty()){
805 if (toAddMinusCurr.isEmpty() &&
806 toAdd.tagMask == POSTCODE_ONLY) {
822807 // toadd is fully covered by curr
823 if (toAdd.tagMask == POSTCODE_ONLY){
824 // if we get here, toAdd has only zip code that is already known
825 // in larger or equal area of currElem
826 toAdd.getArea().reset(); // ignore this
827 break;
828 }
808 // if we get here, toAdd has only zip code that is already known
809 // in larger or equal area of currElem
810 toAdd.getArea().reset(); // ignore this
811 break;
829812 }
830813
831814 // test if toAdd contains usable tag(s)
832815 String chkMsg = currElem.checkAddTags(toAdd, bounds);
833816 // warning: intersection of areas with equal levels
834 if (chkMsg != null){
835 if (DEBUG){
836 // save debug GPX for areas that wiil
837 // appear in warning message below
838 toAdd.saveGPX("warn_toAdd",treePath);
839 currElem.saveGPX("warn_curr",treePath);
817 if (chkMsg != null) {
818 if (DEBUG) {
819 // save debug GPX for areas that wiil
820 // appear in warning message below
821 toAdd.saveGPX("warn_toAdd", treePath);
822 currElem.saveGPX("warn_curr", treePath);
840823 }
841824 log.warn(chkMsg);
842825 }
876859 }
877860 nodes = reworked;
878861 // free memory for nodes with empty or too small areas
879 removeEmptyAreas(treePath);
862 removeEmptyAreas();
880863
881864 long dt = System.currentTimeMillis()-t1;
882865 if (dt > 1000){
883 log.info(bbox_key, " : makeDistinct required long time:", dt, "ms");
866 log.info(bboxKey, " : makeDistinct required long time:", dt, "ms");
884867 }
885868 if (DEBUG)
886869 printNodes("end", treePath);
921904 NodeElem lastNode = nodes.get(nodes.size()-1);
922905 NodeElem prevNode = nodes.get(nodes.size()-2);
923906 // don't merge admin_level tags into zip-code only boundary
924 if (prevNode.tagMask != POSTCODE_ONLY && lastNode.getArea().isRectangular() && prevNode.getArea().isRectangular()){
907 if (prevNode.tagMask != POSTCODE_ONLY && lastNode.getArea().isRectangular()
908 && prevNode.getArea().isRectangular()) {
925909 // two areas are rectangles, it is likely that they are equal to the bounding box
926910 // In this case we add the tags to the existing area instead of creating a new one
927911 if (prevNode.getArea().equals(lastNode.getArea())){
937921 * The mergeBoundaries() algorithm can create empty
938922 * areas (points, lines, or extremely small intersections).
939923 * These are removed here.
940 * @param treePath
941 */
942 private void removeEmptyAreas(String treePath){
924 */
925 private void removeEmptyAreas() {
943926 for (int j = nodes.size()-1; j >= 0 ; j--){
944927 boolean removeThis = false;
945928 NodeElem chkRemove = nodes.get(j);
946 if (chkRemove.isValid() == false)
929 if (!chkRemove.isValid())
947930 removeThis = true;
948 else if (this.bbox.intersects(chkRemove.getArea().getBounds2D()) == false){
931 else if (!this.bbox.intersects(chkRemove.getArea().getBounds2D())) {
949932 // we might get here because of errors in java.awt.geom.Area
950933 // sometimes, Area.subtract() seems to produce an area which
951934 // lies outside of original areas
952935 removeThis = true;
953 }else if (!isWritable(chkRemove.getArea())){
936 } else if (!isWritable(chkRemove.getArea())) {
954937 removeThis = true;
955938 }
956 if (removeThis){
939 if (removeThis) {
957940 nodes.remove(j);
958941 }
959942 }
986969 * distribute the data.
987970 */
988971 private void split(String treePath){
989 if (isLeaf == true){
972 if (isLeaf) {
990973 if (nodes == null)
991974 return;
992975 if (DEBUG){
1000983 return ;
1001984 }
1002985
1003 // mergeLastRectangles();
1004986 allocChilds();
1005 for (NodeElem nodeElem: nodes){
987 for (NodeElem nodeElem : nodes) {
1006988 Rectangle shapeBBox = nodeElem.shape.getBounds();
1007 for (int i = 0; i < 4; i++){
989 for (int i = 0; i < 4; i++) {
1008990 if (childs[i].bbox.intersects(shapeBBox))
1009991 childs[i].add(nodeElem.shape, nodeElem.boundaryId, nodeElem.locationDataSrc, DO_CLIP);
1010992 }
10871069 * @return false if either the area is not usable or
10881070 * the tags should be ignored.
10891071 */
1090 private boolean isValid(){
1072 private boolean isValid() {
10911073 if (tagMask == 0)
10921074 return false;
10931075 Area checkArea = getArea();
1094 if (checkArea == null || checkArea.isEmpty()
1095 || checkArea.getBounds2D().getWidth() <= BoundaryUtil.MIN_DIMENSION && checkArea.getBounds2D().getHeight() <= BoundaryUtil.MIN_DIMENSION)
1096 return false;
1097 return true;
1098 }
1076 return checkArea != null && !checkArea.isEmpty()
1077 && (checkArea.getBounds2D().getWidth() > BoundaryUtil.MIN_DIMENSION
1078 || checkArea.getBounds2D().getHeight() > BoundaryUtil.MIN_DIMENSION);
1079 }
1080
10991081 /**
11001082 * Add the location relevant data of another NodeElem
11011083 * @param toAdd the other NodeElem
11021084 */
1103 private void addLocInfo(NodeElem toAdd){
1085 private void addLocInfo(NodeElem toAdd) {
11041086 addLocationDataString(toAdd);
1105 addMissingTags(toAdd.locTags);
1087 addMissingTags(toAdd.locTags);
11061088 tagMask |= toAdd.tagMask;
11071089 }
11081090
11171099 locTags = new Tags();
11181100 tagMask = 0;
11191101 BoundaryLocationInfo bInfo = preparedLocationInfo.get(boundaryId);
1120 if (bInfo == null){
1102 if (bInfo == null) {
11211103 log.error("unknown boundaryId " + boundaryId);
11221104 return;
11231105 }
1124 if (bInfo.getZip() != null){
1125 locTags.put("mkgmap:postcode",bInfo.getZip());
1126 }
1127
1128 if (bInfo.getAdmLevel() != BoundaryLocationPreparer.UNSET_ADMIN_LEVEL){
1129 locTags.put(BoundaryQuadTree.mkgmapTagsArray[bInfo.getAdmLevel()-1], bInfo.getName());
1106 if (bInfo.getZip() != null) {
1107 locTags.put("mkgmap:postcode", bInfo.getZip());
1108 }
1109
1110 if (bInfo.getAdmLevel() != BoundaryLocationPreparer.UNSET_ADMIN_LEVEL) {
1111 locTags.put(BoundaryQuadTree.mkgmapTagsArray[bInfo.getAdmLevel() - 1], bInfo.getName());
11301112 }
11311113 if (locTags.size() == 0 && bInfo.getName() != null) {
11321114 locTags.put("mkgmap:other", bInfo.getName());
11331115 }
1134 if (locationDataSrc != null && locationDataSrc.isEmpty() == false){
1116 if (locationDataSrc != null && !locationDataSrc.isEmpty()) {
11351117 // the common format of refInfo is
11361118 // 2:r19884;4:r20039;6:r998818
11371119 String[] relBounds = locationDataSrc.split(Pattern.quote(";"));
11541136 }
11551137 String addZip = addInfo.getZip();
11561138
1157 if (addAdmName != null){
1158 if (locTags.get(BoundaryQuadTree.mkgmapTagsArray[addAdmLevel-1]) == null)
1159 locTags.put(BoundaryQuadTree.mkgmapTagsArray[addAdmLevel-1], addAdmName);
1160 }
1161 if (addZip != null){
1162 if (locTags.get("mkgmap:postcode") == null)
1163 locTags.put("mkgmap:postcode", addZip);
1139 if (addAdmName != null && locTags.get(BoundaryQuadTree.mkgmapTagsArray[addAdmLevel - 1]) == null) {
1140 locTags.put(BoundaryQuadTree.mkgmapTagsArray[addAdmLevel - 1], addAdmName);
1141 }
1142 if (addZip != null && locTags.get("mkgmap:postcode") == null) {
1143 locTags.put("mkgmap:postcode", addZip);
11641144 }
11651145 }
11661146 }
12911271 String errMsg = null;
12921272 int errAdmLevel = 0;
12931273 // case c) toAdd area is fully covered by currElem area
1294 for (int k = 0; k < mkgmapTagsArray.length; k++){
1274 for (int k = 0; k < mkgmapTagsArray.length; k++) {
12951275 int testMask = 1 << k;
1296 if ((testMask & other.tagMask) != 0 && (this.tagMask & testMask) != 0){
1297 if (testMask == POSTCODE_ONLY){
1276 if ((testMask & other.tagMask) != 0 && (this.tagMask & testMask) != 0) {
1277 if (testMask == POSTCODE_ONLY) {
12981278 String zipKey = mkgmapTagsArray[k];
1299 if (other.locTags.get(zipKey).equals(this.locTags.get(zipKey)) == false){
1279 if (!other.locTags.get(zipKey).equals(this.locTags.get(zipKey))) {
13001280 errMsg = "different " + zipKey;
13011281 break;
13021282 }
13031283 } else if (testMask == NAME_ONLY) {
13041284 break; // happens with ResidentialHook, silently ignore it
13051285 } else {
1306 errAdmLevel = k+1;
1307 errMsg = new String ("same admin_level (" + errAdmLevel + ")");
1286 errAdmLevel = k + 1;
1287 errMsg = "same admin_level (" + errAdmLevel + ")";
13081288 break;
13091289 }
13101290 }
13111291 }
1312 if (errMsg != null){
1292 if (errMsg != null) {
13131293 String url = bounds.getCenter().toOSMURL() + "&";
13141294 url += (other.boundaryId.startsWith("w")) ? "way" : "relation";
13151295 url += "=" + other.boundaryId.substring(1);
1316 //http://www.openstreetmap.org/?lat=49.394988&lon=6.551425&zoom=18&layers=M&relation=122907
1317 errMsg= "incorrect data: " + url + " intersection of boundaries with " + errMsg + " " + other.boundaryId + " " + this.boundaryId + " " ;
1296 // http://www.openstreetmap.org/?lat=49.394988&lon=6.551425&zoom=18&layers=M&relation=122907
1297 errMsg = "incorrect data: " + url + " intersection of boundaries with " + errMsg + " "
1298 + other.boundaryId + " " + this.boundaryId + " ";
13181299 if (errAdmLevel != 0 && this.locationDataSrc != null)
13191300 errMsg += this.locationDataSrc;
13201301 }
13911372 return false;
13921373 Path2D.Double path = new Path2D.Double(area);
13931374 Area testArea = new Area(path);
1394 if (testArea.isEmpty()){
1395 return false;
1396 }
1397 return true;
1375 return !testArea.isEmpty();
13981376 }
13991377 }
1515 */
1616 package uk.me.parabola.mkgmap.reader.polish;
1717
18 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
19
20
2118 import java.io.BufferedReader;
2219 import java.io.FileNotFoundException;
2320 import java.io.IOException;
3734 import java.util.Map;
3835
3936 import uk.me.parabola.imgfmt.FormatException;
37 import uk.me.parabola.imgfmt.MapFailedException;
4038 import uk.me.parabola.imgfmt.Utils;
4139 import uk.me.parabola.imgfmt.app.Area;
4240 import uk.me.parabola.imgfmt.app.Coord;
5351 import uk.me.parabola.mkgmap.general.MapElement;
5452 import uk.me.parabola.mkgmap.general.MapLine;
5553 import uk.me.parabola.mkgmap.general.MapPoint;
54 import uk.me.parabola.mkgmap.general.MapRoad;
5655 import uk.me.parabola.mkgmap.general.MapShape;
5756 import uk.me.parabola.mkgmap.general.ZipCodeInfo;
5857 import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
5958 import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
59 import uk.me.parabola.mkgmap.reader.osm.GType;
6060 import uk.me.parabola.mkgmap.reader.osm.GeneralRelation;
6161 import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
6262 import uk.me.parabola.mkgmap.reader.osm.Way;
112112
113113 private boolean havePolygon4B;
114114
115 /** if false, assume that lines with routable types are roads and create corresponding NET data */
116 private boolean routing;
117 private int roadIdGenerated;
118
115119 // Use to decode labels if they are not in cp1252
116120 private CharsetDecoder dec;
117121
118 Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>();
119122 public boolean isFileSupported(String name) {
120123 // Supported if the extension is .mp
121124 return name.endsWith(".mp") || name.endsWith(".MP") || name.endsWith(".mp.gz");
162165
163166 if (addBackground && !havePolygon4B)
164167 addBackground();
165 coordMap = null;
166168 }
167169
168170 public LevelInfo[] mapLevels() {
234236 section = S_RESTRICTION;
235237 }
236238 else
237 System.out.println("Ignoring unrecognised section: " + name);
239 log.info("Ignoring unrecognised section: " + name);
238240 }
239241
240242 /**
248250 break;
249251
250252 case S_POINT:
251 if(extraAttributes != null && point.hasExtendedType())
252 point.setExtTypeAttributes(makeExtTypeAttributes());
253 mapper.addToBounds(point.getLocation());
254 mapper.addPoint(point);
253 if (point.getLocation() != null) {
254 if(extraAttributes != null && point.hasExtendedType())
255 point.setExtTypeAttributes(makeExtTypeAttributes());
256 mapper.addToBounds(point.getLocation());
257 mapper.addPoint(point);
258 } else {
259 log.error("skipping POI without coordinates near line " + lineNo );
260 }
261
255262 break;
256263 case S_POLYLINE:
257264 if (!lineStringMap.isEmpty()) {
262269 setResolution(origPolyline, level);
263270 for (List<Coord> points : entry.getValue()) {
264271 polyline = origPolyline.copy();
272 if (!routing && GType.isRoutableLineType(polyline.getType())) {
273 roadHelper.setRoadId(++roadIdGenerated);
274 }
265275 if (roadHelper.isRoad() && level == 0) {
266276 polyline.setPoints(points);
267 mapper.addRoad(roadHelper.makeRoad(polyline));
277 MapRoad r = roadHelper.makeRoad(polyline);
278 if (!routing) {
279 r.skipAddToNOD(true);
280 }
281 mapper.addRoad(r);
268282 }
269283 else {
270284 if(extraAttributes != null && polyline.hasExtendedType())
411425 private void point(String name, String value) {
412426 if (name.equals("Type")) {
413427 int type = Integer.decode(value);
428 if (type <= 0xff)
429 type <<= 8;
414430 point.setType(type);
415431 } else if (name.equals("SubType")) {
416432 int subtype = Integer.decode(value);
417433 int type = point.getType();
418 if (type <= 0xff)
419 point.setType((type << 8) | subtype);
434 point.setType(type | subtype);
420435 } else if (name.startsWith("Data") || name.startsWith("Origin")) {
421436 Coord co = makeCoord(value);
422437 setResolution(point, name);
451466 fixElevation();
452467 }
453468 } else if (name.equals("RoadID")) {
469 if (!routing && roadIdGenerated > 0)
470 throw new MapFailedException("found RoadID without Routing=Y in [IMG ID] section in line " + lineNo);
454471 roadHelper.setRoadId(Integer.parseInt(value));
455472 } else if (name.startsWith("Nod")) {
456473 roadHelper.addNode(value);
476493 public Numbers parseNumbers(String spec) {
477494 Numbers nums = new Numbers();
478495 String[] strings = spec.split(",");
479 nums.setNodeNumber(Integer.parseInt(strings[0]));
496 nums.setPolishIndex(Integer.parseInt(strings[0]));
480497 NumberStyle numberStyle = NumberStyle.fromChar(strings[1]);
481498 int start = Integer.parseInt(strings[2]);
482499 int end = Integer.parseInt(strings[3]);
821838 } else if ("Numbering".equals(name)) {
822839 // ignore
823840 } else if ("Routing".equals(name)) {
824 // ignore
841 if ("Y".equals(value)) {
842 routing = true; // don't generate roadid
843 }
825844 } else if ("CountryName".equalsIgnoreCase(name)) {
826845 defaultCountry = value;
827846 } else if ("RegionName".equalsIgnoreCase(name)) {
828847 defaultRegion = value;
829848 } else {
830 System.out.println("'IMG ID' section: ignoring " + name + " " + value);
849 log.info("'IMG ID' section: ignoring " + name + " " + value);
831850 }
832851
833852 }
847866
848867 Double f1 = Double.valueOf(fields[i]);
849868 Double f2 = Double.valueOf(fields[i+1]);
850 Coord co = new Coord(f1, f2);
851 long key = Utils.coord2Long(co);
852 Coord co2 = coordMap.get(key);
853 if (co2 != null)
854 return co2;
855 coordMap.put(key, co);
856 return co;
869 return new Coord(f1, f2);
857870 }
858871
859872 private ExtTypeAttributes makeExtTypeAttributes() {
934947 } else if (name.equalsIgnoreCase("RestrParam")) {
935948 restriction.setExceptMask(getRestrictionExceptionMask(value));
936949 } else if (name.equalsIgnoreCase("Time")) {
937 // Do nothing for now
950 log.info("Time in restriction definition is ignored " + restriction);
938951 }
939952 }
940953 } catch (NumberFormatException ex) { // This exception means that this restriction is not properly defined.
4747
4848 @Override
4949 public String toString() {
50 return "TurnRestriction" + trafficNodes;
50 return "TurnRestriction" + Arrays.toString(trafficNodes);
5151 }
5252
5353 public void setTrafficPoints(String idsList) {
1919 import java.util.HashMap;
2020 import java.util.List;
2121 import java.util.Map;
22 import java.util.SortedMap;
23 import java.util.TreeMap;
2224
2325 import uk.me.parabola.imgfmt.MapFailedException;
2426 import uk.me.parabola.imgfmt.app.Coord;
2527 import uk.me.parabola.imgfmt.app.CoordNode;
2628 import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits;
27 import uk.me.parabola.imgfmt.app.net.NumberStyle;
2829 import uk.me.parabola.imgfmt.app.net.Numbers;
2930 import uk.me.parabola.log.Logger;
3031 import uk.me.parabola.mkgmap.general.MapLine;
5455 private boolean toll;
5556
5657 private byte mkgmapAccess;
57 private List<Numbers> numbers;
58 private SortedMap<Integer, Numbers> numbersMap;
5859
5960 public RoadHelper() {
6061 clear();
6869 roadClass = 0;
6970 oneway = false;
7071 toll = false;
71 numbers = null;
72 numbersMap = null;
7273 }
7374
7475 public void setRoadId(int roadId) {
9798 roadClass = 0;
9899 if (roadClass > 4)
99100 roadClass = 4;
100 oneway = (f.length > 2) ? Integer.parseInt(f[2]) > 0: false;
101 toll = (f.length > 3) ? Integer.parseInt(f[3]) > 0: false;
101 oneway = f.length > 2 && Integer.parseInt(f[2]) > 0;
102 toll = f.length > 3 && Integer.parseInt(f[3]) > 0;
102103 byte noAccess = 0;
103104 for (int j = 0; j < f.length - 4; j++){
104105 if (Integer.parseInt(f[4+j]) == 0)
133134 road.setToll();
134135 road.setAccess(mkgmapAccess);
135136
136 if (numbers != null && !numbers.isEmpty()) {
137 convertNodesForHouseNumbers(road);
138 road.setNumbers(numbers);
139 }
140
141137 List<Coord> points = road.getPoints();
142
143138 for (NodeIndex ni : nodes) {
144139 int n = ni.index;
145140 if (log.isDebugEnabled())
160155 log.warn("Inconsistant node ids");
161156 }
162157 }
158 if (numbersMap != null) {
159 if (numbersMap.values().stream().anyMatch(n -> !n.isEmpty())) {
160 convertNodesForHouseNumbers(road);
161 } else {
162 numbersMap = null;
163 }
164 }
163165
164166 return road;
165167 }
166168
167169 /**
168 * Make sure that each node that is referenced by the house
170 * Make sure that each node which is referenced by the house
169171 * numbers is a number node. Some of them will later be changed
170172 * to routing nodes.
171173 * Only called if numbers is non-null and not empty.
172174 */
173175 private void convertNodesForHouseNumbers(MapRoad road) {
174 int rNodNumber = 0;
175 for (Numbers n : numbers) {
176 int node = n.getNodeNumber();
177 n.setIndex(rNodNumber++);
178 road.getPoints().get(node).setNumberNode(true);
179 }
176 List<Coord> points = road.getPoints();
177 if (points.isEmpty())
178 return;
179 // make sure all number nodes are marked as such
180 for (Integer idx : numbersMap.keySet()) {
181 if (idx < 0 || idx >= points.size()) {
182 throw new MapFailedException("bad number node index " + idx + " in road id " + roadId);
183 }
184 road.getPoints().get(idx).setNumberNode(true);
185 }
186
187 points.get(0).setNumberNode(true);
188 int roadNodeNumber = 0;
189 for (int i = 0; i < points.size(); i++) {
190 if (points.get(i).isNumberNode()) {
191 Numbers nums = numbersMap.get(i);
192 if (nums != null) {
193 // we have numbers for this node
194 nums.setIndex(roadNodeNumber);
195 } else {
196 // no numbers given, default is the empty interval (N,-1,-1,N,-1,-1)
197 }
198 roadNodeNumber++;
199 }
200 }
201 road.setNumbers(new ArrayList<>(numbersMap.values()));
180202 }
181203
182204 public boolean isRoad() {
188210 }
189211
190212 public void addNumbers(Numbers nums) {
191 if (numbers == null)
192 numbers = new ArrayList<>();
193 if (nums.getLeftNumberStyle() != NumberStyle.NONE || nums.getRightNumberStyle() != NumberStyle.NONE)
194 numbers.add(nums);
213 if (numbersMap == null)
214 numbersMap = new TreeMap<>();
215 numbersMap.put(nums.getPolishIndex(),nums);
195216 }
196217
197218 private static class NodeIndex {
3838 * variables BASE_LAT and BASE_LONG set to something just SW of where you
3939 * are then the map generated will be located near where you are. Otherwise
4040 * the default location is at (51.7, 0.24).
41 * Options --x-base-lat and --x-base-long have the same effect.
41 * Options --x-base-lat and --x-base-long have the same effect.
42 *
43 * The map doesn't have a standard background and so flags have to be set to
44 * say it is transparent. This is done in ./ElementTestDataSource.java
4245 *
4346 * To run, something like:
44 * java -jar mkgmap.jar --gmapsupp test-map:all-elements ...
47 * java -jar mkgmap/mkgmap.jar --verbose --gmapsupp --order-by-decreasing-area test-map:all-elements
4548 *
4649 * You can then use the find facility of your GPS to
4750 * show the near-by points. When viewing a category the menu key will allow
9699
97100 drawTestMap(mapper, baseLat, baseLong, false);
98101 // do same again but on different background without labels
102 // Some devices will tell you the default name for the selected object if it is unnamed
99103 baseLat += (MAX_POINT_SUB_TYPE + 4) * ELEMENT_SPACING; // assume taller than lines and areas
100104 drawBackground(mapper, baseLat, baseLong, MAX_POINT_SUB_TYPE + 3, MAX_POINT_TYPE + MAX_LINE_TYPE_X + MAX_SHAPE_TYPE_X + 4);
101105 drawTestMap(mapper, baseLat, baseLong, true);
102106 }
103107
104 private void drawBackground(MapCollector mapper, double startLat, double startLong, int nUp, int nAcross) {
108 private static String makeName(int type, boolean pointType) {
109 return 'x' + String.format(pointType ? "%04x" : "%02x", type);
110 }
111
112 private void drawBackground(MapCollector mapper, double startLat, double startLong, int nUp, int nAcross) {
105113 MapShape shape = new MapShape();
106 int type = 0x1b; // Area - Green // 0x4d; // glacier-white
114 int type = 0x1b; // Area - Green. NB default _draworder of this is low on some devices
107115 shape.setMinResolution(10);
108116 shape.setName("background");
109117
159167 double lat = slat + 0.004;
160168 double lon = slon + 0.002;
161169
162 for (int maintype = 0; maintype <= MAX_POINT_TYPE; maintype++) {
163 // for (int subtype = 0; subtype <= MAX_POINT_SUB_TYPE; subtype++) {
164 for (int subtype = -1; subtype <= MAX_POINT_SUB_TYPE; subtype++) {
165 // if maintype is zero, the subtype will be treated as the type
166 // use subtype -1 to indicate no subtype and draw, say
167 // point 0x23 under 0x2300 to check they are the same
168 // The zero column is just for just to see 0x00
169 int type; // = (maintype << 8) + subtype;
170 if (subtype < 0)
171 type = maintype;
172 else
173 type = (maintype << 8) + subtype;
170 for (int maintype = 1; maintype <= MAX_POINT_TYPE; maintype++) {
171 for (int subtype = 0; subtype <= MAX_POINT_SUB_TYPE; subtype++) {
172 if (MapPoint.isCityType(maintype << 8) && subtype != 0x00)
173 continue;
174 int type = (maintype << 8) + subtype;
174175
175176 MapPoint point = new MapPoint();
176177
178179 double baseLong = lon + maintype * ELEMENT_SPACING;
179180
180181 point.setMinResolution(10);
181 if (subtype < 0 ? hasBackground : !hasBackground)
182 point.setName(GType.formatType(type));
182 if (!hasBackground)
183 point.setName(makeName(type, true));
183184 point.setLocation(new Coord(baseLat, baseLong));
184185 point.setType(type);
185186
186187 mapper.addPoint(point);
187
188
188189 if (configProps.containsKey("verbose"))
189 System.out.println("Generated POI " + GType.formatType(type) + " at " + point.getLocation().toDegreeString());
190 System.out.println("Generated POI " + GType.formatType(type) + " at "
191 + point.getLocation().toDegreeString() + " " + point.getName());
190192 mapper.addToBounds(point.getLocation()); // XXX shouldn't be needed.
191 if (maintype == 0)
192 break;
193193 }
194194 }
195195 }
205205 MapLine line = new MapLine();
206206 line.setMinResolution(10);
207207 if (!hasBackground)
208 line.setName(GType.formatType(type));
208 line.setName(makeName(type, false));
209209
210210 double baseLat = lat + y * ELEMENT_SPACING;
211211 double baseLong = lon + x * ELEMENT_SPACING;
215215 coords.add(co);
216216 mapper.addToBounds(co);
217217 if (configProps.containsKey("verbose"))
218 System.out.println("Generated line " + GType.formatType(type) + " at " + co.toDegreeString());
218 System.out.println("Generated line " + GType.formatType(type) + " at " + co.toDegreeString() + " " + line.getName());
219219
220220 co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE);
221221 coords.add(co);
222222 mapper.addToBounds(co);
223223
224 co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE + ELEMENT_SIZE/2);
224 co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE + ELEMENT_SIZE/2);
225225 coords.add(co);
226226 mapper.addToBounds(co);
227227
247247
248248 MapShape shape = new MapShape();
249249 shape.setMinResolution(10);
250 if (hasBackground)
251 shape.setName(GType.formatType(type));
250 if (!hasBackground)
251 shape.setName(makeName(type, false));
252252
253253 double baseLat = lat + y * ELEMENT_SPACING;
254254 double baseLong = lon + x * ELEMENT_SPACING;
260260 coords.add(co);
261261 mapper.addToBounds(co);
262262 if (configProps.containsKey("verbose"))
263 System.out.println("Generated polygon " + GType.formatType(type) + " at " + co.toDegreeString());
263 System.out.println("Generated polygon " + GType.formatType(type) + " at " + co.toDegreeString() + " " + shape.getName());
264264
265265 co = new Coord(baseLat + ELEMENT_SIZE, baseLong);
266266 coords.add(co);
207207 continue;
208208 }
209209 w.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG);
210 w.deleteTag(MultiPolygonRelation.MP_CREATED_TAG);
210 w.deleteTag(MultiPolygonRelation.MP_CREATED_TAG_KEY);
211211 ways.add(w);
212212 }
213213 }
328328 CodePosition cp = new CodePosition();
329329 int b = r.getBval();
330330 int primary = sort.getPrimary(b);
331 cp.setPrimary((char) primary);
331 cp.setPrimary(primary);
332332
333333 // We do not want the character to sort fully equal to the expanded characters (or any other
334334 // character so adjust the ordering at other strengths. May need further tweaks.
345345 } else {
346346 secondary = 1;
347347 }
348 cp.setSecondary((byte) (secondary));
349 cp.setTertiary((byte) (tertiary));
348 cp.setSecondary(secondary);
349 cp.setTertiary(tertiary);
350350 } else {
351351 num++;
352 secondary = sort.getSecondary(b) & 0xff;
353 cp.setSecondary((byte) (secondary + 7));
354
355 tertiary = sort.getTertiary(b) & 0xff;
356 cp.setTertiary((byte) (tertiary + 2));
352 secondary = sort.getSecondary(b);
353 cp.setSecondary(secondary + 7);
354
355 tertiary = sort.getTertiary(b);
356 cp.setTertiary(tertiary + 2);
357357 }
358358 expansions.add(cp);
359359 }
7272 public boolean getProperty(String key, boolean def) {
7373 String s = getProperty(key);
7474 if (s != null) {
75 if (s.length() == 0)
75 if (s.isEmpty())
7676 return true;
7777 char c = s.toLowerCase().charAt(0);
78 if (c == '1' || c == 'y' || c == 't')
79 return true;
80 else
81 return false;
78 return (c == '1' || c == 'y' || c == 't');
8279 }
8380 return def;
8481 }
119119 set = null;
120120 nextPoint = null;
121121
122 // false => first node is a latitude level
123 return findNextPoint(p.getLocation(), root, ROOT_NODE_USES_LONGITUDE);
122 findNextPoint(p.getLocation(), root, ROOT_NODE_USES_LONGITUDE);
123 return nextPoint;
124124 }
125125
126126 /**
135135 this.maxDist = Math.pow(maxDist * 360 / Coord.U, 2); // convert maxDist in meter to distanceInDegreesSquared
136136 nextPoint = null;
137137 set = new LinkedHashSet<>();
138 // false => first node is a latitude level
139138 findNextPoint(p.getLocation(), root, ROOT_NODE_USES_LONGITUDE);
140139 return set;
141140 }
142141
143142 /**
144143 * Recursive routine to find the closest point. If set is not null, all
145 * elements within the range given by maxDist are collected.
144 * elements within the range given by maxDist are collected.
145 * Closest point is in field nextPoint.
146146 *
147147 * @param p the location of the given point
148148 * @param tree the sub tree
149149 * @param useLongitude gives the dimension to search in
150 * @return the closest point
151150 */
152 private T findNextPoint(Coord p, KdNode tree, boolean useLongitude) {
151 private void findNextPoint(Coord p, KdNode tree, boolean useLongitude) {
153152 if (tree == null)
154 return nextPoint;
155
156 boolean smaller;
153 return;
157154
158155 if (tree.left == null && tree.right == null) {
159156 processNode(tree, p);
160 return nextPoint;
157 return;
161158 }
162 smaller = isSmaller(useLongitude, p, tree.point.getLocation());
163 nextPoint = findNextPoint(p, smaller ? tree.left : tree.right, !useLongitude);
159 boolean smaller = isSmaller(useLongitude, p, tree.point.getLocation());
160 findNextPoint(p, smaller ? tree.left : tree.right, !useLongitude);
164161
165162 processNode(tree, p);
166163 // do we have to search the other part of the tree?
168165 int testLon = useLongitude ? tree.point.getLocation().getHighPrecLon() : p.getHighPrecLon();
169166 Coord test = Coord.makeHighPrecCoord(testLat, testLon);
170167 if (test.distanceInDegreesSquared(p) < minDist) {
171 nextPoint = findNextPoint(p, smaller ? tree.right : tree.left, !useLongitude);
168 findNextPoint(p, smaller ? tree.right : tree.left, !useLongitude);
172169 }
173 return nextPoint;
174170 }
175171
176172 private void processNode(KdNode node, Coord p) {
9494 case "RGN":
9595 count++;
9696 System.out.println("RGN size " + size);
97 assertThat("RGN size", size, new RangeMatcher(2704));
97 assertThat("RGN size", size, new RangeMatcher(2710));
9898 break;
9999 case "TRE":
100100 count++;
5151 case "RGN":
5252 count++;
5353 System.out.println("RGN size " + size);
54 assertThat("RGN size", size, new RangeMatcher(127586));
54 assertThat("RGN size", size, new RangeMatcher(127579));
5555 break;
5656 case "TRE":
5757 count++;
6969 break;
7070 case "NOD":
7171 count++;
72 assertEquals("NOD size", 170201, size);
72 System.out.println("NOD size " + size);
73 assertEquals("NOD size", 146631, size);
7374 break;
7475 }
7576 }
106107 break;
107108 case "NOD":
108109 count++;
110 System.out.println("NOD size " + size);
109111 assertEquals("NOD size", 3584, size);
110112 break;
111113 }
7171 List<Numbers> numbers = new ArrayList<Numbers>();
7272 for (String s : strings) {
7373 Numbers n = new Numbers(s);
74 n.setIndex(n.getNodeNumber());
74 n.setIndex(n.getPolishIndex());
7575 numbers.add(n);
7676 }
7777
8888
8989 // Have to fix up the node numbers
9090 for (Numbers n : list) {
91 n.setNodeNumber(n.getIndex());
91 n.setPolishIndex(n.getIndex());
9292 }
9393
9494 // Test that they are the same.
104104 "0,O,1,9,E,2,12",
105105 "1,O,11,17,E,14,20",
106106 "2,O,21,31,E,26,36",
107 });
108 List<Numbers> output = writeAndRead(numbers);
109 assertEquals(numbers, output);
110 }
111
112 @Test
113 public void testMultipleNodesWithSkip() {
114 List<Numbers> numbers = createList(new String[]{
115 "0,O,1,9,E,2,12",
116 "2,O,11,17,E,14,20",
117 "3,O,21,31,E,26,36",
107118 });
108119 List<Numbers> output = writeAndRead(numbers);
109120 assertEquals(numbers, output);
267278 nr.setNumberOfNodes(numbers.get(numbers.size()-1).getIndex() + 1);
268279 List<Numbers> list = nr.readNumbers(swapped);
269280 for (Numbers n : list)
270 n.setNodeNumber(n.getIndex());
281 n.setPolishIndex(n.getIndex());
271282
272283 return list;
273284 }
274285
275286 private List<Numbers> createList(String[] specs) {
276 List<Numbers> numbers = new ArrayList<Numbers>();
287 List<Numbers> numbers = new ArrayList<>();
277288 for (String s : specs) {
278289 Numbers n = new Numbers(s);
279 n.setIndex(n.getNodeNumber());
290 n.setIndex(n.getPolishIndex());
280291 numbers.add(n);
281292 }
282293 return numbers;
8282 assertFalse(numbers.isPlausible());
8383 }
8484 @Test
85 public void testOKDifferentZipCodes() {
86 String spec = "0,O,15,15,B,10,23,83-047,83-048";
87 Numbers numbers = new Numbers(spec);
88 assertTrue(numbers.isPlausible());
89 }
90 @Test
8591 public void testSingleNumBothSides() {
8692 String spec = "0,O,15,15,O,15,15";
8793 Numbers numbers = new Numbers(spec);
2828 "pool", "ocean"
2929 };
3030
31 String PATH_SEP = System.getProperty("file.separator");
31 static final String PATH_SEP = System.getProperty("file.separator");
3232
33 private final List<Option> found = new ArrayList<Option>();
34 private final List<String> options = new ArrayList<String>();
35 private final List<String> values = new ArrayList<String>();
33 private final List<Option> found = new ArrayList<>();
34 private final List<String> options = new ArrayList<>();
35 private final List<String> values = new ArrayList<>();
3636
3737 /**
3838 * You can have options with values separated by either a ':' or an
118118 public void testRelativeFilenamesInFile() {
119119 String s = "input-file: foo\n";
120120
121 OptionProcessor proc = new MyOptionProcessor();
122 Options opts = new Options(proc);
121 Options opts = new Options(myOptionProcessor);
123122 Reader r = new StringReader(s);
124123
125124 opts.readOptionFile(r, "/bar/string.args");
146145 exp_dir = "/home";
147146 }
148147
149 OptionProcessor proc = new MyOptionProcessor();
150 Options opts = new Options(proc);
148 Options opts = new Options(myOptionProcessor);
151149 Reader r = new StringReader(s);
152150
153151 opts.readOptionFile(r, "/bar/string.args");
166164 }
167165
168166 private void readOptionsFromString(String s) {
169 OptionProcessor proc = new MyOptionProcessor();
170 Options opts = new Options(proc);
167 Options opts = new Options(myOptionProcessor);
171168 Reader r = new StringReader(s);
172169
173170 opts.readOptionFile(r, "from-string");
174171 }
175172
176 private class MyOptionProcessor implements OptionProcessor {
177 public void processOption(Option opt) {
178 found.add(opt);
179 options.add(opt.getOption());
180 values.add(opt.getValue());
181 }
182 }
173 private OptionProcessor myOptionProcessor = opt -> {
174 found.add(opt);
175 options.add(opt.getOption());
176 values.add(opt.getValue());
177 };
183178 }
12191219 */
12201220 private GType getFirstType(Rule rs, Element el) {
12211221 final List<GType> types = new ArrayList<>();
1222 rs.resolveType(el, new TypeResult() {
1223 public void add(Element el, GType type) {
1224 types.add(type);
1225 }
1226 });
1222 rs.resolveType(el, (element, type) -> types.add(type));
12271223 if (types.isEmpty())
12281224 return null;
12291225 else