New upstream version 0.0.0+svn4380
Bas Couwenberg
4 years ago
12 | 12 | Using |
13 | 13 | ===== |
14 | 14 | |
15 | This program requires Java 1.6 or above to run. | |
15 | This program requires Java 1.8 or above to run. | |
16 | 16 | |
17 | 17 | Producing a map is simple. Save OpenStreetMap data from JOSM |
18 | 18 | or by any other method to a file and copy it to the mkgmap |
59 | 59 | |
60 | 60 | to obtain an up to date and complete listing of options. |
61 | 61 | |
62 | Also consider examples/sample.cfg as a starting point for your options package | |
63 | ||
62 | 64 | Processing more than one file at a time |
63 | 65 | --------------------------------------- |
64 | 66 |
400 | 400 | <copy todir="${dist}/examples"> |
401 | 401 | <fileset dir="resources"> |
402 | 402 | <include name="roadNameConfig.txt"/> |
403 | <include name="sample.cfg"/> | |
403 | 404 | <include name="known-hgt.txt"/> |
404 | 405 | <include name="installer/**"/> |
405 | 406 | <include name="styles/default/**"/> |
574 | 574 | ;--check-roundabout-flares |
575 | 575 | : Check that roundabout flare roads point in the correct |
576 | 576 | 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. | |
577 | 596 | <p> |
578 | 597 | ;--max-flare-length-ratio=NUM |
579 | 598 | : When checking flare roads, ignore roads whose length is |
64 | 64 | | +mkgmap:dir-check+ | Set to +false+ to tell mkgmap to ignore the way when checking roundabouts for direction | 'check-roundabouts' |
65 | 65 | | +mkgmap:no-dir-check+ | Set to +true+ to tell mkgmap to ignore the way when checking roundabouts for direction | 'check-roundabouts' |
66 | 66 | | +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 | ||
68 | 71 | |========================================================= |
69 | 72 | |
70 | 73 |
566 | 566 | Check that roundabout flare roads point in the correct direction, |
567 | 567 | are one-way and don't extend too far. |
568 | 568 | |
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 | ||
569 | 590 | --max-flare-length-ratio=NUM |
570 | 591 | When checking flare roads, ignore roads whose length is |
571 | 592 | 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 |
4 | 4 | |
5 | 5 | characters |
6 | 6 | |
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 | |
8 | 9 | < 0009 |
9 | 10 | < 000a |
10 | 11 | < 000b |
31 | 31 | */ |
32 | 32 | public class Area { |
33 | 33 | 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); | |
35 | 35 | |
36 | 36 | private final int minLat; |
37 | 37 | private final int minLong; |
168 | 168 | * @throws MapFailedException if more complex split operation couldn't be honoured. |
169 | 169 | */ |
170 | 170 | public Area[] split(int xsplit, int ysplit, int resolutionShift) { |
171 | Area[] areas = new Area[xsplit * ysplit]; | |
171 | Area[] areas = new Area[xsplit * ysplit]; | |
172 | 172 | |
173 | 173 | int xstart; |
174 | 174 | int xend; |
181 | 181 | if (x == xsplit - 1) |
182 | 182 | xend = maxLong; |
183 | 183 | else |
184 | xend = roundPof2(xstart + (maxLong - xstart) / (xsplit - x), | |
185 | resolutionShift); | |
184 | xend = roundPof2(xstart + (maxLong - xstart) / (xsplit - x), resolutionShift); | |
186 | 185 | ystart = minLat; |
187 | 186 | for (int y = 0; y < ysplit; y++) { |
188 | 187 | if (y == ysplit - 1) |
189 | 188 | yend = maxLat; |
190 | 189 | else |
191 | yend = roundPof2(ystart + (maxLat - ystart) / (ysplit - y), | |
192 | resolutionShift); | |
190 | yend = roundPof2(ystart + (maxLat - ystart) / (ysplit - y), resolutionShift); | |
193 | 191 | if (xstart < xend && ystart < yend) { |
194 | 192 | 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); | |
197 | 195 | 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 | } | |
200 | 200 | ystart = yend; |
201 | 201 | } |
202 | 202 | xstart = xend; |
317 | 317 | } |
318 | 318 | |
319 | 319 | 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; | |
322 | 324 | |
323 | 325 | 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; | |
331 | 327 | } |
332 | 328 | |
333 | 329 | public int hashCode() { |
342 | 338 | * @return list of coords that form the rectangle |
343 | 339 | */ |
344 | 340 | 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)); | |
355 | 347 | return coords; |
356 | 348 | } |
357 | 349 |
36 | 36 | * @author Steve Ratcliffe |
37 | 37 | */ |
38 | 38 | 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 | |
62 | 62 | |
63 | 63 | private final int latitude; |
64 | 64 | private final int longitude; |
93 | 93 | this.lonDelta = (byte) ((this.longitude << DELTA_SHIFT) - lonHighPrec); |
94 | 94 | |
95 | 95 | // 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; | |
98 | 98 | } |
99 | 99 | |
100 | 100 | private Coord (int lat, int lon, byte latDelta, byte lonDelta){ |
403 | 403 | * Compares the coordinates that are displayed in the map |
404 | 404 | */ |
405 | 405 | public boolean equals(Object obj) { |
406 | if (obj == null || !(obj instanceof Coord)) | |
406 | if (!(obj instanceof Coord)) | |
407 | 407 | return false; |
408 | 408 | Coord other = (Coord) obj; |
409 | 409 | return latitude == other.latitude && longitude == other.longitude; |
485 | 485 | double sinMidLat = Math.sin((lat1-lat2)/2); |
486 | 486 | double sinMidLon = Math.sin((lon1-lon2)/2); |
487 | 487 | 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 | |
490 | 489 | } |
491 | 490 | |
492 | 491 | /** |
512 | 511 | |
513 | 512 | // distance is pythagoras on 'stretched' Mercator projection |
514 | 513 | 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 | |
519 | 515 | } |
520 | 516 | |
521 | 517 | /** |
651 | 647 | /* Factor for conversion to radians using HIGH_PREC_BITS bits |
652 | 648 | * (Math.PI / 180) * (360.0 / (1 << HIGH_PREC_BITS)) |
653 | 649 | */ |
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; | |
655 | 651 | |
656 | 652 | /** |
657 | 653 | * Convert to radians using high precision |
666 | 662 | * @return Latitude as signed HIGH_PREC_BITS bit integer |
667 | 663 | */ |
668 | 664 | public int getHighPrecLat() { |
669 | return (latitude << DELTA_SHIFT) - latDelta; | |
665 | return (latitude << DELTA_SHIFT) - (int) latDelta; | |
670 | 666 | } |
671 | 667 | |
672 | 668 | /** |
673 | 669 | * @return Longitude as signed HIGH_PREC_BITS bit integer |
674 | 670 | */ |
675 | 671 | public int getHighPrecLon() { |
676 | return (longitude << DELTA_SHIFT) - lonDelta; | |
672 | return (longitude << DELTA_SHIFT) - (int) lonDelta; | |
677 | 673 | } |
678 | 674 | |
679 | 675 | /** |
59 | 59 | } |
60 | 60 | |
61 | 61 | void write(ImgFileWriter writer) { |
62 | //writer.put3() | |
63 | 62 | if (pointRef) { |
64 | // System.err.println("City point = " + (int)pointIndex + " div = " + subdivision.getNumber()); | |
65 | 63 | writer.put1u(pointIndex); |
66 | 64 | writer.put2u(subdivision.getNumber()); |
67 | 65 | } else { |
105 | 103 | } |
106 | 104 | |
107 | 105 | public String getName() { |
108 | if (label == null) | |
109 | return ""; | |
110 | return label.getText(); | |
106 | return label == null ? "" : label.getText(); | |
111 | 107 | } |
112 | 108 | |
113 | 109 | public int getLblOffset() { |
114 | if (label == null) | |
115 | return 0; | |
116 | return label.getOffset(); | |
110 | return label == null ? 0 : label.getOffset(); | |
117 | 111 | } |
118 | 112 | |
119 | 113 | public String toString() { |
123 | 117 | if (subdivision != null) |
124 | 118 | result += " " + subdivision.getNumber() + "/" + pointIndex; |
125 | 119 | if(country != null) |
126 | result += " in country " + (0 + country.getIndex()); | |
120 | result += " in country " + country.getIndex(); | |
127 | 121 | if(region != null) |
128 | result += " in region " + (0 + region.getIndex()); | |
122 | result += " in region " + region.getIndex(); | |
129 | 123 | |
130 | 124 | return result; |
131 | 125 | } |
158 | 152 | } |
159 | 153 | |
160 | 154 | public int getCountryNumber() { |
161 | return country != null ? country.getIndex() : 0; | |
155 | return country == null ? 0 : country.getIndex(); | |
162 | 156 | } |
163 | 157 | |
164 | 158 | public Label getLabel() { |
26 | 26 | public class Country { |
27 | 27 | // The country number. This is not recorded in the file |
28 | 28 | private int index; |
29 | private Label label; | |
29 | private final Label label; | |
30 | 30 | |
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; | |
33 | 34 | } |
34 | 35 | |
35 | 36 | void write(ImgFileWriter writer) { |
40 | 41 | return index; |
41 | 42 | } |
42 | 43 | |
43 | public void setLabel(Label label) { | |
44 | this.label = label; | |
45 | } | |
46 | ||
47 | 44 | public Label getLabel() { |
48 | 45 | return label; |
49 | 46 | } |
56 | 56 | private final PlacesFile places = new PlacesFile(); |
57 | 57 | private Sort sort; |
58 | 58 | |
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; | |
61 | 61 | |
62 | 62 | public LBLFile(ImgChannel chan, Sort sort) { |
63 | 63 | this.sort = sort; |
64 | 64 | lblHeader.setSort(sort); |
65 | lblHeader.setOffsetMultiplier(offsetMultiplier); | |
65 | lblHeader.setOffsetMultiplier(OFFSET_MULTIPLIER); | |
66 | 66 | setHeader(lblHeader); |
67 | 67 | |
68 | 68 | setWriter(new BufferedImgFileWriter(chan)); |
69 | 69 | |
70 | position(LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength()); | |
70 | position((long) LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength()); | |
71 | 71 | |
72 | 72 | // The zero offset is for no label. |
73 | 73 | getWriter().put1u(0); |
155 | 155 | */ |
156 | 156 | private void alignForNext() { |
157 | 157 | // Align ready for next label |
158 | while ((getCurrentLabelOffset() & ((1 << offsetMultiplier) - 1)) != 0) | |
158 | while ((getCurrentLabelOffset() & ((1 << OFFSET_MULTIPLIER) - 1)) != 0) | |
159 | 159 | getWriter().put1u(0); |
160 | 160 | } |
161 | 161 | |
162 | 162 | private int getNextLabelOffset() { |
163 | return getCurrentLabelOffset() >> offsetMultiplier; | |
163 | return getCurrentLabelOffset() >> OFFSET_MULTIPLIER; | |
164 | 164 | } |
165 | 165 | |
166 | 166 | private int getCurrentLabelOffset() { |
142 | 142 | Label label = fetchLabel(offset); |
143 | 143 | |
144 | 144 | if (label != null) { |
145 | Country country = new Country(index); | |
146 | country.setLabel(label); | |
145 | Country country = new Country(index, label); | |
147 | 146 | countries.add(country); |
148 | 147 | } |
149 | 148 | index++; |
170 | 169 | int offset = reader.get3u(); |
171 | 170 | Label label = fetchLabel(offset); |
172 | 171 | if (label != null) { |
173 | Region region = new Region(countries.get(country)); | |
172 | Region region = new Region(countries.get(country), label); | |
174 | 173 | region.setIndex(index); |
175 | region.setLabel(label); | |
176 | 174 | |
177 | 175 | regions.add(region); |
178 | 176 | } |
244 | 242 | int start = header.getLabelStart(); |
245 | 243 | int size = header.getLabelSize(); |
246 | 244 | |
247 | reader.position(start + mult); | |
245 | reader.position((long) start + mult); | |
248 | 246 | int labelOffset = mult; |
249 | 247 | |
250 | 248 | for (int off = mult; off <= size; off++) { |
312 | 310 | while (reader.position() < end) { |
313 | 311 | int lblOffset = reader.get3u(); |
314 | 312 | |
315 | Zip zip = new Zip(); | |
316 | zip.setLabel(fetchLabel(lblOffset)); | |
313 | Zip zip = new Zip(fetchLabel(lblOffset)); | |
317 | 314 | zip.setIndex(zipIndex); |
318 | 315 | |
319 | 316 | zips.put(zip.getIndex(), zip); |
34 | 34 | this.name = name; |
35 | 35 | this.poiIndex = poiIndex; |
36 | 36 | this.group = group; |
37 | this.subType = subType & 0xff; | |
37 | this.subType = subType; | |
38 | 38 | } |
39 | 39 | |
40 | 40 | void write(ImgFileWriter writer) { |
19 | 19 | import java.util.LinkedHashMap; |
20 | 20 | import java.util.List; |
21 | 21 | import java.util.Map; |
22 | import java.util.Map.Entry; | |
22 | 23 | import java.util.Random; |
24 | import java.util.TreeMap; | |
23 | 25 | |
24 | 26 | import uk.me.parabola.imgfmt.app.Exit; |
25 | 27 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
33 | 35 | * This is really part of the LBLFile. We split out all the parts of the file |
34 | 36 | * that are to do with location to here. |
35 | 37 | */ |
36 | @SuppressWarnings({"unchecked", "rawtypes"}) | |
37 | 38 | public class PlacesFile { |
38 | 39 | public static final int MIN_INDEXED_POI_TYPE = 0x29; |
39 | 40 | public static final int MAX_INDEXED_POI_TYPE = 0x30; |
52 | 53 | private final List<Highway> highways = new ArrayList<>(); |
53 | 54 | private final List<ExitFacility> exitFacilities = new ArrayList<>(); |
54 | 55 | 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<>(); | |
56 | 57 | |
57 | 58 | private LBLFile lblFile; |
58 | 59 | private PlacesHeader placeHeader; |
75 | 76 | } |
76 | 77 | |
77 | 78 | void write(ImgFileWriter writer) { |
78 | for (Country c : countryList) | |
79 | c.write(writer); | |
79 | countryList.forEach(c -> c.write(writer)); | |
80 | 80 | placeHeader.endCountries(writer.position()); |
81 | 81 | |
82 | for (Region region : regionList) | |
83 | region.write(writer); | |
82 | regionList.forEach(r -> r.write(writer)); | |
84 | 83 | placeHeader.endRegions(writer.position()); |
85 | 84 | |
86 | for (City sc : cityList) | |
87 | sc.write(writer); | |
85 | cityList.forEach(sc -> sc.write(writer)); | |
88 | 86 | |
89 | 87 | placeHeader.endCity(writer.position()); |
90 | 88 | |
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); | |
104 | 100 | } |
105 | 101 | } |
106 | 102 | placeHeader.endPOIIndex(writer.position()); |
113 | 109 | placeHeader.endPOI(writer.position()); |
114 | 110 | |
115 | 111 | 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(); | |
122 | 117 | } |
123 | 118 | placeHeader.endPOITypeIndex(writer.position()); |
124 | 119 | |
125 | for (Zip z : zipList) | |
126 | z.write(writer); | |
120 | zipList.forEach(z -> z.write(writer)); | |
127 | 121 | placeHeader.endZip(writer.position()); |
128 | 122 | |
129 | 123 | int extraHighwayDataOffset = 0; |
134 | 128 | } |
135 | 129 | placeHeader.endHighway(writer.position()); |
136 | 130 | |
137 | for (ExitFacility ef : exitFacilities) | |
138 | ef.write(writer); | |
131 | exitFacilities.forEach(ef -> ef.write(writer)); | |
139 | 132 | placeHeader.endExitFacility(writer.position()); |
140 | 133 | |
141 | for (Highway h : highways) | |
142 | h.write(writer, true); | |
134 | highways.forEach(h -> h.write(writer, true)); | |
143 | 135 | placeHeader.endHighwayData(writer.position()); |
144 | 136 | } |
145 | 137 | |
146 | 138 | Country createCountry(String name, String abbr) { |
147 | ||
148 | 139 | 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))); | |
160 | 141 | } |
161 | 142 | |
162 | 143 | 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; | |
166 | 145 | 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 | */ | |
180 | 156 | City createCity(Country country, String name, boolean unique) { |
181 | ||
182 | 157 | String uniqueCityName = name.toUpperCase() + "_C" + country.getLabel().getOffset(); |
183 | 158 | |
184 | 159 | // 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); | |
190 | 162 | } |
191 | 163 | |
192 | 164 | City c = null; |
196 | 168 | if (c == null) { |
197 | 169 | c = new City(country); |
198 | 170 | |
199 | Label l = lblFile.newLabel(name); | |
200 | c.setLabel(l); | |
171 | c.setLabel(lblFile.newLabel(name)); | |
201 | 172 | |
202 | 173 | cityList.add(c); |
203 | 174 | cities.put(uniqueCityName, c); |
207 | 178 | return c; |
208 | 179 | } |
209 | 180 | |
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 | */ | |
210 | 188 | City createCity(Region region, String name, boolean unique) { |
211 | 189 | |
212 | 190 | String uniqueCityName = name.toUpperCase() + "_R" + region.getLabel().getOffset(); |
213 | 191 | |
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); | |
220 | 194 | } |
221 | 195 | |
222 | 196 | City c = null; |
223 | if(!unique) | |
197 | if(!unique) { | |
224 | 198 | c = cities.get(uniqueCityName); |
199 | } | |
225 | 200 | |
226 | 201 | if(c == null) { |
227 | 202 | c = new City(region); |
228 | 203 | |
229 | Label l = lblFile.newLabel(name); | |
230 | c.setLabel(l); | |
204 | c.setLabel(lblFile.newLabel(name)); | |
231 | 205 | |
232 | 206 | cityList.add(c); |
233 | 207 | cities.put(uniqueCityName, c); |
237 | 211 | return c; |
238 | 212 | } |
239 | 213 | |
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 | ||
240 | 223 | 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))); | |
253 | 225 | } |
254 | 226 | |
255 | 227 | Highway createHighway(Region region, String name) { |
256 | 228 | Highway h = new Highway(region, highways.size()+1); |
257 | 229 | |
258 | Label l = lblFile.newLabel(name); | |
259 | h.setLabel(l); | |
230 | h.setLabel(lblFile.newLabel(name)); | |
260 | 231 | |
261 | 232 | highways.add(h); |
262 | 233 | return h; |
271 | 242 | |
272 | 243 | POIRecord createPOI(String name) { |
273 | 244 | assert !poisClosed; |
274 | // TODO... | |
275 | 245 | POIRecord p = new POIRecord(); |
276 | 246 | |
277 | Label l = lblFile.newLabel(name); | |
278 | p.setLabel(l); | |
279 | ||
247 | p.setLabel(lblFile.newLabel(name)); | |
280 | 248 | pois.add(p); |
281 | 249 | |
282 | 250 | return p; |
284 | 252 | |
285 | 253 | POIRecord createExitPOI(String name, Exit exit) { |
286 | 254 | assert !poisClosed; |
287 | // TODO... | |
288 | 255 | POIRecord p = new POIRecord(); |
289 | 256 | |
290 | Label l = lblFile.newLabel(name); | |
291 | p.setLabel(l); | |
292 | ||
257 | p.setLabel(lblFile.newLabel(name)); | |
293 | 258 | p.setExit(exit); |
294 | ||
295 | 259 | pois.add(p); |
296 | 260 | |
297 | 261 | return p; |
303 | 267 | if (t < MIN_INDEXED_POI_TYPE || t > MAX_INDEXED_POI_TYPE) |
304 | 268 | return; |
305 | 269 | |
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); | |
310 | 272 | } |
311 | 273 | |
312 | 274 | void allPOIsDone() { |
329 | 291 | } |
330 | 292 | |
331 | 293 | /** |
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 | |
333 | 295 | * only be in one country or at least a very small number). |
334 | 296 | * |
335 | 297 | * But why not? |
356 | 318 | */ |
357 | 319 | private void sortRegions() { |
358 | 320 | List<SortKey<Region>> keys = new ArrayList<>(); |
359 | for (Region r : regionList) { | |
321 | for (Region r : regions.values()) { | |
360 | 322 | SortKey<Region> key = sort.createSortKey(r, r.getLabel(), r.getCountry().getIndex()); |
361 | 323 | keys.add(key); |
362 | 324 | } |
27 | 27 | private int index; |
28 | 28 | |
29 | 29 | private final Country country; |
30 | private Label label; | |
30 | private final Label label; | |
31 | 31 | |
32 | public Region(Country country) { | |
32 | public Region(Country country, Label label) { | |
33 | 33 | this.country = country; |
34 | this.label = label; | |
34 | 35 | } |
35 | 36 | |
36 | 37 | public void write(ImgFileWriter writer) { |
51 | 52 | this.index = index; |
52 | 53 | } |
53 | 54 | |
54 | public void setLabel(Label label) { | |
55 | this.label = label; | |
56 | } | |
57 | ||
58 | 55 | public Label getLabel() { |
59 | 56 | return label; |
60 | 57 | } |
27 | 27 | // The index is not stored in the file, you just use the index of it in |
28 | 28 | // the section. |
29 | 29 | private int index; |
30 | private Label label; | |
30 | private final Label label; | |
31 | ||
32 | public Zip(Label label) { | |
33 | this.label = label; | |
34 | } | |
31 | 35 | |
32 | 36 | public void write(ImgFileWriter writer) { |
33 | 37 | writer.put3u(label.getOffset()); |
37 | 41 | return label; |
38 | 42 | } |
39 | 43 | |
40 | public void setLabel(Label label) { | |
41 | this.label = label; | |
42 | } | |
43 | ||
44 | 44 | public int getIndex() { |
45 | 45 | return index; |
46 | 46 | } |
56 | 56 | merge(list,start,len); |
57 | 57 | } else { |
58 | 58 | // sort one chunk |
59 | // System.out.println("sorting list of roads. positions " + start + " to " + (start + len - 1)); | |
60 | 59 | Map<String, byte[]> cache = new HashMap<>(); |
61 | 60 | List<SortKey<T>> keys = new ArrayList<>(len); |
62 | 61 | |
63 | 62 | for (int i = start; i < start + len; i++) { |
64 | 63 | keys.add(makeKey(list.get(i), sort, cache)); |
65 | 64 | } |
66 | cache = null; | |
65 | cache = null; // release memory | |
67 | 66 | keys.sort(null); |
68 | 67 | |
69 | 68 | for (int i = 0; i < keys.size(); i++){ |
71 | 70 | T r = sk.getObject(); |
72 | 71 | list.set(start+i, r); |
73 | 72 | } |
74 | return; | |
75 | 73 | } |
76 | 74 | } |
77 | 75 | |
78 | 76 | |
79 | 77 | private void merge(List<T> list, int start, int len) { |
80 | // System.out.println("merging positions " + start + " to " + (start + len - 1)); | |
81 | 78 | int pos1 = start; |
82 | 79 | int pos2 = start + len / 2; |
83 | 80 | int stop1 = start + len / 2; |
88 | 85 | SortKey<T> sk1 = null; |
89 | 86 | SortKey<T> sk2 = null; |
90 | 87 | while (pos1 < stop1 && pos2 < stop2) { |
91 | if (fetch1 && pos1 < stop1) { | |
88 | if (fetch1) { | |
92 | 89 | sk1 = makeKey(list.get(pos1), sort, null); |
93 | 90 | fetch1 = false; |
94 | 91 | } |
95 | if (fetch2 && pos2 < stop2) { | |
92 | if (fetch2) { | |
96 | 93 | sk2 = makeKey(list.get(pos2), sort, null); |
97 | 94 | fetch2 = false; |
98 | 95 | } |
309 | 309 | mdr10.setNumberOfPois(mdr11.getNumberOfPois()); |
310 | 310 | mdr12.setIndex(mdr11.getIndex()); |
311 | 311 | mdr19.setPois(mdr11.getPois()); |
312 | if (forDevice & !isMulti) { | |
312 | if (forDevice && !isMulti) { | |
313 | 313 | mdr17.addPois(mdr11.getPois()); |
314 | 314 | } |
315 | 315 | mdr11.release(); |
336 | 336 | writeSection(writer, 5, mdr5); |
337 | 337 | mdr25.sortCities(mdr5.getCities()); |
338 | 338 | mdr27.sortCities(mdr5.getCities()); |
339 | if (forDevice & !isMulti) { | |
339 | if (forDevice && !isMulti) { | |
340 | 340 | mdr17.addCities(mdr5.getSortedCities()); |
341 | 341 | } |
342 | 342 | mdr5.release(); |
350 | 350 | mdr21.release(); |
351 | 351 | |
352 | 352 | mdr22.buildFromStreets(mdr7.getStreets()); |
353 | if (forDevice & !isMulti) { | |
353 | if (forDevice && !isMulti) { | |
354 | 354 | mdr17.addStreets(mdr7.getSortedStreets()); |
355 | 355 | } |
356 | 356 | |
357 | 357 | mdr7.release(); |
358 | 358 | writeSection(writer, 22, mdr22); |
359 | if (forDevice & !isMulti) { | |
359 | if (forDevice && !isMulti) { | |
360 | 360 | mdr17.addStreetsByCountry(mdr22.getStreets()); |
361 | 361 | } |
362 | 362 | mdr22.release(); |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.imgfmt.app.mdr; |
13 | 13 | |
14 | import uk.me.parabola.imgfmt.ReadFailedException; | |
15 | 14 | import uk.me.parabola.imgfmt.app.CommonHeader; |
16 | 15 | import uk.me.parabola.imgfmt.app.ImgFileReader; |
17 | 16 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
48 | 47 | sections[1].setPosition(getHeaderLength()); |
49 | 48 | } |
50 | 49 | |
51 | protected void readFileHeader(ImgFileReader reader) throws ReadFailedException { | |
50 | protected void readFileHeader(ImgFileReader reader) { | |
52 | 51 | throw new UnsupportedOperationException("not implemented yet"); |
53 | 52 | } |
54 | 53 |
56 | 56 | */ |
57 | 57 | public void addMap(int mapNumber, int index) { |
58 | 58 | assert index > 0; |
59 | Mdr1Record rec = new Mdr1Record(mapNumber, getConfig()); | |
59 | Mdr1Record rec = new Mdr1Record(mapNumber); | |
60 | 60 | rec.setMapIndex(index); |
61 | 61 | maps.add(rec); |
62 | 62 | |
95 | 95 | } |
96 | 96 | |
97 | 97 | 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 | |
103 | 99 | } |
104 | 100 | |
105 | 101 | public void setEndPosition(int sectionNumber) { |
54 | 54 | if (group == 0) |
55 | 55 | return; |
56 | 56 | if (group == 1) |
57 | t.setSubtype(fullType); | |
57 | t.setSubtype(MdrUtils.getTypeFromFullType(fullType)); // cities | |
58 | 58 | else { |
59 | 59 | t.setSubtype(MdrUtils.getSubtypeFromFullType(fullType)); |
60 | 60 | } |
126 | 126 | this.numberOfPois = numberOfPois; |
127 | 127 | } |
128 | 128 | |
129 | @Override | |
129 | 130 | protected void releaseMemory() { |
130 | 131 | poiTypes = null; |
131 | 132 | } |
28 | 28 | private Mdr11Record mdr11ref; |
29 | 29 | |
30 | 30 | 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()); | |
37 | 32 | } |
38 | 33 | |
39 | 34 | public Mdr11Record getMdr11ref() { |
55 | 55 | * The POI index contains individual references to POI by subdiv and index, so they are not |
56 | 56 | * de-duplicated in the index in the same way that streets and cities are. |
57 | 57 | */ |
58 | @Override | |
58 | 59 | protected void preWriteImpl() { |
59 | 60 | pois.trimToSize(); |
60 | 61 | Sort sort = getConfig().getSort(); |
66 | 67 | return sort.createSortKey(r, r.getName(), r.getMapIndex(), cache); |
67 | 68 | } |
68 | 69 | }; |
69 | // System.out.println("sorting " + pois.size() + " pois by name"); | |
70 | 70 | sorter.sort(pois); |
71 | 71 | for (Mdr11Record poi : pois) { |
72 | 72 | mdr10.addPoiType(poi); |
87 | 87 | if (poi.isCity()) |
88 | 88 | putRegionIndex(writer, poi.getRegionIndex()); |
89 | 89 | else |
90 | putCityIndex(writer, poi.getCityIndex(), true); | |
90 | putCityIndex(writer, poi.getCityIndex()); | |
91 | 91 | if (hasStrings) |
92 | 92 | putStringOffset(writer, poi.getStrOffset()); |
93 | 93 | } |
144 | 144 | rec--; |
145 | 145 | } |
146 | 146 | |
147 | Mdr12Record indexRecord = new Mdr12Record(); | |
148 | indexRecord.setPrefix(prefix); | |
149 | indexRecord.setRecordNumber(rec); | |
150 | list.add(indexRecord); | |
147 | list.add(new Mdr12Record(prefix, rec)); | |
151 | 148 | } |
152 | 149 | return list; |
153 | 150 | } |
167 | 164 | this.mdr10 = mdr10; |
168 | 165 | } |
169 | 166 | |
167 | @Override | |
170 | 168 | public void releaseMemory() { |
171 | 169 | pois = null; |
172 | 170 | mdr10 = null; |
22 | 22 | super(config); |
23 | 23 | } |
24 | 24 | |
25 | @Override | |
25 | 26 | protected int associatedSize() { |
26 | 27 | return getSizes().getPoiSize(); |
27 | 28 | } |
17 | 17 | */ |
18 | 18 | public class Mdr12Record extends Mdr8Record { |
19 | 19 | // This is exactly the same as mdr8 |
20 | public Mdr12Record(char[] prefix, int recordNumber) { | |
21 | super(prefix, recordNumber); | |
22 | } | |
20 | 23 | } |
22 | 22 | * @author Steve Ratcliffe |
23 | 23 | */ |
24 | 24 | public class Mdr13 extends MdrSection implements HasHeaderFlags { |
25 | private final List<Mdr13Record> regions = new ArrayList<Mdr13Record>(); | |
25 | private final List<Mdr13Record> regions = new ArrayList<>(); | |
26 | 26 | |
27 | 27 | public Mdr13(MdrConfig config) { |
28 | 28 | setConfig(config); |
28 | 28 | * We sort first by map id and then by region id. |
29 | 29 | */ |
30 | 30 | 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; | |
39 | 35 | } |
40 | 36 | |
41 | 37 | public int getRegionIndex() { |
22 | 22 | * @author Steve Ratcliffe |
23 | 23 | */ |
24 | 24 | public class Mdr14 extends MdrSection implements HasHeaderFlags { |
25 | private final List<Mdr14Record> countries = new ArrayList<Mdr14Record>(); | |
25 | private final List<Mdr14Record> countries = new ArrayList<>(); | |
26 | 26 | |
27 | 27 | public Mdr14(MdrConfig config) { |
28 | 28 | setConfig(config); |
28 | 28 | * it wasn't. |
29 | 29 | */ |
30 | 30 | 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; | |
39 | 35 | } |
40 | 36 | |
41 | 37 | public int getCountryIndex() { |
40 | 40 | private final OutputStream stringFile; |
41 | 41 | private int nextOffset; |
42 | 42 | |
43 | private Map<String, Integer> strings = new HashMap<String, Integer>(); | |
43 | private Map<String, Integer> strings = new HashMap<>(); | |
44 | 44 | private final Charset charset; |
45 | 45 | private final File tempFile; |
46 | 46 | |
93 | 93 | * Close the temporary file, and release the string table which is no longer |
94 | 94 | * needed. |
95 | 95 | */ |
96 | @Override | |
96 | 97 | public void releaseMemory() { |
97 | 98 | strings = null; |
98 | 99 | try { |
103 | 104 | } |
104 | 105 | |
105 | 106 | public void writeSectData(ImgFileWriter writer) { |
106 | FileInputStream stream = null; | |
107 | try { | |
108 | stream = new FileInputStream(tempFile); | |
107 | try (FileInputStream stream = new FileInputStream(tempFile)) { | |
109 | 108 | FileChannel channel = stream.getChannel(); |
110 | 109 | ByteBuffer buf = ByteBuffer.allocate(32 * 1024); |
111 | 110 | while (channel.read(buf) > 0) { |
115 | 114 | } |
116 | 115 | } catch (IOException e) { |
117 | 116 | throw new ExitException("Could not write string section of index"); |
118 | } finally { | |
119 | Utils.closeFile(stream); | |
120 | 117 | } |
121 | 118 | } |
122 | 119 | |
130 | 127 | * offset possible. We are taking the total size of the string section |
131 | 128 | * for this. |
132 | 129 | */ |
130 | @Override | |
133 | 131 | public int getSizeForRecord() { |
134 | 132 | return Utils.numberToPointerSize(nextOffset); |
135 | 133 | } |
48 | 48 | * have a header with the length and the record size and prefix length of the |
49 | 49 | * records in the subsection. |
50 | 50 | */ |
51 | private void writeSubSect(ImgFileWriter writer, PrefixIndex index) { | |
51 | private static void writeSubSect(ImgFileWriter writer, PrefixIndex index) { | |
52 | 52 | index.preWrite(); |
53 | 53 | int len = index.getItemSize() * index.getNumberOfItems() + 2; |
54 | 54 | if (len == 2) |
76 | 76 | index.writeSectData(writer); |
77 | 77 | } |
78 | 78 | |
79 | @Override | |
79 | 80 | protected void releaseMemory() { |
80 | 81 | streets = null; |
81 | 82 | cities = null; |
24 | 24 | * @author Steve Ratcliffe |
25 | 25 | */ |
26 | 26 | public class Mdr18 extends MdrSection implements HasHeaderFlags { |
27 | private List<Mdr18Record> poiTypes = new ArrayList<Mdr18Record>(); | |
27 | private List<Mdr18Record> poiTypes = new ArrayList<>(); | |
28 | 28 | |
29 | 29 | public Mdr18(MdrConfig config) { |
30 | 30 | setConfig(config); |
33 | 33 | this.subWriter.position(subHeader.getHeaderLen()); |
34 | 34 | } |
35 | 35 | |
36 | public void startSection(int n) { | |
37 | } | |
38 | ||
39 | 36 | public void endSection(int n) { |
40 | 37 | int sn = sectionToSubsection(n); |
41 | 38 | if (sn != 0) |
46 | 43 | subWriter.putNu(pointerSize, recordNumber); |
47 | 44 | } |
48 | 45 | |
49 | private int sectionToSubsection(int n) { | |
46 | private static int sectionToSubsection(int n) { | |
50 | 47 | int sn; |
51 | 48 | switch (n) { |
52 | 49 | case 11: sn = 1; break; |
19 | 19 | private Mdr1MapIndex mdrMapIndex; |
20 | 20 | private int indexOffset; |
21 | 21 | |
22 | public Mdr1Record(int mapNumber, MdrConfig config) { | |
22 | public Mdr1Record(int mapNumber) { | |
23 | 23 | this.mapNumber = mapNumber; |
24 | 24 | } |
25 | 25 |
50 | 50 | if (n == 2) |
51 | 51 | writer.put4(section.getPosition()); |
52 | 52 | else { |
53 | //section.writeSectionInfo(writer); | |
54 | 53 | writer.put4(section.getPosition()); |
55 | 54 | int size = section.getSize(); |
56 | 55 | if (size == 0) |
24 | 24 | * @author Steve Ratcliffe |
25 | 25 | */ |
26 | 26 | public class Mdr25 extends MdrSection { |
27 | private final List<Mdr5Record> cities = new ArrayList<Mdr5Record>(); | |
27 | private final List<Mdr5Record> cities = new ArrayList<>(); | |
28 | 28 | |
29 | 29 | public Mdr25(MdrConfig config) { |
30 | 30 | setConfig(config); |
37 | 37 | public void sortCities(List<Mdr5Record> list) { |
38 | 38 | Sort sort = getConfig().getSort(); |
39 | 39 | |
40 | List<SortKey<Mdr5Record>> keys = new ArrayList<SortKey<Mdr5Record>>(); | |
40 | List<SortKey<Mdr5Record>> keys = new ArrayList<>(); | |
41 | 41 | for (Mdr5Record c : list) { |
42 | 42 | SortKey<Mdr5Record> key = sort.createSortKey(c, c.getMdrCountry().getName(), c.getGlobalCityIndex()); |
43 | 43 | keys.add(key); |
24 | 24 | * @author Steve Ratcliffe |
25 | 25 | */ |
26 | 26 | public class Mdr26 extends MdrSection { |
27 | private final List<Mdr28Record> index = new ArrayList<Mdr28Record>(); | |
27 | private final List<Mdr28Record> index = new ArrayList<>(); | |
28 | 28 | |
29 | 29 | public Mdr26(MdrConfig config) { |
30 | 30 | setConfig(config); |
33 | 33 | public void sortMdr28(List<Mdr28Record> in) { |
34 | 34 | Sort sort = getConfig().getSort(); |
35 | 35 | |
36 | List<SortKey<Mdr28Record>> sortList = new ArrayList<SortKey<Mdr28Record>>(); | |
36 | List<SortKey<Mdr28Record>> sortList = new ArrayList<>(); | |
37 | 37 | int record = 0; |
38 | 38 | for (Mdr28Record mdr28 : in) { |
39 | 39 | SortKey<Mdr28Record> key = sort.createSortKey(mdr28, mdr28.getMdr14().getName(), ++record); |
24 | 24 | * @author Steve Ratcliffe |
25 | 25 | */ |
26 | 26 | public class Mdr27 extends MdrSection { |
27 | private final List<Mdr5Record> cities = new ArrayList<Mdr5Record>(); | |
27 | private final List<Mdr5Record> cities = new ArrayList<>(); | |
28 | 28 | |
29 | 29 | public Mdr27(MdrConfig config) { |
30 | 30 | setConfig(config); |
37 | 37 | public void sortCities(List<Mdr5Record> list) { |
38 | 38 | Sort sort = getConfig().getSort(); |
39 | 39 | |
40 | List<SortKey<Mdr5Record>> keys = new ArrayList<SortKey<Mdr5Record>>(); | |
40 | List<SortKey<Mdr5Record>> keys = new ArrayList<>(); | |
41 | 41 | for (Mdr5Record c : list) { |
42 | 42 | Mdr13Record mdrRegion = c.getMdrRegion(); |
43 | 43 | if (mdrRegion != null) { |
26 | 26 | * @author Steve Ratcliffe |
27 | 27 | */ |
28 | 28 | public class Mdr28 extends MdrSection implements HasHeaderFlags { |
29 | private final List<Mdr28Record> index = new ArrayList<Mdr28Record>(); | |
29 | private final List<Mdr28Record> index = new ArrayList<>(); | |
30 | 30 | |
31 | 31 | public Mdr28(MdrConfig config) { |
32 | 32 | setConfig(config); |
18 | 18 | * |
19 | 19 | * @author Steve Ratcliffe |
20 | 20 | */ |
21 | public class Mdr28Record extends ConfigBase { | |
21 | public class Mdr28Record { | |
22 | 22 | private int index; |
23 | 23 | private String name; |
24 | 24 | private int mdr21; |
58 | 58 | } |
59 | 59 | } |
60 | 60 | |
61 | @Override | |
61 | 62 | protected void preWriteImpl() { |
62 | 63 | if (!index.isEmpty()) { |
63 | 64 | for (Mdr29Record r : index) { |
43 | 43 | boolean writeLabel = (magic & HAS_LABEL) != 0; // A guess |
44 | 44 | boolean writeNameOffset = (magic & HAS_NAME_OFFSET) != 0; // A guess, but less so |
45 | 45 | int partialInfoSize = ((magic >> 3) & 0x7); |
46 | // int partialBShift = ((magic >> 6) & 0xf); | |
47 | // int partialBMask = (1 << partialBShift) - 1; | |
48 | 46 | |
49 | 47 | int recordNumber = 0; |
50 | 48 | for (Mdr7Record street : streets) { |
67 | 65 | |
68 | 66 | if (partialInfoSize > 0) { |
69 | 67 | int trailingFlags = ((rr & 1) == 0) ? 1 : 0; |
70 | // trailingFlags |= s.getB() << 1; | |
71 | // trailingFlags |= s.getS() << (1 + partialBShift); | |
72 | 68 | writer.putNu(partialInfoSize, trailingFlags); |
73 | 69 | } |
74 | 70 | } else { |
118 | 114 | return streets.size(); |
119 | 115 | } |
120 | 116 | |
117 | @Override | |
121 | 118 | protected void releaseMemory() { |
122 | 119 | streets = null; |
123 | 120 | } |
20 | 20 | |
21 | 21 | /** |
22 | 22 | * 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 | |
24 | 24 | * goes away. |
25 | 25 | * |
26 | 26 | * @author Steve Ratcliffe |
27 | 27 | */ |
28 | 28 | public class Mdr4 extends MdrSection implements HasHeaderFlags { |
29 | private final Set<Mdr4Record> poiTypes = new HashSet<Mdr4Record>(); | |
29 | private final Set<Mdr4Record> poiTypes = new HashSet<>(); | |
30 | 30 | |
31 | 31 | public Mdr4(MdrConfig config) { |
32 | 32 | setConfig(config); |
34 | 34 | |
35 | 35 | |
36 | 36 | public void writeSectData(ImgFileWriter writer) { |
37 | List<Mdr4Record> list = new ArrayList<Mdr4Record>(poiTypes); | |
37 | List<Mdr4Record> list = new ArrayList<>(poiTypes); | |
38 | 38 | list.sort(null); |
39 | 39 | |
40 | 40 | for (Mdr4Record r : list) { |
49 | 49 | } |
50 | 50 | |
51 | 51 | 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)); | |
62 | 53 | } |
63 | 54 | |
64 | 55 | /** |
16 | 16 | * The records in MDR 4 are a list of poi types with an unknown byte. |
17 | 17 | */ |
18 | 18 | public class Mdr4Record implements Comparable<Mdr4Record> { |
19 | private int type; | |
20 | private int subtype; | |
19 | private final int type; | |
20 | private final int subtype; | |
21 | 21 | private int unknown; |
22 | 22 | |
23 | public Mdr4Record(int type, int subtype) { | |
24 | this.type = type; | |
25 | this.subtype = subtype; | |
26 | } | |
27 | ||
23 | 28 | 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); | |
32 | 32 | } |
33 | 33 | |
34 | 34 | public boolean equals(Object o) { |
38 | 38 | Mdr4Record that = (Mdr4Record) o; |
39 | 39 | |
40 | 40 | if (subtype != that.subtype) return false; |
41 | if (type != that.type) return false; | |
42 | ||
43 | return true; | |
41 | return (type == that.type); | |
44 | 42 | } |
45 | 43 | |
46 | 44 | |
54 | 52 | return type; |
55 | 53 | } |
56 | 54 | |
57 | public void setType(int type) { | |
58 | this.type = type; | |
59 | } | |
60 | ||
61 | 55 | public int getSubtype() { |
62 | 56 | return subtype; |
63 | } | |
64 | ||
65 | public void setSubtype(int subtype) { | |
66 | this.subtype = subtype; | |
67 | 57 | } |
68 | 58 | |
69 | 59 | public int getUnknown() { |
51 | 51 | /** |
52 | 52 | * Called after all cities to sort and number them. |
53 | 53 | */ |
54 | @Override | |
54 | 55 | public void preWriteImpl() { |
55 | 56 | localCitySize = Utils.numberToPointerSize(maxCityIndex + 1); |
56 | 57 | |
281 | 282 | return val; |
282 | 283 | } |
283 | 284 | |
285 | @Override | |
284 | 286 | protected void releaseMemory() { |
285 | 287 | allCities = null; |
286 | 288 | cities = null; |
139 | 139 | st.setOutNameOffset((byte) outOffset); |
140 | 140 | st.setPrefixOffset((byte) prefix); |
141 | 141 | st.setSuffixOffset((byte) suffix); |
142 | //System.out.println(st.getName() + ": add partial " + st.getPartialName()); | |
143 | 142 | if (!exclNames.contains(st.getPartialName())) |
144 | 143 | storeMdr7(st); |
145 | 144 | |
201 | 200 | * This is a performance critical part of the index creation process |
202 | 201 | * as it requires a lot of heap to store the sort keys. |
203 | 202 | */ |
203 | @Override | |
204 | 204 | protected void preWriteImpl() { |
205 | 205 | |
206 | 206 | LargeListSorter<Mdr7Record> partialSorter = new LargeListSorter<Mdr7Record>(sort) { |
232 | 232 | |
233 | 233 | allStreets.trimToSize(); |
234 | 234 | streets.trimToSize(); |
235 | return; | |
236 | 235 | } |
237 | 236 | |
238 | 237 | /** |
355 | 354 | return magic; |
356 | 355 | } |
357 | 356 | |
357 | @Override | |
358 | 358 | protected void releaseMemory() { |
359 | 359 | allStreets = null; |
360 | 360 | streets = null; |
168 | 168 | return false; |
169 | 169 | if (suffixOffset != other.suffixOffset) |
170 | 170 | return false; |
171 | if (city != other.getCity()) | |
172 | return false; | |
173 | return true; | |
171 | return (city == other.getCity()); | |
174 | 172 | } |
175 | 173 | |
176 | 174 | /** |
183 | 181 | if (last == null) |
184 | 182 | return 0; |
185 | 183 | 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()); | |
189 | 185 | if (cmp == 0) |
190 | 186 | res = 1; |
191 | 187 | res |= checkFullRepeat(last, collator); |
202 | 198 | if (last == null) |
203 | 199 | return 0; |
204 | 200 | 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()); | |
208 | 202 | if (cmp == 0) |
209 | 203 | res |= 2; |
210 | 204 | return res; |
24 | 24 | public class Mdr8 extends MdrSection implements HasHeaderFlags { |
25 | 25 | private static final int STRING_WIDTH = 4; |
26 | 26 | |
27 | private List<Mdr8Record> index = new ArrayList<Mdr8Record>(); | |
27 | private List<Mdr8Record> index = new ArrayList<>(); | |
28 | 28 | |
29 | 29 | public Mdr8(MdrConfig config) { |
30 | 30 | setConfig(config); |
16 | 16 | * |
17 | 17 | * @author Steve Ratcliffe |
18 | 18 | */ |
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 | } | |
22 | 28 | |
23 | 29 | public char[] getPrefix() { |
24 | 30 | return prefix; |
25 | 31 | } |
26 | 32 | |
27 | public void setPrefix(char[] prefix) { | |
28 | this.prefix = prefix; | |
29 | } | |
30 | ||
31 | 33 | public int getRecordNumber() { |
32 | 34 | return recordNumber; |
33 | 35 | } |
34 | ||
35 | public void setRecordNumber(int recordNumber) { | |
36 | this.recordNumber = recordNumber; | |
37 | } | |
38 | 36 | } |
24 | 24 | * @author Steve Ratcliffe |
25 | 25 | */ |
26 | 26 | 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<>(); | |
28 | 28 | |
29 | 29 | public Mdr9(MdrConfig config) { |
30 | 30 | setConfig(config); |
33 | 33 | * @author Steve Ratcliffe |
34 | 34 | */ |
35 | 35 | public class MdrConfig { |
36 | //private static final int DEFAULT_HEADER_LEN = 286; | |
37 | 36 | private static final int DEFAULT_HEADER_LEN = 568; |
38 | 37 | |
39 | 38 | private boolean writable; |
129 | 128 | } |
130 | 129 | |
131 | 130 | public void setMdr7Excl(String exclList) { |
132 | mdr7Excl = StringToSet(exclList); | |
131 | mdr7Excl = stringToSet(exclList); | |
133 | 132 | } |
134 | 133 | |
135 | 134 | public Set<String> getMdr7Del() { |
137 | 136 | } |
138 | 137 | |
139 | 138 | public void setMdr7Del(String delList) { |
140 | mdr7Del = StringToSet(delList); | |
139 | mdr7Del = stringToSet(delList); | |
141 | 140 | } |
142 | 141 | |
143 | private Set<String> StringToSet (String opt) { | |
142 | private static Set<String> stringToSet (String opt) { | |
144 | 143 | Set<String> set; |
145 | 144 | |
146 | 145 | if (opt == null) |
151 | 150 | if (opt.endsWith("'") || opt.endsWith("\"")) |
152 | 151 | opt = opt.substring(0, opt.length() - 1); |
153 | 152 | List<String> list = Arrays.asList(opt.split(",")); |
154 | set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); | |
153 | set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); | |
155 | 154 | for (String s : list) { |
156 | 155 | set.add(s.trim()); |
157 | 156 | } |
188 | 187 | * @param start first type |
189 | 188 | * @param stop last type (included) |
190 | 189 | */ |
191 | private void genTypesForRange(Set<Integer> set, String start, String stop) { | |
190 | private static void genTypesForRange(Set<Integer> set, String start, String stop) { | |
192 | 191 | GType[] types = new GType[2]; |
193 | 192 | String[] ranges = {start, stop}; |
194 | 193 | boolean ok = true; |
210 | 209 | } |
211 | 210 | for (int i = types[0].getType(); i <= types[1].getType(); i++) { |
212 | 211 | if ((i & 0xff) > 0x1f) |
213 | i = ((i >> 8) + 1) << 8; | |
212 | i = ((i >> 8) + 1) << 8; | |
214 | 213 | set.add(i); |
215 | 214 | } |
216 | 215 |
50 | 50 | index.addPointer(mapNumber, recordNumber); |
51 | 51 | } |
52 | 52 | |
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; | |
55 | 55 | writer.putNu(getSizes().getCitySizeFlagged(), cityIndex | flag); |
56 | 56 | } |
57 | 57 |
196 | 196 | return sections[20].getSizeForRecord(); |
197 | 197 | } |
198 | 198 | |
199 | private int flagForSize(int size) { | |
199 | private static int flagForSize(int size) { | |
200 | 200 | int flag; |
201 | 201 | if (size == 1) |
202 | 202 | flag = 0x80; |
14 | 14 | import java.util.ArrayList; |
15 | 15 | import java.util.List; |
16 | 16 | |
17 | import uk.me.parabola.mkgmap.general.MapPoint; | |
17 | 18 | import uk.me.parabola.imgfmt.app.srt.Sort; |
18 | 19 | import uk.me.parabola.imgfmt.app.srt.SortKey; |
19 | 20 | |
26 | 27 | public static final int POI_INDEX_PREFIX_LEN = 4; |
27 | 28 | public static final int MAX_GROUP = 13; |
28 | 29 | |
30 | private MdrUtils () { | |
31 | // private constructor to hide the implicit public one. | |
32 | } | |
33 | ||
29 | 34 | /** |
30 | 35 | * Get the group number for the poi. This is the first byte of the records |
31 | 36 | * in mdr9. |
32 | 37 | * |
33 | 38 | * Not entirely sure about how this works yet. |
34 | 39 | * @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 | |
48 | 59 | */ |
49 | 60 | public static int getGroupForPoi(int fullType) { |
50 | 61 | // We group pois based on their type. This may not be the final thoughts on this. |
51 | 62 | int type = getTypeFromFullType(fullType); |
52 | 63 | int group = 0; |
53 | if (fullType <= 0xf) | |
64 | if (MapPoint.isCityType(fullType)) | |
54 | 65 | 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) | |
58 | 69 | 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; | |
62 | 72 | assert group >= 0 && group <= MAX_GROUP : "invalid group " + Integer.toHexString(group); |
63 | 73 | return group; |
64 | 74 | } |
68 | 78 | } |
69 | 79 | |
70 | 80 | 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; | |
75 | 82 | } |
76 | 83 | |
77 | 84 | /** |
80 | 87 | * @return If there is a subtype, then it is returned, else 0. |
81 | 88 | */ |
82 | 89 | public static int getSubtypeFromFullType(int fullType) { |
83 | return fullType < 0xff ? 0 : fullType & 0xff; | |
90 | return fullType & 0xff; | |
84 | 91 | } |
85 | 92 | |
86 | 93 | /** |
113 | 120 | */ |
114 | 121 | public static int fullTypeToNaturalType(int ftype) { |
115 | 122 | 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); | |
120 | 125 | return type << 5 | sub; |
121 | 126 | } |
122 | 127 | } |
16 | 16 | * Marks a record that has a name. |
17 | 17 | */ |
18 | 18 | public interface NamedRecord { |
19 | public int getMapIndex(); | |
20 | public String getName(); | |
19 | int getMapIndex(); | |
20 | String getName(); | |
21 | 21 | } |
51 | 51 | /** |
52 | 52 | * We can create an index for any type that has a name. |
53 | 53 | * @param list A list of items that have a name. |
54 | * @param grouped used with MDR7 records | |
54 | 55 | */ |
55 | 56 | public void createFromList(List<? extends NamedRecord> list, boolean grouped) { |
56 | 57 | maxIndex = list.size(); |
84 | 85 | int cmp = collator.compareOneStrengthWithLength(prefix, lastPrefix, Collator.PRIMARY, prefixLength); |
85 | 86 | if (cmp > 0) { |
86 | 87 | 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)); | |
91 | 89 | |
92 | 90 | lastPrefix = prefix; |
93 | 91 | |
106 | 104 | } |
107 | 105 | } |
108 | 106 | |
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 | */ | |
109 | 111 | public void createFromList(List<? extends NamedRecord> list) { |
110 | 112 | createFromList(list, false); |
111 | 113 | } |
16 | 16 | package uk.me.parabola.imgfmt.app.net; |
17 | 17 | |
18 | 18 | import java.util.ArrayList; |
19 | import java.util.BitSet; | |
20 | import java.util.Comparator; | |
19 | 21 | import java.util.HashMap; |
20 | 22 | import java.util.List; |
21 | 23 | import java.util.Map; |
26 | 28 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
27 | 29 | import uk.me.parabola.imgfmt.app.Label; |
28 | 30 | import uk.me.parabola.imgfmt.app.lbl.City; |
31 | import uk.me.parabola.imgfmt.app.lbl.Zip; | |
29 | 32 | import uk.me.parabola.imgfmt.app.srt.DoubleSortKey; |
30 | 33 | import uk.me.parabola.imgfmt.app.srt.IntegerSortKey; |
31 | 34 | import uk.me.parabola.imgfmt.app.srt.MultiSortKey; |
81 | 84 | |
82 | 85 | ImgFileWriter writer = netHeader.makeSortedRoadWriter(getWriter()); |
83 | 86 | try { |
84 | List<LabeledRoadDef> labeledRoadDefs = sortRoads(); | |
87 | List<LabeledRoadDef> labeledRoadDefs = deDupRoads(); | |
88 | sortByName(labeledRoadDefs); | |
85 | 89 | for (LabeledRoadDef labeledRoadDef : labeledRoadDefs) |
86 | 90 | labeledRoadDef.roadDef.putSortedRoadEntry(writer, labeledRoadDef.label); |
87 | 91 | } finally { |
93 | 97 | |
94 | 98 | /** |
95 | 99 | * 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() { | |
114 | 140 | List<SortKey<LabeledRoadDef>> sortKeys = new ArrayList<>(roads.size()); |
115 | Map<Label, byte[]> cache = new HashMap<>(); | |
116 | 141 | |
117 | 142 | for (RoadDef rd : roads) { |
118 | 143 | Label[] labels = rd.getLabels(); |
124 | 149 | // Sort by name, city, region/country and subdivision number. |
125 | 150 | LabeledRoadDef lrd = new LabeledRoadDef(label, rd); |
126 | 151 | SortKey<LabeledRoadDef> nameKey = new IntegerSortKey<>(lrd, label.getOffset(), 0); |
152 | ||
127 | 153 | // 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)); | |
129 | 155 | SortKey<LabeledRoadDef> cityKey; |
130 | 156 | if (city != null) { |
131 | 157 | int region = city.getRegionNumber(); |
132 | 158 | 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)); | |
135 | 160 | } else { |
136 | cityKey = sort.createSortKey(null, Label.NULL_OUT_LABEL, 0, cache); | |
161 | cityKey = sort.createSortKey(null, Label.NULL_OUT_LABEL, 0); | |
137 | 162 | } |
138 | 163 | |
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; | |
178 | 173 | } |
179 | 174 | |
180 | 175 | /** |
205 | 200 | * @param in A list of duplicate roads. |
206 | 201 | * @param out The list of sorted roads. Any new road is added to this. |
207 | 202 | */ |
208 | private void addDisconnected(List<LabeledRoadDef> in, List<LabeledRoadDef> out) { | |
203 | private static void analyseRoadsOfCity(List<LabeledRoadDef> in, List<LabeledRoadDef> out) { | |
209 | 204 | // switch out to different routines depending on the input size. A normal number of |
210 | 205 | // roads with the same name in the same city is a few tens. |
211 | 206 | if (in.size() > 200) { |
212 | addDisconnectedLarge(in, out); | |
207 | analyseRoadsOfCityLarge(in, out); | |
213 | 208 | } else { |
214 | addDisconnectedSmall(in, out); | |
209 | analyseRoadsOfCitySmall(in, out); | |
215 | 210 | } |
216 | 211 | } |
217 | 212 | |
221 | 216 | * This is done in an accurate manner which is slow for large numbers (eg thousands) of items in the |
222 | 217 | * input. |
223 | 218 | * |
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 | * | |
224 | 256 | * @param in Input set of roads with the same name. |
225 | 257 | * @param out List to add the discovered groups. |
226 | 258 | */ |
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) { | |
228 | 288 | // 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]; | |
230 | 291 | for (int i = 0; i < groups.length; i++) |
231 | 292 | groups[i] = i; |
232 | 293 | |
294 | // cache for results of RoadDef#connectedTo(RoadDef) where result was false. | |
295 | BitSet unconnected = new BitSet(inSize * inSize); | |
296 | ||
233 | 297 | // Go through pairs of roads, any that are connected we mark with the same (lowest) group number. |
234 | 298 | boolean done; |
235 | 299 | do { |
236 | 300 | done = true; |
237 | 301 | for (int current = 0; current < groups.length; current++) { |
238 | 302 | 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 | } | |
248 | 314 | } |
249 | 315 | } |
250 | 316 | } |
251 | 317 | } while (!done); |
252 | 318 | |
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++) { | |
256 | 322 | if (groups[i] > last) { |
257 | 323 | LabeledRoadDef lrd = in.get(i); |
258 | 324 | out.add(lrd); |
261 | 327 | } |
262 | 328 | } |
263 | 329 | |
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 | ||
296 | 330 | public void setNetwork(List<RoadDef> roads) { |
297 | 331 | this.roads = roads; |
298 | 332 | } |
51 | 51 | |
52 | 52 | private final NODHeader nodHeader = new NODHeader(); |
53 | 53 | |
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<>(); | |
57 | 57 | |
58 | 58 | public NODFile(ImgChannel chan, boolean write) { |
59 | 59 | setHeader(nodHeader); |
133 | 133 | private void writeBoundary() { |
134 | 134 | log.info("writeBoundary"); |
135 | 135 | |
136 | boundary.sort(null); | |
137 | ||
138 | 136 | ImgFileWriter writer = new SectionWriter(getWriter(), nodHeader.getBoundarySection()); |
139 | 137 | |
140 | 138 | boolean debug = log.isDebugEnabled(); |
141 | for (RouteNode node : boundary) { | |
139 | for (RouteNode node : boundaryNodes) { | |
142 | 140 | if(debug) |
143 | 141 | log.debug("wrting nod3", writer.position()); |
144 | 142 | node.writeNod3OrNod4(writer); |
165 | 163 | ImgFileWriter writer = new SectionWriter(getWriter(), section); |
166 | 164 | |
167 | 165 | boolean debug = log.isDebugEnabled(); |
168 | for (RouteNode node : boundary) { | |
166 | for (RouteNode node : boundaryNodes) { | |
169 | 167 | if (node.getNodeClass() == 0) |
170 | 168 | continue; |
171 | 169 | if(debug) |
180 | 178 | public void setNetwork(List<RouteCenter> centers, List<RoadDef> roads, List<RouteNode> boundary) { |
181 | 179 | this.centers = centers; |
182 | 180 | this.roads = roads; |
183 | this.boundary = boundary; | |
181 | this.boundaryNodes = new ArrayList<>(boundary); | |
182 | this.boundaryNodes.sort(null); | |
184 | 183 | } |
185 | 184 | |
186 | 185 | public void setDriveOnLeft(boolean dol) { |
12 | 12 | package uk.me.parabola.imgfmt.app.net; |
13 | 13 | |
14 | 14 | import java.io.ByteArrayOutputStream; |
15 | import java.util.ArrayList; | |
15 | 16 | import java.util.List; |
16 | 17 | |
17 | 18 | import uk.me.parabola.imgfmt.Utils; |
48 | 49 | CityZipWriter cityWriter; |
49 | 50 | |
50 | 51 | public NumberPreparer(List<Numbers> numbers) { |
51 | this.numbers = numbers; | |
52 | this.numbers = new ArrayList<>(numbers); | |
52 | 53 | this.zipWriter = new CityZipWriter("zip", 0, 0); |
53 | 54 | this.cityWriter = new CityZipWriter("city", 0, 0); |
54 | 55 | } |
157 | 158 | int lastNode = -1; |
158 | 159 | for (Numbers n : numbers) { |
159 | 160 | if (!n.hasIndex()) |
160 | throw new Abandon("no r node set"); | |
161 | throw new Abandon("no index set"); | |
161 | 162 | // See if we need to skip some nodes |
162 | 163 | if (n.getIndex() != lastNode + 1) |
163 | 164 | state.writeSkip(bw, n.getIndex() - lastNode - 2); |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.imgfmt.app.net; |
13 | 13 | |
14 | import java.util.Objects; | |
15 | ||
14 | 16 | import uk.me.parabola.log.Logger; |
15 | 17 | import uk.me.parabola.mkgmap.general.CityInfo; |
16 | 18 | import uk.me.parabola.mkgmap.general.ZipCodeInfo; |
26 | 28 | |
27 | 29 | private static final int MAX_DELTA = 131071; // see NumberPreparer |
28 | 30 | |
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 */ | |
34 | 42 | private RoadSide leftSide,rightSide; |
35 | 43 | |
36 | 44 | private class RoadSide { |
45 | 53 | |
46 | 54 | private class NumDesc{ |
47 | 55 | NumberStyle numberStyle; |
48 | int start,end; | |
56 | int start, end; | |
49 | 57 | |
50 | 58 | public NumDesc(NumberStyle numberStyle, int start, int end) { |
51 | 59 | this.numberStyle = numberStyle; |
52 | 60 | this.start = start; |
53 | 61 | this.end = end; |
54 | 62 | } |
55 | public boolean contained(int hn){ | |
63 | ||
64 | public boolean contained(int hn) { | |
56 | 65 | boolean isEven = (hn % 2 == 0); |
57 | if (numberStyle == NumberStyle.BOTH | |
66 | if (numberStyle == NumberStyle.BOTH | |
58 | 67 | || numberStyle == NumberStyle.EVEN && isEven |
59 | || numberStyle == NumberStyle.ODD && !isEven){ | |
68 | || numberStyle == NumberStyle.ODD && !isEven) { | |
60 | 69 | 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; | |
67 | 73 | } |
68 | 74 | } |
69 | 75 | return false; |
87 | 93 | */ |
88 | 94 | public Numbers(String spec) { |
89 | 95 | String[] strings = spec.split(","); |
90 | nodeNumber = Integer.valueOf(strings[0]); | |
96 | polishPointIndex = Integer.parseInt(strings[0]); | |
91 | 97 | 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]); | |
94 | 100 | setNumbers(LEFT, numberStyle, start, end); |
95 | 101 | 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]); | |
98 | 104 | setNumbers(RIGHT, numberStyle, start, end); |
99 | 105 | |
100 | 106 | if (strings.length > 8){ |
101 | 107 | // zip codes |
102 | 108 | String zip = strings[7]; |
103 | if ("-1".equals(zip) == false) | |
109 | if (!"-1".equals(zip)) | |
104 | 110 | setZipCode(LEFT, new ZipCodeInfo(zip)); |
105 | 111 | zip = strings[8]; |
106 | if ("-1".equals(zip) == false) | |
112 | if (!"-1".equals(zip)) | |
107 | 113 | setZipCode(RIGHT, new ZipCodeInfo(zip)); |
108 | 114 | } |
109 | if (strings.length > 9){ | |
110 | String city,region,country; | |
115 | if (strings.length > 9) { | |
116 | String city, region, country; | |
111 | 117 | int nextPos = 9; |
112 | 118 | city = strings[nextPos]; |
113 | if ("-1".equals(city) == false){ | |
119 | if (!"-1".equals(city)) { | |
114 | 120 | region = strings[nextPos + 1]; |
115 | 121 | country = strings[nextPos + 2]; |
116 | 122 | setCityInfo(LEFT, new CityInfo(city, region, country)); |
117 | 123 | nextPos = 12; |
118 | } else | |
124 | } else { | |
119 | 125 | nextPos = 10; |
126 | } | |
120 | 127 | city = strings[nextPos]; |
121 | if ("-1".equals(city) == false){ | |
128 | if (!"-1".equals(city)) { | |
122 | 129 | region = strings[nextPos + 1]; |
123 | 130 | country = strings[nextPos + 2]; |
124 | 131 | 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){ | |
130 | 137 | if (numberStyle != NumberStyle.NONE || start != -1 || end != -1){ |
131 | RoadSide rs = assureSideIsAllocated(left); | |
138 | RoadSide rs = assureSideIsAllocated(useLeft); | |
132 | 139 | rs.numbers = new NumDesc(numberStyle, start, end); |
133 | 140 | } else { |
134 | RoadSide rs = (left) ? leftSide : rightSide; | |
141 | RoadSide rs = (useLeft) ? leftSide : rightSide; | |
135 | 142 | if (rs != null) |
136 | 143 | rs.numbers = null; |
137 | removeIfEmpty(left); | |
144 | removeIfEmpty(useLeft); | |
138 | 145 | } |
139 | 146 | } |
140 | 147 | |
189 | 196 | return (left) ? leftSide : rightSide; |
190 | 197 | } |
191 | 198 | |
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 | */ | |
200 | 210 | 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; | |
206 | 216 | } |
207 | 217 | |
208 | 218 | public boolean hasIndex() { |
209 | return indexNumber != null; | |
219 | return nodeIndex >= 0; | |
210 | 220 | } |
211 | 221 | |
212 | 222 | /** |
213 | 223 | * @param index the nth number node |
214 | 224 | */ |
215 | 225 | public void setIndex(int index) { |
216 | this.indexNumber = index; | |
226 | this.nodeIndex = index; | |
217 | 227 | } |
218 | 228 | |
219 | 229 | private NumDesc getNumbers(boolean left) { |
238 | 248 | |
239 | 249 | public String toString() { |
240 | 250 | String nodeStr = "0"; |
241 | if (nodeNumber > 0) | |
242 | nodeStr = String.valueOf(nodeNumber); | |
251 | if (polishPointIndex > 0) | |
252 | nodeStr = Integer.toString(polishPointIndex); | |
243 | 253 | else if (getIndex() > 0) |
244 | 254 | nodeStr = String.format("(n%d)", getIndex()); |
245 | 255 | |
300 | 310 | return false; |
301 | 311 | if (!isPlausible(getRightNumberStyle(), getRightStart(), getRightEnd())) |
302 | 312 | 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))) { | |
305 | 317 | 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))) { | |
310 | 320 | 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) { | |
317 | 326 | // 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()) { | |
320 | 329 | start1 = getLeftStart(); |
321 | 330 | end1 = getLeftEnd(); |
322 | 331 | } else { |
323 | 332 | start1 = getLeftEnd(); |
324 | 333 | end1 = getLeftStart(); |
325 | 334 | } |
326 | if (getRightStart() < getRightEnd()){ | |
335 | if (getRightStart() < getRightEnd()) { | |
327 | 336 | start2 = getRightStart(); |
328 | 337 | end2 = getRightEnd(); |
329 | 338 | } else { |
332 | 341 | } |
333 | 342 | if (start2 > end1 || end2 < start1) |
334 | 343 | 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(); | |
339 | 347 | } |
340 | 348 | |
341 | 349 | return true; |
351 | 359 | return true; |
352 | 360 | } |
353 | 361 | |
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; | |
356 | 364 | if (rs == null || rs.numbers == null) |
357 | 365 | return false; |
358 | 366 | return rs.numbers.contained(hn); |
369 | 377 | matches++; |
370 | 378 | if (isContained(hn, RIGHT)) |
371 | 379 | 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 | |
375 | 382 | } |
376 | 383 | return matches; |
377 | 384 | } |
384 | 391 | public boolean isSimilar(Numbers other){ |
385 | 392 | if (other == null) |
386 | 393 | 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() | |
389 | 397 | || getRightNumberStyle() != other.getRightNumberStyle() |
390 | || getRightStart() != other.getRightStart() || getRightEnd() != other.getRightEnd()) | |
391 | return false; | |
392 | return true; | |
398 | || getRightStart() != other.getRightStart() | |
399 | || getRightEnd() != other.getRightEnd()); | |
393 | 400 | |
394 | 401 | } |
395 | 402 |
152 | 152 | // for diagnostic purposes |
153 | 153 | private final long id; |
154 | 154 | private final String name; |
155 | ||
156 | // road/address search data from (house) numbers | |
155 | 157 | private List<Numbers> numbersList; |
156 | 158 | private List<City> cityList; |
157 | 159 | 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 | ||
160 | 166 | public RoadDef(long id, String name) { |
161 | 167 | this.id = id; |
162 | 168 | this.name = name; |
178 | 184 | // for diagnostic purposes |
179 | 185 | public String toString() { |
180 | 186 | // 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 + ")"; | |
186 | 188 | } |
187 | 189 | |
188 | 190 | public String getName() { |
230 | 232 | writeLevelDivs(writer, maxlevel); |
231 | 233 | |
232 | 234 | 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 | |
240 | 242 | int len, flag; |
241 | 243 | |
242 | 244 | ByteArrayOutputStream zipBuf = null, cityBuf = null; |
244 | 246 | if (len > 0){ |
245 | 247 | zipBuf = numbers.zipWriter.getBuffer(); |
246 | 248 | flag = Utils.numberToPointerSize(len) - 1; |
247 | } else | |
249 | } else { | |
248 | 250 | flag = (zip == null) ? 3 : 2; |
251 | } | |
249 | 252 | code |= flag << 2; |
250 | 253 | |
251 | 254 | len = (numbers == null) ? 0: numbers.cityWriter.getBuffer().size(); |
252 | 255 | if (len > 0){ |
253 | 256 | cityBuf = numbers.cityWriter.getBuffer(); |
254 | 257 | flag = Utils.numberToPointerSize(len) - 1; |
255 | } else | |
258 | } else { | |
256 | 259 | flag = (city == null) ? 3 : 2; |
260 | } | |
257 | 261 | code |= flag << 4; |
258 | 262 | |
259 | 263 | len = (numbers == null) ? 0 : numbers.fetchBitStream().getLength(); |
260 | 264 | if (len > 0){ |
261 | 265 | flag = Utils.numberToPointerSize(len) - 1; |
262 | } else | |
266 | } else { | |
263 | 267 | flag = 3; |
268 | } | |
264 | 269 | code |= flag << 6; |
265 | 270 | |
266 | 271 | writer.put1u(code); |
267 | // System.out.printf("%d %d %d\n", (code >> 2 & 0x3), (code >> 4 & 0x3), (code >> 6 & 0x3)); | |
268 | 272 | |
269 | 273 | if (zipBuf != null){ |
270 | 274 | len = zipBuf.size(); |
381 | 385 | if(log.isDebugEnabled()) |
382 | 386 | log.debug("adding polyline ref", this, pl.getSubdiv()); |
383 | 387 | 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)); | |
390 | 389 | |
391 | 390 | if (level == 0) { |
392 | nodeCount += pl.getNodeCount(hasHouseNumbers()); | |
391 | nodeCountInner += pl.getNodeCount(hasHouseNumbers()); | |
393 | 392 | } |
394 | 393 | } |
395 | 394 | |
426 | 425 | * Set the road length (in meters). |
427 | 426 | */ |
428 | 427 | public void setLength(double lenInMeter) { |
428 | this.lenInMeter = (int) Math.round(lenInMeter); | |
429 | 429 | roadLength = NODHeader.metersToRaw(lenInMeter); |
430 | 430 | } |
431 | 431 | |
480 | 480 | rgn.position(off.getPosition()); |
481 | 481 | rgn.put3u(offsetNet1 | off.getFlags()); |
482 | 482 | } |
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; | |
499 | 483 | } |
500 | 484 | |
501 | 485 | /** |
573 | 557 | // If the road has house numbers, we count also |
574 | 558 | // the number nodes, and these get a 0 in the bit stream. |
575 | 559 | int nbits = nnodes; |
576 | if (!startsWithNode) | |
560 | if (!startsWithNode && !hasHouseNumbers()) | |
577 | 561 | nbits++; |
578 | 562 | writer.put2u(nbits); |
579 | 563 | boolean[] bits = new boolean[nbits]; |
580 | 564 | |
581 | 565 | if (hasHouseNumbers()){ |
582 | int off = startsWithNode ? 0 :1; | |
583 | 566 | for (int i = 0; i < bits.length; i++){ |
584 | 567 | if (nod2BitSet.get(i)) |
585 | bits[i+off] = true; | |
568 | bits[i] = true; | |
586 | 569 | } |
587 | 570 | } else { |
588 | 571 | for (int i = 0; i < bits.length; i++) |
592 | 575 | } |
593 | 576 | for (int i = 0; i < bits.length; i += 8) { |
594 | 577 | 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++) { | |
596 | 579 | if (bits[i+j]) |
597 | 580 | b |= 1 << j; |
581 | } | |
598 | 582 | writer.put1u(b); |
599 | 583 | } |
600 | 584 | } |
745 | 729 | if (cityList == null){ |
746 | 730 | cityList = new ArrayList<>(2); |
747 | 731 | } |
748 | if (cityList.contains(city) == false) | |
732 | if (!cityList.contains(city)) | |
749 | 733 | cityList.add(city); |
750 | 734 | } |
751 | 735 | |
758 | 742 | if (zipList == null){ |
759 | 743 | zipList = new ArrayList<>(2); |
760 | 744 | } |
761 | if (zipList.contains(zip) == false) | |
745 | if (!zipList.contains(zip)) | |
762 | 746 | zipList.add(zip); |
763 | 747 | } |
764 | 748 | |
835 | 819 | |
836 | 820 | public void skipAddToNOD(boolean skip) { |
837 | 821 | this.skipAddToNOD = skip; |
822 | if (hasNodInfo()) { | |
823 | log.info("road", this, "is removed from NOD, length:", lenInMeter, "m"); | |
824 | netFlags &= ~NET_FLAG_NODINFO; | |
825 | } | |
838 | 826 | } |
839 | 827 | |
840 | 828 | public void resetImgData() { |
854 | 842 | } |
855 | 843 | } |
856 | 844 | } |
845 | ||
846 | public double getLenInMeter() { | |
847 | return lenInMeter; | |
848 | } | |
857 | 849 | |
858 | 850 | } |
18 | 18 | import java.util.ArrayList; |
19 | 19 | import java.util.Arrays; |
20 | 20 | import java.util.BitSet; |
21 | import java.util.Collection; | |
22 | import java.util.Collections; | |
23 | import java.util.HashSet; | |
21 | 24 | import java.util.Iterator; |
22 | 25 | import java.util.LinkedHashMap; |
23 | 26 | import java.util.List; |
24 | 27 | import java.util.Map; |
25 | 28 | import java.util.Map.Entry; |
29 | import java.util.Set; | |
26 | 30 | import java.util.TreeMap; |
27 | 31 | |
28 | 32 | import uk.me.parabola.imgfmt.app.Coord; |
29 | 33 | import uk.me.parabola.imgfmt.app.CoordNode; |
30 | 34 | import uk.me.parabola.log.Logger; |
31 | 35 | import uk.me.parabola.util.EnhancedProperties; |
36 | import uk.me.parabola.util.MultiHashMap; | |
32 | 37 | |
33 | 38 | /** |
34 | 39 | * This holds the road network. That is all the roads and the nodes |
40 | 45 | public class RoadNetwork { |
41 | 46 | private static final Logger log = Logger.getLogger(RoadNetwork.class); |
42 | 47 | |
43 | private final static int MAX_RESTRICTIONS_ARCS = 7; | |
48 | private static final int MAX_RESTRICTIONS_ARCS = 7; | |
44 | 49 | private final Map<Integer, RouteNode> nodes = new LinkedHashMap<>(); |
45 | 50 | |
46 | 51 | // boundary nodes |
54 | 59 | private boolean checkRoundaboutFlares; |
55 | 60 | private int maxFlareLengthRatio ; |
56 | 61 | private boolean reportSimilarArcs; |
62 | private boolean routable; | |
63 | ||
64 | private long maxSumRoadLenghts; | |
65 | /** for route island search */ | |
66 | private int visitId; | |
57 | 67 | |
58 | 68 | public void config(EnhancedProperties props) { |
59 | 69 | checkRoundabouts = props.getProperty("check-roundabouts", false); |
60 | 70 | checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false); |
61 | 71 | maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0); |
62 | 72 | reportSimilarArcs = props.getProperty("report-similar-arcs", false); |
73 | maxSumRoadLenghts = props.getProperty("check-routing-island-len", -1); | |
74 | routable = props.containsKey("route"); | |
63 | 75 | angleChecker.config(props); |
64 | 76 | } |
65 | 77 | |
66 | 78 | public void addRoad(RoadDef roadDef, List<Coord> coordList) { |
67 | 79 | roadDefs.add(roadDef); |
68 | 80 | |
69 | CoordNode lastCoord = null; | |
70 | int lastIndex = 0; | |
81 | int lastNodePos = -1; | |
71 | 82 | double roadLength = 0; |
72 | 83 | double arcLength = 0; |
73 | 84 | int pointsHash = 0; |
74 | 85 | |
75 | 86 | int npoints = coordList.size(); |
76 | 87 | int numCoordNodes = 0; |
77 | boolean hasInternalNodes = false; | |
78 | 88 | int numNumberNodes = 0; |
79 | 89 | BitSet nodeFlags = new BitSet(); |
80 | 90 | for (int index = 0; index < npoints; index++) { |
81 | 91 | 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 | |
85 | 101 | nodeFlags.set(numNumberNodes); |
86 | 102 | ++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 | } | |
89 | 116 | } |
90 | 117 | if (co.isNumberNode()) |
91 | 118 | ++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(); | |
199 | 119 | } |
200 | 120 | if (roadDef.hasHouseNumbers()){ |
201 | // we ignore number nodes when we have no house numbers | |
202 | if (numCoordNodes < numNumberNodes) | |
203 | hasInternalNodes = true; | |
204 | 121 | roadDef.setNumNodes(numNumberNodes); |
205 | 122 | roadDef.setNod2BitSet(nodeFlags); |
206 | 123 | } else { |
124 | // we ignore number nodes when we have no house numbers | |
207 | 125 | roadDef.setNumNodes(numCoordNodes); |
208 | 126 | } |
209 | if (hasInternalNodes) | |
210 | roadDef.setInternalNodes(true); | |
211 | 127 | 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 | ||
214 | 223 | 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)); | |
223 | 225 | } |
224 | 226 | |
225 | 227 | public List<RoadDef> getRoadDefs() { |
227 | 229 | } |
228 | 230 | |
229 | 231 | /** |
230 | * Split the network into RouteCenters. | |
232 | * Split the network into RouteCenters, calls nodes.clear(). | |
231 | 233 | * |
232 | 234 | * The resulting centers must satisfy several constraints, |
233 | 235 | * documented in NOD1Part. |
244 | 246 | NOD1Part nod1 = new NOD1Part(); |
245 | 247 | int n = 0; |
246 | 248 | 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 | } | |
260 | 255 | } |
261 | 256 | if (n > 0) |
262 | 257 | centers.addAll(nod1.subdivide()); |
263 | 258 | } |
264 | 259 | } |
265 | 260 | |
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 | ||
266 | 272 | 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 | ||
268 | 281 | angleChecker.check(nodes); |
269 | 282 | addArcsToMajorRoads(); |
270 | 283 | splitCenters(); |
271 | 284 | } |
272 | 285 | 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); | |
273 | 388 | } |
274 | 389 | |
275 | 390 | /** |
293 | 408 | * Currently empty. |
294 | 409 | */ |
295 | 410 | public List<RouteNode> getBoundary() { |
296 | return boundary; | |
411 | return Collections.unmodifiableList(boundary); | |
297 | 412 | } |
298 | 413 | |
299 | 414 | /** |
479 | 594 | List<RouteArc> arcs = arcLists.get(i); |
480 | 595 | int countNoEffect = 0; |
481 | 596 | int countOneway= 0; |
482 | for (int j = arcs.size()-1; j >= 0; --j){ | |
597 | for (int j = arcs.size() - 1; j >= 0; --j) { | |
483 | 598 | RouteArc arc = arcs.get(j); |
484 | if (isUsable(arc.getRoadDef().getAccess(), grr.getExceptionMask()) == false){ | |
599 | if (!isUsable(arc.getRoadDef().getAccess(), grr.getExceptionMask())) { | |
485 | 600 | countNoEffect++; |
486 | 601 | 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); | |
493 | 605 | } |
494 | 606 | } |
495 | 607 | String arcType = null; |
505 | 617 | else |
506 | 618 | arcType = "via way is"; |
507 | 619 | String reason; |
508 | if (countNoEffect > 0 & countOneway > 0) | |
620 | if (countNoEffect > 0 && countOneway > 0) | |
509 | 621 | reason = "wrong direction in oneway or not accessible for restricted vehicles"; |
510 | 622 | else if (countNoEffect > 0) |
511 | 623 | reason = "not accessible for restricted vehicles"; |
537 | 649 | byte pathNoAccessMask = 0; |
538 | 650 | for (int j = 0; j < indexes.length; j++){ |
539 | 651 | RouteArc arc = arcLists.get(j).get(indexes[j]); |
540 | if (arc.getDest() == vn || viaNodeFound == false){ | |
652 | if (!viaNodeFound || arc.getDest() == vn){ | |
541 | 653 | arc = arc.getReverseArc(); |
542 | 654 | } |
543 | 655 | if (arc.getSource() == vn) |
576 | 688 | } |
577 | 689 | |
578 | 690 | /** |
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 | |
583 | 695 | */ |
584 | 696 | 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; | |
588 | 698 | } |
589 | 699 | |
590 | 700 | private int addNoThroughRoute(GeneralRouteRestriction grr) { |
598 | 708 | } |
599 | 709 | int added = 0; |
600 | 710 | |
601 | for (RouteArc out: vn.arcsIteration()){ | |
711 | for (RouteArc out : vn.arcsIteration()) { | |
602 | 712 | if (!out.isDirect()) |
603 | 713 | continue; |
604 | for (RouteArc in: vn.arcsIteration()){ | |
714 | for (RouteArc in : vn.arcsIteration()) { | |
605 | 715 | if (!in.isDirect() || in == out || in.getDest() == out.getDest()) |
606 | 716 | continue; |
607 | 717 | 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())); | |
610 | 720 | 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); | |
614 | 723 | } |
615 | 724 | } |
616 | 725 | } |
648 | 757 | * @return |
649 | 758 | */ |
650 | 759 | private static Integer getBetterAngle (Integer angle1, Integer angle2, char dirIndicator){ |
651 | switch (dirIndicator){ | |
760 | switch (dirIndicator) { | |
652 | 761 | case 'l': |
653 | if (Math.abs(-90-angle2) < Math.abs(-90-angle1)) | |
762 | if (Math.abs(-90 - angle2) < Math.abs(-90 - angle1)) | |
654 | 763 | return angle2; // closer to -90 |
655 | 764 | break; |
656 | 765 | case 'r': |
657 | if (Math.abs(90-angle2) < Math.abs(90-angle1)) | |
766 | if (Math.abs(90 - angle2) < Math.abs(90 - angle1)) | |
658 | 767 | return angle2; // closer to 90 |
659 | 768 | 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; | |
663 | 772 | if (Math.abs(d2) < Math.abs(d1)) |
664 | 773 | return angle2; // closer to -180 |
665 | 774 | break; |
666 | case 's': | |
775 | case 's': | |
667 | 776 | if (Math.abs(angle2) < Math.abs(angle1)) |
668 | 777 | return angle2; // closer to 0 |
669 | 778 | break; |
670 | } | |
671 | ||
779 | default: | |
780 | } | |
672 | 781 | return angle1; |
673 | 782 | } |
674 | 783 | |
679 | 788 | * @return |
680 | 789 | */ |
681 | 790 | private static boolean matchDirectionInfo (float angle, char dirIndicator){ |
682 | switch (dirIndicator){ | |
791 | switch (dirIndicator) { | |
683 | 792 | case 'l': |
684 | if (angle < -3 && angle > - 177) | |
793 | if (angle < -3 && angle > -177) | |
685 | 794 | return true; |
686 | 795 | break; |
687 | 796 | case 'r': |
700 | 809 | return true; |
701 | 810 | } |
702 | 811 | return false; |
703 | } | |
704 | ||
812 | } | |
705 | 813 | |
706 | 814 | } |
13 | 13 | */ |
14 | 14 | package uk.me.parabola.imgfmt.app.net; |
15 | 15 | |
16 | import it.unimi.dsi.fastutil.ints.IntArrayList; | |
16 | import java.util.ArrayDeque; | |
17 | 17 | import java.util.ArrayList; |
18 | 18 | import java.util.Collections; |
19 | import java.util.Deque; | |
19 | 20 | import java.util.HashSet; |
20 | import java.util.Iterator; | |
21 | 21 | import java.util.List; |
22 | 22 | |
23 | import it.unimi.dsi.fastutil.ints.IntArrayList; | |
23 | 24 | import uk.me.parabola.imgfmt.Utils; |
24 | 25 | import uk.me.parabola.imgfmt.app.Coord; |
25 | 26 | import uk.me.parabola.imgfmt.app.CoordNode; |
111 | 112 | * get all direct arcs to the given node and the given way id |
112 | 113 | * @param otherNode |
113 | 114 | * @param roadId |
114 | * @return | |
115 | * @return list with the direct arcs | |
115 | 116 | */ |
116 | 117 | public List<RouteArc> getDirectArcsTo(RouteNode otherNode, long roadId) { |
117 | 118 | 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); | |
122 | 122 | } |
123 | 123 | } |
124 | 124 | return result; |
127 | 127 | /** |
128 | 128 | * get all direct arcs on a given way id |
129 | 129 | * @param roadId |
130 | * @return | |
130 | * @return @return list with the direct arcs | |
131 | 131 | */ |
132 | 132 | public List<RouteArc> getDirectArcsOnWay(long roadId) { |
133 | 133 | 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); | |
138 | 137 | } |
139 | 138 | } |
140 | 139 | return result; |
147 | 146 | * @return |
148 | 147 | */ |
149 | 148 | 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; | |
154 | 152 | } |
155 | 153 | } |
156 | 154 | return null; |
228 | 226 | int index = 0; |
229 | 227 | for (RouteArc arc: arcs){ |
230 | 228 | 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++; | |
237 | 233 | } |
238 | 234 | arc.write(writer, lastArc, useCompactDirs, compactedDir); |
239 | 235 | lastArc = arc; |
261 | 257 | public void discard() { |
262 | 258 | // mark the node as having been discarded |
263 | 259 | 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; | |
264 | 267 | } |
265 | 268 | |
266 | 269 | public int getOffsetNod1() { |
321 | 324 | return nodeClass; |
322 | 325 | } |
323 | 326 | |
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(); | |
330 | 329 | } |
331 | 330 | |
332 | 331 | public List<RouteRestriction> getRestrictions() { |
659 | 658 | public void reportSimilarArcs() { |
660 | 659 | for(int i = 0; i < arcs.size(); ++i) { |
661 | 660 | RouteArc arci = arcs.get(i); |
662 | if (arci.isDirect() == false) | |
661 | if (!arci.isDirect()) | |
663 | 662 | continue; |
664 | 663 | for(int j = i + 1; j < arcs.size(); ++j) { |
665 | 664 | RouteArc arcj = arcs.get(j); |
666 | if (arcj.isDirect() == false) | |
665 | if (!arcj.isDirect()) | |
667 | 666 | continue; |
668 | 667 | if(arci.getDest() == arcj.getDest() && |
669 | 668 | arci.getLength() == arcj.getLength() && |
706 | 705 | RouteNode next = null; |
707 | 706 | for (int i = 0; i < current.arcs.size(); i++){ |
708 | 707 | 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 | } | |
722 | 719 | } |
723 | 720 | current = next; |
724 | 721 | } |
817 | 814 | */ |
818 | 815 | public int getGroup() { |
819 | 816 | if (nodeGroup < 0){ |
817 | if (arcs.isEmpty()) { | |
818 | nodeGroup = 0; | |
819 | return nodeGroup; | |
820 | } | |
820 | 821 | HashSet<RoadDef> roads = new HashSet<>(); |
821 | 822 | for (RouteArc arc: arcs){ |
822 | 823 | roads.add(arc.getRoadDef()); |
856 | 857 | public List<RouteArc> getArcs() { |
857 | 858 | return arcs; |
858 | 859 | } |
859 | ||
860 | public int hashCode(){ | |
861 | return getCoord().getId(); | |
860 | ||
861 | @Override | |
862 | public int hashCode() { | |
863 | return getCoord().getId(); | |
862 | 864 | } |
863 | 865 | |
864 | 866 | public List<RouteArc> getDirectArcsBetween(RouteNode otherNode) { |
871 | 873 | return result; |
872 | 874 | } |
873 | 875 | |
876 | /** used to find routing island, but may also be used for other checks */ | |
877 | private int visitId; | |
874 | 878 | |
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 | } | |
875 | 897 | } |
19 | 19 | * @author Steve Ratcliffe |
20 | 20 | */ |
21 | 21 | 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; | |
25 | 25 | |
26 | public char getPrimary() { | |
26 | public int getPrimary() { | |
27 | 27 | return primary; |
28 | 28 | } |
29 | 29 | |
30 | public byte getSecondary() { | |
30 | public int getSecondary() { | |
31 | 31 | return secondary; |
32 | 32 | } |
33 | 33 | |
34 | public byte getTertiary() { | |
34 | public int getTertiary() { | |
35 | 35 | return tertiary; |
36 | 36 | } |
37 | 37 | |
46 | 46 | case Collator.PRIMARY: |
47 | 47 | return primary; |
48 | 48 | case Collator.SECONDARY: |
49 | return secondary & 0xff; | |
49 | return secondary; | |
50 | 50 | case Collator.TERTIARY: |
51 | return tertiary & 0xff; | |
51 | return tertiary; | |
52 | 52 | default: |
53 | 53 | return 0; |
54 | 54 | } |
55 | 55 | } |
56 | 56 | |
57 | public void setPrimary(char primary) { | |
57 | public void setPrimary(int primary) { | |
58 | 58 | this.primary = primary; |
59 | 59 | } |
60 | 60 | |
61 | public void setSecondary(byte secondary) { | |
61 | public void setSecondary(int secondary) { | |
62 | 62 | this.secondary = secondary; |
63 | 63 | } |
64 | 64 | |
65 | public void setTertiary(byte tertiary) { | |
65 | public void setTertiary(int tertiary) { | |
66 | 66 | this.tertiary = tertiary; |
67 | 67 | } |
68 | 68 | } |
23 | 23 | private final int first; |
24 | 24 | private final int second; |
25 | 25 | |
26 | //public CombinedSortKey(SortKey<T> key, int first) { | |
27 | // this(key, first, 0); | |
28 | //} | |
29 | ||
30 | 26 | public CombinedSortKey(SortKey<T> obj, int first, int second) { |
31 | 27 | this.key = obj; |
32 | 28 | this.first = first; |
41 | 37 | CombinedSortKey<T> other = (CombinedSortKey<T>) o; |
42 | 38 | int res = key.compareTo(other.key); |
43 | 39 | if (res == 0) { |
44 | res = compareInts(first, other.first); | |
40 | res = Integer.compare(first, other.first); | |
45 | 41 | if (res == 0) { |
46 | res = compareInts(second, other.second); | |
42 | res = Integer.compare(second, other.second); | |
47 | 43 | } |
48 | 44 | } |
49 | 45 | return res; |
50 | 46 | } |
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 | } | |
62 | 47 | } |
105 | 105 | int secondary = sort.getSecondary(i); |
106 | 106 | int tertiary = sort.getTertiary(i); |
107 | 107 | 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; | |
111 | 111 | writer.put2u(primary); |
112 | 112 | writer.put1u(secondary); |
113 | 113 | writer.put1u(tertiary); |
114 | 114 | } 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; | |
118 | 118 | writer.put1u(primary); |
119 | 119 | writer.put1u((tertiary << 4) | (secondary & 0xf)); |
120 | 120 | } |
134 | 134 | for (int i = 0; i < 256; i++) { |
135 | 135 | if (((p.flags[i] >>> 4) & 0xf) == 0) { |
136 | 136 | if (p.getPrimary(i) != 0) { |
137 | byte second = p.getSecondary(i); | |
137 | int second = p.getSecondary(i); | |
138 | 138 | maxSecondary = Math.max(maxSecondary, second); |
139 | 139 | if (second != 0) { |
140 | 140 | maxTertiary = Math.max(maxTertiary, p.getTertiary(i)); |
360 | 360 | // We need +1 for the null bytes, we also +2 for a couple of expanded characters. For a complete |
361 | 361 | // german map this was always enough in tests. |
362 | 362 | byte[] key = new byte[(chars.length + 1 + 2) * 4]; |
363 | int needed = 0; | |
363 | int needed; | |
364 | 364 | try { |
365 | 365 | needed = fillCompleteKey(chars, key); |
366 | 366 | } catch (ArrayIndexOutOfBoundsException e) { |
596 | 596 | |
597 | 597 | /** |
598 | 598 | * Allocate space for up to n pages. |
599 | * @param n | |
599 | * @param n Number of pages | |
600 | 600 | */ |
601 | 601 | public void setMaxPage(int n) { |
602 | 602 | pages = Arrays.copyOf(pages, n + 1); |
625 | 625 | private final byte[] tertiary = new byte[256]; |
626 | 626 | private final byte[] flags = new byte[256]; |
627 | 627 | |
628 | char getPrimary(int ch) { | |
628 | int getPrimary(int ch) { | |
629 | 629 | return primary[ch & 0xff]; |
630 | 630 | } |
631 | 631 | |
633 | 633 | primary[ch & 0xff] = (char) val; |
634 | 634 | } |
635 | 635 | |
636 | byte getSecondary(int ch) { | |
637 | return secondary[ch & 0xff]; | |
636 | int getSecondary(int ch) { | |
637 | return secondary[ch & 0xff] & 0xff; | |
638 | 638 | } |
639 | 639 | |
640 | 640 | void setSecondary(int ch, int val) { |
641 | 641 | secondary[ch & 0xff] = (byte) val; |
642 | 642 | } |
643 | 643 | |
644 | byte getTertiary(int ch) { | |
645 | return tertiary[ch & 0xff]; | |
644 | int getTertiary(int ch) { | |
645 | return tertiary[ch & 0xff] & 0xff; | |
646 | 646 | } |
647 | 647 | |
648 | 648 | void setTertiary(int ch, int val) { |
658 | 658 | public int getPos(int type, int ch) { |
659 | 659 | switch (type) { |
660 | 660 | case Collator.PRIMARY: |
661 | return getPrimary(ch) & 0xffff; | |
661 | return getPrimary(ch); | |
662 | 662 | case Collator.SECONDARY: |
663 | return getSecondary(ch) & 0xff; | |
663 | return getSecondary(ch); | |
664 | 664 | case Collator.TERTIARY: |
665 | return getTertiary(ch) & 0xff; | |
665 | return getTertiary(ch); | |
666 | 666 | default: |
667 | 667 | assert false : "bad collation type passed"; |
668 | 668 | return 0; |
849 | 849 | } |
850 | 850 | |
851 | 851 | // Get the first non-ignorable at this level |
852 | int c = chars[(pos++ & 0xff)]; | |
852 | int c = chars[pos++ & 0xff]; | |
853 | 853 | if (!hasPage(c >>> 8)) { |
854 | 854 | next = 0; |
855 | 855 | continue; |
172 | 172 | int rec; |
173 | 173 | if (posLength == 2) { |
174 | 174 | 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); | |
178 | 178 | |
179 | 179 | } else if (posLength == 3) { |
180 | // what is the extra byte for ??? | |
180 | 181 | 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); | |
184 | 185 | } else if (posLength == 4) { |
185 | 186 | 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); | |
189 | 190 | } else { |
190 | 191 | throw new RuntimeException("unexpected value posLength " + posLength); |
191 | 192 | } |
30 | 30 | // These are our inputs. |
31 | 31 | private final Polyline polyline; |
32 | 32 | |
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; | |
34 | 35 | private final boolean extTypeLine; |
35 | 36 | private boolean xSameSign; |
36 | 37 | private boolean xSignNegative; // Set if all negative |
46 | 47 | private int[] deltas; |
47 | 48 | private boolean[] nodes; |
48 | 49 | |
49 | private boolean ignoreNumberOnlyNodes; | |
50 | private final boolean ignoreNumberOnlyNodes; | |
50 | 51 | |
51 | 52 | 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(); | |
62 | 55 | extTypeLine = line.hasExtendedType(); |
63 | 56 | |
64 | 57 | polyline = line; |
274 | 267 | int lon = subdiv.roundLonToLocalShifted(co.getLongitude()); |
275 | 268 | if (log.isDebugEnabled()) |
276 | 269 | log.debug("shifted pos", lat, lon); |
277 | if (first) { | |
278 | lastLat = lat; | |
279 | lastLong = lon; | |
280 | first = false; | |
281 | continue; | |
282 | } | |
283 | 270 | |
284 | 271 | int dx = lon - lastLong; |
285 | 272 | int dy = lat - lastLat; |
286 | 273 | lastLong = lon; |
287 | 274 | 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 | } | |
293 | 279 | |
294 | 280 | /* |
295 | 281 | * Current thought is that the node indicator is set when |
300 | 286 | * polyline making up the road. |
301 | 287 | */ |
302 | 288 | if (extraBit) { |
289 | boolean isSpecialNode = co.getId() > 0 || (!ignoreNumberOnlyNodes && co.isNumberNode()); | |
290 | if (dx != 0 || dy != 0 || isSpecialNode) | |
291 | firstsame = i; | |
303 | 292 | boolean extra = false; |
304 | 293 | if (isSpecialNode) { |
305 | 294 | if (i < nodes.length - 1) |
49 | 49 | boolean hasSubtype = false; |
50 | 50 | int type = getType(); |
51 | 51 | 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; | |
58 | 55 | } |
56 | type >>= 8; | |
59 | 57 | |
60 | 58 | file.put1u(type); |
61 | 59 |
232 | 232 | return roaddef != null; |
233 | 233 | } |
234 | 234 | |
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; | |
237 | 249 | } |
238 | 250 | |
239 | 251 | public void setLastSegment(boolean last) { |
257 | 269 | public boolean sharesNodeWith(Polyline other) { |
258 | 270 | for (Coord p1 : points) { |
259 | 271 | 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 | |
261 | 273 | // same node |
262 | 274 | for (Coord p2 : other.points) |
263 | 275 | if (p1.getId() == p2.getId()) |
277 | 289 | } |
278 | 290 | |
279 | 291 | /** |
280 | * | |
292 | * Count special nodes between (excluding) first and last node. | |
281 | 293 | * @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 | |
283 | 295 | */ |
284 | 296 | public int getNodeCount(boolean countAllNodes ) { |
285 | int idx = 0; | |
286 | 297 | int count = 0; |
287 | 298 | |
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()) | |
290 | 302 | count++; |
291 | 303 | } |
292 | 304 | return count; |
101 | 101 | |
102 | 102 | int t = reader.get1u(); |
103 | 103 | 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; | |
111 | 106 | |
112 | 107 | Label l; |
113 | 108 | int labelOffset = val & 0x3fffff; |
126 | 121 | p.setDeltaLong(reader.get2s()); |
127 | 122 | p.setDeltaLat(reader.get2s()); |
128 | 123 | |
124 | t <<= 8; | |
129 | 125 | if (hasSubtype) { |
130 | 126 | int st = reader.get1u(); |
131 | p.setType((t << 8) | st); | |
127 | t |= st; | |
132 | 128 | //p.setHasSubtype(true); |
133 | } else { | |
134 | p.setType(t); | |
135 | } | |
129 | } | |
130 | p.setType(t); | |
136 | 131 | |
137 | 132 | p.setNumber(number++); |
138 | 133 | points.add(p); |
52 | 52 | private final EnhancedProperties args = new EnhancedProperties(); |
53 | 53 | private Set<String> validOptions; |
54 | 54 | |
55 | { | |
55 | public CommandArgsReader(ArgumentProcessor proc) { | |
56 | this.proc = proc; | |
56 | 57 | // Set some default values. It is as if these were on the command |
57 | 58 | // line before any user supplied options. |
58 | 59 | add(new CommandOption("mapname", "63240001")); |
61 | 62 | add(new CommandOption("overview-mapnumber", "63240000")); |
62 | 63 | add(new CommandOption("poi-address", "")); |
63 | 64 | add(new CommandOption("merge-lines", "")); |
64 | } | |
65 | ||
66 | public CommandArgsReader(ArgumentProcessor proc) { | |
67 | this.proc = proc; | |
68 | 65 | } |
69 | 66 | |
70 | 67 | /** |
178 | 175 | log.debug("adding option", option, value); |
179 | 176 | |
180 | 177 | // 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 | } | |
183 | 185 | |
184 | 186 | switch (option) { |
185 | 187 | case "input-file": |
218 | 220 | * @param filename The filename to obtain options from. |
219 | 221 | */ |
220 | 222 | 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)); | |
226 | 226 | }); |
227 | 227 | try { |
228 | 228 | opts.readOptionFile(filename); |
267 | 267 | args.setProperty("mapname", mapname); |
268 | 268 | } |
269 | 269 | |
270 | checkMapName(); | |
270 | 271 | // Now process the file |
271 | 272 | proc.processFilename(new CommandArgs(args), name); |
272 | 273 | |
273 | 274 | // Increase the name number. If the next arg sets it then that |
274 | 275 | // will override this new name. |
275 | 276 | mapname = args.getProperty("mapname"); |
276 | try { | |
277 | Formatter fmt = new Formatter(); | |
277 | try (Formatter fmt = new Formatter()) { | |
278 | 278 | try { |
279 | 279 | int n = Integer.parseInt(mapname); |
280 | 280 | fmt.format("%08d", ++n); |
282 | 282 | fmt.format("%8.8s", mapname); |
283 | 283 | } |
284 | 284 | args.setProperty("mapname", fmt.toString()); |
285 | fmt.close(); | |
286 | 285 | } catch (NumberFormatException e) { |
287 | 286 | // If the name is not a number then we just leave it alone... |
288 | 287 | } |
289 | 288 | } |
290 | 289 | |
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; | |
303 | 314 | } |
304 | 315 | |
305 | 316 | /** |
348 | 359 | private int filenameCount; |
349 | 360 | |
350 | 361 | ArgList() { |
351 | alist = new ArrayList<ArgType>(); | |
362 | alist = new ArrayList<>(); | |
352 | 363 | } |
353 | 364 | |
354 | 365 | protected void add(CommandOption option) { |
12 | 12 | |
13 | 13 | package uk.me.parabola.mkgmap.build; |
14 | 14 | |
15 | import java.awt.Rectangle; | |
15 | 16 | import java.awt.geom.Path2D; |
16 | 17 | import java.awt.geom.Rectangle2D; |
17 | 18 | import java.io.File; |
27 | 28 | import java.util.IdentityHashMap; |
28 | 29 | import java.util.List; |
29 | 30 | import java.util.Set; |
31 | import java.util.TreeMap; | |
30 | 32 | import java.util.function.UnaryOperator; |
31 | 33 | |
32 | 34 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; |
99 | 101 | import uk.me.parabola.mkgmap.reader.hgt.HGTConverter; |
100 | 102 | import uk.me.parabola.mkgmap.reader.hgt.HGTConverter.InterpolationMethod; |
101 | 103 | import uk.me.parabola.mkgmap.reader.hgt.HGTReader; |
104 | import uk.me.parabola.mkgmap.reader.osm.GType; | |
102 | 105 | import uk.me.parabola.mkgmap.reader.overview.OverviewMapDataSource; |
103 | 106 | import uk.me.parabola.util.Configurable; |
104 | 107 | import uk.me.parabola.util.EnhancedProperties; |
126 | 129 | private List<String> mapInfo = new ArrayList<>(); |
127 | 130 | private List<String> copyrights = new ArrayList<>(); |
128 | 131 | |
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 | |
131 | 134 | private Locator locator; |
132 | 135 | |
133 | 136 | private final java.util.Map<String, Highway> highways = new HashMap<>(); |
134 | 137 | |
135 | 138 | /** 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 = ""; | |
137 | 140 | |
138 | 141 | private Country defaultCountry; |
139 | 142 | private String countryName = "COUNTRY"; |
145 | 148 | |
146 | 149 | private int minSizePolygon; |
147 | 150 | private String polygonSizeLimitsOpt; |
148 | private HashMap<Integer,Integer> polygonSizeLimits; | |
151 | private TreeMap<Integer,Integer> polygonSizeLimits; | |
149 | 152 | private double reducePointError; |
150 | 153 | private double reducePointErrorPolygon; |
151 | 154 | private boolean mergeLines; |
190 | 193 | mergeLines = props.containsKey("merge-lines"); |
191 | 194 | |
192 | 195 | // undocumented option - usually used for debugging only |
193 | mergeShapes = props.getProperty("no-mergeshapes", false) == false; | |
196 | mergeShapes = !props.getProperty("no-mergeshapes", false); | |
194 | 197 | |
195 | 198 | makePOIIndex = props.getProperty("make-poi-index", false); |
196 | 199 | |
221 | 224 | String ipm = props.getProperty("dem-interpolation", "auto"); |
222 | 225 | switch (ipm) { |
223 | 226 | case "auto": |
224 | demInterpolationMethod = InterpolationMethod.Automatic; | |
227 | demInterpolationMethod = InterpolationMethod.AUTOMATIC; | |
225 | 228 | break; |
226 | 229 | case "bicubic": |
227 | demInterpolationMethod = InterpolationMethod.Bicubic; | |
230 | demInterpolationMethod = InterpolationMethod.BICUBIC; | |
228 | 231 | break; |
229 | 232 | case "bilinear": |
230 | demInterpolationMethod = InterpolationMethod.Bilinear; | |
233 | demInterpolationMethod = InterpolationMethod.BILINEAR; | |
231 | 234 | break; |
232 | 235 | default: |
233 | 236 | throw new IllegalArgumentException("invalid argument for option dem-interpolation: '" + ipm + |
235 | 238 | } |
236 | 239 | } |
237 | 240 | |
238 | private List<Integer> parseDemDists(String demDists) { | |
241 | private static List<Integer> parseDemDists(String demDists) { | |
239 | 242 | List<Integer> dists = new ArrayList<>(); |
240 | 243 | if (demDists == null) |
241 | 244 | dists.add(-1); |
262 | 265 | TREFile treFile = map.getTreFile(); |
263 | 266 | lblFile = map.getLblFile(); |
264 | 267 | 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()); | |
272 | 275 | } |
273 | 276 | } |
274 | 277 | if (mapInfo.isEmpty()) |
275 | 278 | getMapInfo(); |
276 | 279 | |
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 | } | |
277 | 284 | normalizeCountries(src); |
278 | 285 | |
279 | 286 | processCities(map, src); |
283 | 290 | processInfo(map, src); |
284 | 291 | makeMapAreas(map, src); |
285 | 292 | |
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(); | |
291 | 296 | } |
292 | 297 | if (driveOnLeft == null) |
293 | 298 | driveOnLeft = false; |
317 | 322 | netFile.writePost(rgnFile.getWriter()); |
318 | 323 | } |
319 | 324 | 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); | |
349 | 326 | treFile.writePost(); |
350 | 327 | } |
351 | 328 | |
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 | ||
352 | 362 | private void warnAbout3ByteImgRefs() { |
363 | String mapContains = "Map contains"; | |
353 | 364 | String infoMsg = "- more than 65535 might cause indexing problems and excess size. Suggest splitter with lower --max-nodes"; |
354 | 365 | int itemCount; |
355 | 366 | itemCount = lblFile.numCities(); |
356 | 367 | if (itemCount > 0xffff) |
357 | log.error("Map contains", itemCount, "Cities", infoMsg); | |
368 | log.error(mapContains, itemCount, "Cities", infoMsg); | |
358 | 369 | itemCount = lblFile.numZips(); |
359 | 370 | if (itemCount > 0xffff) |
360 | log.error("Map contains", itemCount, "Zips", infoMsg); | |
371 | log.error(mapContains, itemCount, "Zips", infoMsg); | |
361 | 372 | itemCount = lblFile.numHighways(); |
362 | 373 | if (itemCount > 0xffff) |
363 | log.error("Map contains", itemCount, "Highways", infoMsg); | |
374 | log.error(mapContains, itemCount, "Highways", infoMsg); | |
364 | 375 | itemCount = lblFile.numExitFacilities(); |
365 | 376 | if (itemCount > 0xffff) |
366 | log.error("Map contains", itemCount, "Exit facilities", infoMsg); | |
377 | log.error(mapContains, itemCount, "Exit facilities", infoMsg); | |
367 | 378 | } // warnAbout3ByteImgRefs |
368 | 379 | |
369 | 380 | private Country getDefaultCountry() { |
379 | 390 | * @return the default region in the given country ({@code null} if not available) |
380 | 391 | */ |
381 | 392 | private Region getDefaultRegion(Country country) { |
382 | if (lblFile==null || regionName == null) { | |
393 | if (lblFile == null || regionName == null) { | |
383 | 394 | return null; |
384 | 395 | } |
385 | 396 | if (country == null) { |
405 | 416 | if (countryStr != null) { |
406 | 417 | countryStr = locator.normalizeCountry(countryStr); |
407 | 418 | p.setCountry(countryStr); |
408 | } | |
409 | } | |
410 | ||
419 | } | |
420 | } | |
421 | ||
411 | 422 | for (MapLine l : src.getLines()) { |
412 | 423 | String countryStr = l.getCountry(); |
413 | 424 | if (countryStr != null) { |
414 | 425 | countryStr = locator.normalizeCountry(countryStr); |
415 | 426 | l.setCountry(countryStr); |
416 | } | |
417 | } | |
427 | } | |
428 | } | |
418 | 429 | |
419 | 430 | // 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 | ||
429 | 431 | } |
430 | 432 | |
431 | 433 | /** |
441 | 443 | private void processCities(Map map, MapDataSource src) { |
442 | 444 | LBLFile lbl = map.getLblFile(); |
443 | 445 | |
444 | if (locationAutofill.isEmpty() == false) { | |
446 | if (!locationAutofill.isEmpty()) { | |
445 | 447 | // collect the names of the cities |
446 | 448 | for (MapPoint p : src.getPoints()) { |
447 | 449 | if(p.isCity() && p.getName() != null) |
451 | 453 | locator.autofillCities(); // Try to fill missing information that include search of next city |
452 | 454 | } |
453 | 455 | |
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); | |
482 | 480 | } |
483 | 481 | |
484 | 482 | } |
487 | 485 | LBLFile lbl = map.getLblFile(); |
488 | 486 | MapPoint searchPoint = new MapPoint(); |
489 | 487 | 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); | |
511 | 541 | } |
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); | |
551 | 551 | } |
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) { | |
558 | 560 | if (city == null && region == null && country == null) |
559 | 561 | 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 | |
564 | 567 | // use empty name for the city |
565 | 568 | city = UNKNOWN_CITY_NAME; |
566 | 569 | } |
567 | if (city == null) | |
568 | return null; | |
569 | if(cr != null) { | |
570 | if (cr != null) { | |
570 | 571 | return lbl.createCity(cr, city, false); |
571 | } | |
572 | else { | |
572 | } else { | |
573 | 573 | return lbl.createCity(cc, city, false); |
574 | 574 | } |
575 | 575 | } |
576 | ||
577 | 576 | |
578 | 577 | private void processPOIs(Map map, MapDataSource src) { |
579 | 578 | |
589 | 588 | // * cities (already processed) |
590 | 589 | // * extended types (address information not shown in MapSource and on GPS) |
591 | 590 | // * 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) { | |
594 | 592 | |
595 | 593 | String countryStr = p.getCountry(); |
596 | 594 | String regionStr = p.getRegion(); |
597 | 595 | String zipStr = p.getZip(); |
598 | 596 | String cityStr = p.getCity(); |
599 | 597 | |
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))) { | |
602 | 600 | MapPoint nextCity = locator.findNearbyCityByName(p); |
603 | ||
604 | if(nextCity == null) | |
601 | ||
602 | if (nextCity == null) | |
605 | 603 | nextCity = locator.findNextPoint(p); |
606 | 604 | |
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) { | |
614 | 612 | 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) | |
619 | 616 | zipStr = cityZipStr; |
620 | 617 | } |
621 | ||
622 | if(cityStr == null) cityStr = nextCity.getCity(); | |
623 | ||
618 | ||
619 | if (cityStr == null) | |
620 | cityStr = nextCity.getCity(); | |
621 | ||
624 | 622 | } |
625 | } | |
626 | ||
623 | } | |
627 | 624 | |
628 | if(countryStr != null && !checkedForPoiDispFlag) | |
629 | { | |
625 | if (countryStr != null && !checkedForPoiDispFlag) { | |
630 | 626 | // Different countries require different address notation |
631 | 627 | |
632 | 628 | poiDisplayFlags = locator.getPOIDispFlag(countryStr); |
633 | 629 | checkedForPoiDispFlag = true; |
634 | 630 | } |
635 | 631 | |
636 | ||
637 | 632 | POIRecord r = lbl.createPOI(p.getName()); |
638 | 633 | |
639 | if(cityStr != null || regionStr != null || countryStr != null){ | |
634 | if (cityStr != null || regionStr != null || countryStr != null) { | |
640 | 635 | r.setCity(calcCity(lbl, cityStr, regionStr, countryStr)); |
641 | 636 | } |
642 | 637 | |
643 | if (zipStr != null) | |
644 | { | |
638 | if (zipStr != null) { | |
645 | 639 | Zip zip = lbl.createZip(zipStr); |
646 | 640 | r.setZip(zip); |
647 | 641 | } |
648 | 642 | |
649 | if(p.getStreet() != null) | |
650 | { | |
643 | if (p.getStreet() != null) { | |
651 | 644 | Label streetName = lbl.newLabel(p.getStreet()); |
652 | r.setStreetName(streetName); | |
645 | r.setStreetName(streetName); | |
653 | 646 | } |
654 | 647 | |
655 | 648 | 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)); | |
659 | 651 | } |
660 | 652 | |
661 | 653 | 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 | } | |
666 | 657 | |
667 | 658 | poimap.put(p, r); |
668 | 659 | } |
674 | 665 | private void processExit(Map map, MapExitPoint mep) { |
675 | 666 | LBLFile lbl = map.getLblFile(); |
676 | 667 | String ref = mep.getMotorwayRef(); |
677 | String OSMId = mep.getOSMId(); | |
668 | String osmId = mep.getOSMId(); | |
678 | 669 | if(ref != null) { |
679 | 670 | Highway hw = highways.get(ref); |
680 | 671 | if(hw == null) |
681 | 672 | hw = makeHighway(map, ref); |
682 | 673 | 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); | |
684 | 675 | return; |
685 | 676 | } |
686 | 677 | String exitName = mep.getName(); |
687 | 678 | String exitTo = mep.getTo(); |
688 | 679 | Exit exit = new Exit(hw); |
689 | 680 | 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)); | |
691 | 682 | if(facilityDescription != null) { |
692 | 683 | // description is TYPE,DIR,FACILITIES,LABEL |
693 | 684 | // (same as Polish Format) |
738 | 729 | private void makeMapAreas(Map map, LoadableMapDataSource src) { |
739 | 730 | // The top level has to cover the whole map without subdividing, so |
740 | 731 | // 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) { | |
743 | 734 | mergeLines = true; |
744 | 735 | prepShapesForMerge(src.getShapes()); |
745 | 736 | mergeShapes = true; |
746 | 737 | 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) { | |
756 | 744 | throw new ExitException("no info about levels available."); |
757 | 745 | } |
758 | 746 | LevelInfo levelInfo = levels[0]; |
802 | 790 | log.debug("ADD parent-subdiv", parent, srcDivPair.getSource(), ", z=", zoom, " new=", div); |
803 | 791 | nextList.add(new SourceSubdiv(area, div)); |
804 | 792 | } |
805 | if (nextList.size() > 0){ | |
793 | if (!nextList.isEmpty()) { | |
806 | 794 | Subdivision lastdiv = nextList.get(nextList.size() - 1).getSubdiv(); |
807 | 795 | lastdiv.setLast(true); |
808 | 796 | } |
819 | 807 | */ |
820 | 808 | private static void prepShapesForMerge(List<MapShape> shapes) { |
821 | 809 | Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>(); |
822 | for (MapShape s : shapes){ | |
810 | for (MapShape s : shapes) { | |
823 | 811 | List<Coord> points = s.getPoints(); |
824 | 812 | int n = points.size(); |
825 | for (int i = 0; i< n; i++){ | |
813 | for (int i = 0; i < n; i++) { | |
826 | 814 | Coord co = points.get(i); |
827 | 815 | Coord repl = coordMap.get(Utils.coord2Long(co)); |
828 | 816 | if (repl == null) |
831 | 819 | points.set(i, repl); |
832 | 820 | } |
833 | 821 | } |
834 | return; | |
835 | 822 | } |
836 | 823 | |
837 | 824 | /** |
931 | 918 | catch (Exception e) { |
932 | 919 | throw new ExitException("Error reading license file " + licenseFileName, e); |
933 | 920 | } |
934 | if ((licenseArray.size() > 0) && licenseArray.get(0).startsWith("\ufeff")) | |
921 | if ((!licenseArray.isEmpty()) && licenseArray.get(0).startsWith("\ufeff")) | |
935 | 922 | licenseArray.set(0, licenseArray.get(0).substring(1)); |
936 | 923 | UnaryOperator<String> replaceVariables = s -> s.replace("$MKGMAP_VERSION$", Version.VERSION) |
937 | 924 | .replace("$JAVA_VERSION$", System.getProperty("java.version")) |
961 | 948 | } |
962 | 949 | } |
963 | 950 | |
964 | public void setMapInfo(List<String> msgs){ | |
951 | public void setMapInfo(List<String> msgs) { | |
965 | 952 | mapInfo = msgs; |
966 | 953 | } |
967 | ||
968 | public void setCopyrights(List<String> msgs){ | |
954 | ||
955 | public void setCopyrights(List<String> msgs) { | |
969 | 956 | copyrights = msgs; |
970 | } | |
971 | ||
957 | } | |
972 | 958 | |
973 | 959 | /** |
974 | 960 | * Set all the information that appears in the header. |
992 | 978 | // |
993 | 979 | // We use it to add copyright information that there is no room for |
994 | 980 | // 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()) { | |
1002 | 989 | // There has to be (at least) two copyright messages or else the map |
1003 | 990 | // does not show up. The second and subsequent ones will be displayed |
1004 | 991 | // at startup, although the conditions where that happens are not known. |
1038 | 1025 | // pointIndex must be initialized to the number of indexed |
1039 | 1026 | // points (not 1) |
1040 | 1027 | for (MapPoint point : points) { |
1041 | if (point.isCity() && | |
1042 | point.getMinResolution() <= res) { | |
1028 | if (point.isCity() && point.getMinResolution() <= res) { | |
1043 | 1029 | ++pointIndex; |
1044 | 1030 | haveIndPoints = true; |
1045 | 1031 | } |
1047 | 1033 | |
1048 | 1034 | for (MapPoint point : points) { |
1049 | 1035 | |
1050 | if (point.isCity() || | |
1051 | point.getMinResolution() > res) | |
1036 | if (point.isCity() || point.getMinResolution() > res) | |
1052 | 1037 | continue; |
1053 | 1038 | |
1054 | 1039 | String name = point.getName(); |
1056 | 1041 | Point p = div.createPoint(name); |
1057 | 1042 | p.setType(point.getType()); |
1058 | 1043 | |
1059 | if(point.hasExtendedType()) { | |
1044 | if (point.hasExtendedType()) { | |
1060 | 1045 | ExtTypeAttributes eta = point.getExtTypeAttributes(); |
1061 | if(eta != null) { | |
1046 | if (eta != null) { | |
1062 | 1047 | eta.processLabels(lbl); |
1063 | 1048 | p.setExtTypeAttributes(eta); |
1064 | 1049 | } |
1068 | 1053 | try { |
1069 | 1054 | p.setLatitude(coord.getLatitude()); |
1070 | 1055 | p.setLongitude(coord.getLongitude()); |
1071 | } | |
1072 | catch (AssertionError ae) { | |
1056 | } catch (AssertionError ae) { | |
1073 | 1057 | log.error("Problem with point of type 0x" + Integer.toHexString(point.getType()) + " at " + coord.toOSMURL()); |
1074 | 1058 | log.error(" Subdivision shift is " + div.getShift() + |
1075 | 1059 | " and its centre is at " + div.getCenter().toOSMURL()); |
1082 | 1066 | p.setPOIRecord(r); |
1083 | 1067 | |
1084 | 1068 | 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) | |
1092 | 1077 | e.getHighway().addExitPoint(name, pointIndex, div); |
1078 | } else if (makePOIIndex) { | |
1079 | lbl.createPOIIndex(name, pointIndex, div, point.getType()); | |
1093 | 1080 | } |
1094 | else if(makePOIIndex) | |
1095 | lbl.createPOIIndex(name, pointIndex, div, point.getType()); | |
1096 | 1081 | } |
1097 | 1082 | |
1098 | 1083 | ++pointIndex; |
1105 | 1090 | pointIndex = 1; // reset to 1 |
1106 | 1091 | for (MapPoint point : points) { |
1107 | 1092 | |
1108 | if (!point.isCity() || | |
1109 | point.getMinResolution() > res) | |
1093 | if (!point.isCity() || point.getMinResolution() > res) | |
1110 | 1094 | continue; |
1111 | 1095 | |
1112 | 1096 | String name = point.getName(); |
1113 | 1097 | |
1114 | 1098 | 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); | |
1116 | 1102 | |
1117 | 1103 | Coord coord = point.getLocation(); |
1118 | 1104 | try { |
1119 | 1105 | p.setLatitude(coord.getLatitude()); |
1120 | 1106 | p.setLongitude(coord.getLongitude()); |
1121 | } | |
1122 | catch (AssertionError ae) { | |
1107 | } catch (AssertionError ae) { | |
1123 | 1108 | log.error("Problem with point of type 0x" + Integer.toHexString(point.getType()) + " at " + coord.toOSMURL()); |
1124 | 1109 | log.error(" Subdivision shift is " + div.getShift() + |
1125 | 1110 | " and its centre is at " + div.getCenter().toOSMURL()); |
1134 | 1119 | City c = cityMap.get(point); |
1135 | 1120 | |
1136 | 1121 | 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"); | |
1138 | 1123 | } else { |
1139 | 1124 | c.setPointIndex(pointIndex); |
1140 | 1125 | c.setSubdivision(div); |
1157 | 1142 | * @param div The subdivision that the lines belong to. |
1158 | 1143 | * @param lines The lines to be added. |
1159 | 1144 | */ |
1160 | private void processLines(Map map, Subdivision div, List<MapLine> lines) | |
1161 | { | |
1145 | private void processLines(Map map, Subdivision div, List<MapLine> lines) { | |
1162 | 1146 | div.startLines(); // Signal that we are beginning to draw the lines. |
1163 | 1147 | |
1164 | 1148 | int res = div.getResolution(); |
1166 | 1150 | FilterConfig config = new FilterConfig(); |
1167 | 1151 | config.setResolution(res); |
1168 | 1152 | config.setLevel(div.getZoom().getLevel()); |
1169 | config.setRoutable(doRoads); | |
1153 | config.setHasNet(doRoads); | |
1170 | 1154 | |
1171 | 1155 | //TODO: Maybe this is the wrong place to do merging. |
1172 | 1156 | // Maybe more efficient if merging before creating subdivisions. |
1174 | 1158 | LineMergeFilter merger = new LineMergeFilter(); |
1175 | 1159 | lines = merger.merge(lines, res); |
1176 | 1160 | } |
1161 | ||
1177 | 1162 | LayerFilterChain filters = new LayerFilterChain(config); |
1178 | 1163 | if (enableLineCleanFilters && (res < 24)) { |
1179 | 1164 | filters.addFilter(new RoundCoordsFilter()); |
1185 | 1170 | filters.addFilter(new RemoveEmpty()); |
1186 | 1171 | filters.addFilter(new RemoveObsoletePointsFilter()); |
1187 | 1172 | filters.addFilter(new LinePreparerFilter(div)); |
1188 | filters.addFilter(new LineAddFilter(div, map, doRoads)); | |
1173 | filters.addFilter(new LineAddFilter(div, map)); | |
1189 | 1174 | |
1190 | 1175 | 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 | } | |
1194 | 1179 | } |
1195 | 1180 | } |
1196 | 1181 | |
1205 | 1190 | * @param div The subdivision that the polygons belong to. |
1206 | 1191 | * @param shapes The polygons to be added. |
1207 | 1192 | */ |
1208 | private void processShapes(Map map, Subdivision div, List<MapShape> shapes) | |
1209 | { | |
1193 | private void processShapes(Map map, Subdivision div, List<MapShape> shapes) { | |
1210 | 1194 | div.startShapes(); // Signal that we are beginning to draw the shapes. |
1211 | 1195 | |
1212 | 1196 | int res = div.getResolution(); |
1214 | 1198 | FilterConfig config = new FilterConfig(); |
1215 | 1199 | config.setResolution(res); |
1216 | 1200 | config.setLevel(div.getZoom().getLevel()); |
1217 | config.setRoutable(doRoads); | |
1201 | config.setHasNet(doRoads); | |
1218 | 1202 | |
1219 | 1203 | if (mergeShapes){ |
1220 | 1204 | ShapeMergeFilter shapeMergeFilter = new ShapeMergeFilter(res, orderByDecreasingArea); |
1247 | 1231 | filters.addFilter(new ShapeAddFilter(div, map)); |
1248 | 1232 | |
1249 | 1233 | 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 | } | |
1253 | 1237 | } |
1254 | 1238 | } |
1255 | 1239 | |
1275 | 1259 | int maxLon = shape.getBounds().getMaxLong(); |
1276 | 1260 | |
1277 | 1261 | List<Coord> points = shape.getPoints(); |
1278 | int n = shape.getPoints().size(); | |
1262 | int n = points.size(); | |
1279 | 1263 | 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) { | |
1284 | 1267 | last = points.get(i); |
1285 | 1268 | // preserve coord instances which are used more than once, |
1286 | 1269 | // these are typically produced by the ShapeMergerFilter |
1287 | 1270 | // to connect holes |
1288 | if (coords.get(last) == null){ | |
1271 | if (coords.get(last) == null) { | |
1289 | 1272 | 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); | |
1295 | 1275 | } |
1296 | 1276 | |
1297 | 1277 | // preserve the end points of horizontal and vertical lines that lie |
1298 | 1278 | // on the bbox of the shape. |
1299 | 1279 | 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)) { | |
1301 | 1281 | last.preserved(true); |
1302 | 1282 | prev.preserved(true); |
1303 | 1283 | } |
1307 | 1287 | } |
1308 | 1288 | |
1309 | 1289 | Highway makeHighway(Map map, String ref) { |
1310 | if(getDefaultRegion(null) == null) { | |
1290 | if (getDefaultRegion(null) == null) { | |
1311 | 1291 | log.warn("Highway " + ref + " has no region (define a default region to zap this warning)"); |
1312 | 1292 | } |
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 | }); | |
1322 | 1297 | } |
1323 | 1298 | |
1324 | 1299 | /** |
1336 | 1311 | return 24 - minShift; |
1337 | 1312 | } |
1338 | 1313 | |
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 | ||
1347 | 1314 | public void setEnableLineCleanFilters(boolean enable) { |
1348 | 1315 | this.enableLineCleanFilters = enable; |
1349 | 1316 | } |
1354 | 1321 | * @return the size filter value |
1355 | 1322 | */ |
1356 | 1323 | private int getMinSizePolygonForResolution(int res) { |
1357 | ||
1358 | 1324 | if (polygonSizeLimitsOpt == null) |
1359 | 1325 | return minSizePolygon; |
1360 | 1326 | |
1361 | if (polygonSizeLimits == null){ | |
1362 | polygonSizeLimits = new HashMap<>(); | |
1327 | if (polygonSizeLimits == null) { | |
1328 | polygonSizeLimits = new TreeMap<>(); | |
1363 | 1329 | String[] desc = polygonSizeLimitsOpt.split("[, \\t\\n]+"); |
1364 | ||
1365 | int count = 0; | |
1330 | ||
1366 | 1331 | for (String s : desc) { |
1367 | 1332 | String[] keyVal = s.split("[=:]"); |
1368 | 1333 | 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); | |
1371 | 1335 | } |
1372 | 1336 | |
1373 | 1337 | try { |
1374 | 1338 | int key = Integer.parseInt(keyVal[0]); |
1375 | 1339 | int value = Integer.parseInt(keyVal[1]); |
1376 | 1340 | 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); | |
1380 | 1344 | } |
1381 | 1345 | } 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(); | |
1400 | 1354 | } |
1401 | 1355 | |
1402 | 1356 | private static class SourceSubdiv { |
1417 | 1371 | } |
1418 | 1372 | } |
1419 | 1373 | |
1420 | private class LineAddFilter extends BaseFilter implements MapFilter { | |
1374 | private static class LineAddFilter extends BaseFilter implements MapFilter { | |
1421 | 1375 | private final Subdivision div; |
1422 | 1376 | 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) { | |
1426 | 1379 | this.div = div; |
1427 | 1380 | this.map = map; |
1428 | this.doRoads = doRoads; | |
1429 | 1381 | } |
1430 | 1382 | |
1431 | 1383 | public void doFilter(MapElement element, MapFilterChain next) { |
1439 | 1391 | eta.processLabels(map.getLblFile()); |
1440 | 1392 | pl.setExtTypeAttributes(eta); |
1441 | 1393 | } |
1442 | } else | |
1394 | } else { | |
1443 | 1395 | div.setPolylineNumber(pl); |
1396 | } | |
1444 | 1397 | |
1445 | 1398 | pl.setDirection(line.isDirection()); |
1446 | 1399 | |
1447 | 1400 | pl.addCoords(line.getPoints()); |
1448 | 1401 | |
1449 | 1402 | 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); | |
1463 | 1414 | } |
1464 | 1415 | map.addMapObject(pl); |
1465 | 1416 | } |
1483 | 1434 | pg.addCoords(shape.getPoints()); |
1484 | 1435 | |
1485 | 1436 | pg.setType(shape.getType()); |
1486 | if(element.hasExtendedType()) { | |
1437 | if (element.hasExtendedType()) { | |
1487 | 1438 | ExtTypeAttributes eta = element.getExtTypeAttributes(); |
1488 | if(eta != null) { | |
1439 | if (eta != null) { | |
1489 | 1440 | eta.processLabels(map.getLblFile()); |
1490 | 1441 | pg.setExtTypeAttributes(eta); |
1491 | 1442 | } |
45 | 45 | import uk.me.parabola.imgfmt.fs.ImgChannel; |
46 | 46 | import uk.me.parabola.imgfmt.sys.ImgFS; |
47 | 47 | import uk.me.parabola.mkgmap.CommandArgs; |
48 | import uk.me.parabola.mkgmap.general.MapPoint; | |
48 | 49 | import uk.me.parabola.mkgmap.srt.SrtTextReader; |
49 | 50 | |
50 | 51 | /** |
265 | 266 | |
266 | 267 | Mdr5Record mdrCity = null; |
267 | 268 | boolean isCity; |
268 | if (p.getType() >= 0x1 && p.getType() <= 0x11) { | |
269 | if (MapPoint.isCityType(p.getType())) { | |
269 | 270 | // This is itself a city, it gets a reference to its own MDR 5 record. |
270 | 271 | // and we also use it to set the name of the city. |
271 | 272 | mdrCity = maps.cities.get((p.getSubdiv().getNumber() << 8) + p.getNumber()); |
26 | 26 | public class FilterConfig { |
27 | 27 | private int resolution; |
28 | 28 | private int level; |
29 | private boolean routable; | |
29 | private boolean hasNet; | |
30 | 30 | |
31 | 31 | protected int getResolution() { |
32 | 32 | return resolution; |
57 | 57 | this.level = level; |
58 | 58 | } |
59 | 59 | |
60 | public boolean isRoutable() { | |
61 | return routable; | |
60 | public boolean hasNet() { | |
61 | return hasNet; | |
62 | 62 | } |
63 | 63 | |
64 | public void setRoutable(boolean routable) { | |
65 | this.routable = routable; | |
64 | public void setHasNet(boolean flag) { | |
65 | this.hasNet = flag; | |
66 | 66 | } |
67 | 67 | } |
87 | 87 | int dy = (lat - lastLat) << offset >> offset; |
88 | 88 | lastLong = lon; |
89 | 89 | 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; | |
93 | 92 | } |
94 | 93 | ++numPointsEncoded; |
95 | if (numPointsEncoded >= minPointsRequired && element instanceof MapShape == false) | |
94 | if (numPointsEncoded >= minPointsRequired && !(element instanceof MapShape)) | |
96 | 95 | break; |
97 | 96 | // find out largest and 2nd largest delta for both dx and dy |
98 | 97 | for (int k = 0; k < 2; k++){ |
44 | 44 | private boolean isRoutable; |
45 | 45 | public void init(FilterConfig config) { |
46 | 46 | this.level = config.getLevel(); |
47 | this.isRoutable = config.isRoutable(); | |
47 | this.isRoutable = config.hasNet(); | |
48 | 48 | } |
49 | 49 | |
50 | 50 |
33 | 33 | |
34 | 34 | private boolean checkPreserved; |
35 | 35 | public void init(FilterConfig config) { |
36 | checkPreserved = config.getLevel() == 0 && config.isRoutable(); | |
36 | checkPreserved = config.getLevel() == 0 && config.hasNet(); | |
37 | 37 | } |
38 | 38 | |
39 | 39 | /** |
48 | 48 | return; |
49 | 49 | } |
50 | 50 | int requiredPoints = (line instanceof MapShape ) ? 4:2; |
51 | List<Coord> newPoints = new ArrayList<Coord>(numPoints); | |
51 | List<Coord> newPoints = new ArrayList<>(numPoints); | |
52 | 52 | while (true){ |
53 | 53 | boolean removedSpike = false; |
54 | 54 | numPoints = points.size(); |
56 | 56 | |
57 | 57 | Coord lastP = points.get(0); |
58 | 58 | newPoints.add(lastP); |
59 | for(int i = 1; i < numPoints; i++) { | |
59 | for (int i = 1; i < numPoints; i++) { | |
60 | 60 | Coord newP = points.get(i); |
61 | 61 | int last = newPoints.size()-1; |
62 | 62 | lastP = newPoints.get(last); |
64 | 64 | // only add the new point if it has different |
65 | 65 | // coordinates to the last point or is preserved |
66 | 66 | if (checkPreserved && line.isRoad()){ |
67 | if (newP.preserved() == false) | |
67 | if (!newP.preserved()) { | |
68 | 68 | continue; |
69 | else if (lastP.preserved() == false){ | |
69 | } else if (!lastP.preserved()){ | |
70 | 70 | newPoints.set(last, newP); // replace last |
71 | 71 | } |
72 | } else | |
72 | } else { | |
73 | 73 | continue; |
74 | } | |
74 | 75 | } |
75 | 76 | if (newPoints.size() > 1) { |
76 | 77 | switch (Utils.isStraight(newPoints.get(last-1), lastP, newP)){ |
102 | 103 | if (!removedSpike || newPoints.size() < requiredPoints) |
103 | 104 | break; |
104 | 105 | points = newPoints; |
105 | newPoints = new ArrayList<Coord>(points.size()); | |
106 | newPoints = new ArrayList<>(points.size()); | |
106 | 107 | } |
107 | 108 | if (line instanceof MapShape){ |
108 | 109 | // Check special cases caused by the fact that the first and last point |
19 | 19 | import uk.me.parabola.imgfmt.app.CoordNode; |
20 | 20 | import uk.me.parabola.mkgmap.general.MapElement; |
21 | 21 | import uk.me.parabola.mkgmap.general.MapLine; |
22 | import uk.me.parabola.mkgmap.general.MapRoad; | |
22 | 23 | |
23 | 24 | public class RoundCoordsFilter implements MapFilter { |
24 | 25 | |
25 | 26 | private int shift; |
26 | private boolean checkRouting; | |
27 | private boolean keepNodes; | |
28 | private int level; | |
27 | 29 | |
28 | 30 | public void init(FilterConfig config) { |
29 | 31 | shift = config.getShift(); |
30 | checkRouting = config.getLevel() == 0 && config.isRoutable() == true; | |
31 | ||
32 | keepNodes = config.getLevel() == 0 && config.hasNet(); | |
33 | level = config.getLevel(); | |
32 | 34 | } |
33 | 35 | |
34 | 36 | /** |
37 | 39 | */ |
38 | 40 | public void doFilter(MapElement element, MapFilterChain next) { |
39 | 41 | MapLine line = (MapLine) element; |
40 | int half = 1 << (shift - 1); // 0.5 shifted | |
41 | int mask = ~((1 << shift) - 1); // to remove fraction bits | |
42 | ||
43 | 42 | if(shift == 0) { |
44 | 43 | // do nothing |
45 | 44 | next.doFilter(line); |
46 | 45 | } |
47 | 46 | else { |
47 | int half = 1 << (shift - 1); // 0.5 shifted | |
48 | int mask = ~((1 << shift) - 1); // to remove fraction bits | |
49 | ||
48 | 50 | // 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()); | |
50 | 52 | Coord lastP = null; |
53 | boolean hasNumbers = level == 0 && line.isRoad() && ((MapRoad) line).getRoadDef().hasHouseNumbers(); | |
51 | 54 | 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 | ||
52 | 60 | int lat = (p.getLatitude() + half) & mask; |
53 | 61 | int lon = (p.getLongitude() + half) & mask; |
54 | 62 | Coord newP; |
55 | 63 | |
56 | if(p instanceof CoordNode && checkRouting) | |
64 | if(p instanceof CoordNode && keepNodes) | |
57 | 65 | newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary(), p.getOnCountryBorder()); |
58 | else | |
66 | else { | |
59 | 67 | newP = new Coord(lat, lon); |
60 | newP.preserved(p.preserved()); | |
61 | ||
68 | newP.preserved(p.preserved()); | |
69 | newP.setNumberNode(hasNumbers && p.isNumberNode()); | |
70 | } | |
71 | ||
62 | 72 | // only add the new point if it has different |
63 | 73 | // 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())) { | |
68 | 76 | newPoints.add(newP); |
69 | 77 | lastP = newP; |
70 | } | |
71 | else if(newP.preserved()) { | |
78 | } else if (newP.preserved()) { | |
72 | 79 | // this point is not going to be used because it |
73 | 80 | // has the same (rounded) coordinates as the last |
74 | 81 | // node but it has been marked as being "preserved" - |
75 | 82 | // transfer that property to the previous point so |
76 | // that it's not lost | |
83 | // that it's not lost in further filters | |
77 | 84 | lastP.preserved(true); |
78 | 85 | } |
79 | 86 | } |
26 | 26 | private final int size; |
27 | 27 | |
28 | 28 | private int minSize; |
29 | private boolean checkRouting; | |
29 | private boolean keepRoads; | |
30 | 30 | |
31 | 31 | public SizeFilter(int s) { |
32 | 32 | size = s; |
33 | 33 | } |
34 | 34 | |
35 | 35 | public void init(FilterConfig config) { |
36 | minSize = size * (1<<config.getShift()); | |
36 | minSize = size * (1 << config.getShift()); | |
37 | 37 | // don't remove roads on level 0 |
38 | checkRouting = config.getLevel() == 0 && config.isRoutable() == true; | |
38 | keepRoads = config.getLevel() == 0 && config.hasNet(); | |
39 | 39 | } |
40 | 40 | |
41 | 41 | /** |
48 | 48 | public void doFilter(MapElement element, MapFilterChain next) { |
49 | 49 | MapLine line = (MapLine) element; |
50 | 50 | |
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; | |
55 | 53 | } |
56 | 54 | next.doFilter(line); |
57 | 55 | } |
10 | 10 | * General Public License for more details. |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.general; |
13 | ||
14 | import java.util.Objects; | |
13 | 15 | |
14 | 16 | import uk.me.parabola.imgfmt.app.lbl.City; |
15 | 17 | |
71 | 73 | public boolean equals(Object obj) { |
72 | 74 | if (this == obj) |
73 | 75 | return true; |
74 | if (obj == null) | |
75 | return false; | |
76 | 76 | if (!(obj instanceof CityInfo)) |
77 | 77 | return false; |
78 | 78 | 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)) | |
83 | 80 | 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)) | |
88 | 82 | 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); | |
95 | 84 | } |
96 | 85 | |
97 | 86 | @Override |
63 | 63 | |
64 | 64 | public static boolean isCityType(int type) |
65 | 65 | { |
66 | return type >= 0x0100 && type <= 0x1100; | |
66 | return type >= 0x0100 && type < 0x1100; | |
67 | 67 | } |
68 | 68 | |
69 | 69 | public boolean isExit() { |
10 | 10 | * General Public License for more details. |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.general; |
13 | ||
14 | import java.util.Objects; | |
13 | 15 | |
14 | 16 | import uk.me.parabola.imgfmt.app.lbl.Zip; |
15 | 17 | |
49 | 51 | public boolean equals(Object obj) { |
50 | 52 | if (this == obj) |
51 | 53 | return true; |
52 | if (obj == null) | |
53 | return false; | |
54 | 54 | if (!(obj instanceof ZipCodeInfo)) |
55 | 55 | return false; |
56 | 56 | 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); | |
63 | 58 | } |
64 | 59 | |
65 | 60 | @Override |
249 | 249 | } |
250 | 250 | |
251 | 251 | 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; | |
253 | 256 | processMap.put("img", saver); |
254 | 257 | processMap.put("mdx", saver); |
255 | 258 | |
753 | 756 | return SrtTextReader.sortForCodepage(args.getCodePage()); |
754 | 757 | } |
755 | 758 | |
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 | ||
765 | 759 | private static class FilenameTask extends FutureTask<String> { |
766 | 760 | private CommandArgs args; |
767 | 761 | private String filename; |
103 | 103 | |
104 | 104 | MapBuilder builder = new MapBuilder(); |
105 | 105 | 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 | } | |
110 | 106 | builder.makeMap(map, src); |
111 | 107 | |
112 | 108 | // Collect information on map complete. |
45 | 45 | private long numTrue; // count how often the evaluation returned true |
46 | 46 | |
47 | 47 | /** 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 | }; | |
53 | 51 | |
54 | 52 | public ActionRule(Op expression, List<Action> actions, GType type) { |
55 | 53 | assert actions != null; |
38 | 38 | private long numTrue; // count how often the evaluation returned true |
39 | 39 | |
40 | 40 | /** 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."); | |
45 | 43 | }; |
46 | 44 | |
47 | 45 | public ExpressionRule(Op expression, GType gtype) { |
16 | 16 | import java.util.BitSet; |
17 | 17 | import java.util.HashMap; |
18 | 18 | import java.util.HashSet; |
19 | import java.util.Iterator; | |
20 | 19 | import java.util.LinkedHashMap; |
21 | 20 | import java.util.List; |
22 | 21 | import java.util.Map; |
63 | 62 | * @author Steve Ratcliffe |
64 | 63 | */ |
65 | 64 | public class RuleIndex { |
66 | private final List<RuleDetails> ruleDetails = new ArrayList<RuleDetails>(); | |
65 | private final List<RuleDetails> ruleDetails = new ArrayList<>(); | |
67 | 66 | |
68 | 67 | private final Map<Short, TagHelper> tagKeyMap = new HashMap<>(); |
69 | 68 | private TagHelper[] tagKeyArray = null; |
88 | 87 | merged.or(exists); |
89 | 88 | merged.or(value); |
90 | 89 | tagVals.put(val, merged); |
91 | } else | |
90 | } else { | |
92 | 91 | tagVals.put(val, value); |
92 | } | |
93 | 93 | } |
94 | 94 | |
95 | 95 | public BitSet getBitSet(String tagVal) { |
144 | 144 | assert tagKey > 0; |
145 | 145 | if (tagKey < tagKeyArray.length){ |
146 | 146 | th = tagKeyArray[tagKey]; |
147 | } else | |
147 | } else { | |
148 | 148 | th = null; |
149 | } | |
149 | 150 | } else { |
150 | 151 | th = tagKeyMap.get(tagKey); |
151 | 152 | } |
163 | 164 | if (inited) |
164 | 165 | return; |
165 | 166 | // 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<>(); | |
167 | 168 | // 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<>(); | |
169 | 170 | |
170 | 171 | // 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<>(); | |
172 | 173 | |
173 | 174 | // remove unnecessary rules |
174 | 175 | 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) { | |
176 | 209 | for (int i = 0; i < ruleDetails.size(); i++) { |
177 | 210 | int ruleNumber = i; |
178 | 211 | RuleDetails rd = ruleDetails.get(i); |
189 | 222 | String key = keystring.substring(0, ind); |
190 | 223 | addNumberToMap(tagnames, key, ruleNumber); |
191 | 224 | } 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) { | |
197 | 233 | // find the additional rules which might be triggered as a result of actions changing tags. |
198 | 234 | Map<Integer, BitSet> additionalRules = new LinkedHashMap<>(); |
199 | 235 | for (int i = 0; i < ruleDetails.size(); i++) { |
221 | 257 | addedRules.or(set); |
222 | 258 | } |
223 | 259 | } |
224 | // Only rules after the current one can be affected | |
260 | // Only rules after the current one can be affected | |
225 | 261 | addedRules.clear(0, ruleNumber); |
226 | 262 | if (!addedRules.isEmpty()) { |
227 | 263 | additionalRules.put(ruleNumber, addedRules); |
228 | 264 | } |
229 | 265 | } |
230 | ||
266 | ||
231 | 267 | // now add all the additional rules to the existing sets |
232 | 268 | for (Entry<Integer, BitSet> e : additionalRules.entrySet()) { |
233 | 269 | int ruleNumber = e.getKey(); |
244 | 280 | } |
245 | 281 | } |
246 | 282 | } |
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 | } | |
280 | 284 | } |
281 | 285 | |
282 | 286 | /** |
294 | 298 | ruleDetails.addAll(filteredRules); |
295 | 299 | } |
296 | 300 | |
297 | private void removeUnused(List<RuleDetails> filteredRules, Set<String> usedIfVars) { | |
301 | private static void removeUnused(List<RuleDetails> filteredRules, Set<String> usedIfVars) { | |
298 | 302 | if (usedIfVars.isEmpty()) |
299 | 303 | return; |
300 | Iterator<RuleDetails> iter = filteredRules.iterator(); | |
301 | while (iter.hasNext()) { | |
302 | RuleDetails rd = iter.next(); | |
304 | filteredRules.removeIf(rd -> { | |
303 | 305 | if (rd.getRule() instanceof ActionRule) { |
304 | 306 | ActionRule ar = (ActionRule) rd.getRule(); |
305 | 307 | if (ar.toString().contains("set " + RuleFileReader.IF_PREFIX)) { |
306 | boolean needed = false; | |
307 | 308 | for (String ifVars : usedIfVars) { |
308 | 309 | if (ar.toString().contains("set " + ifVars)) { |
309 | needed = true; | |
310 | return false; | |
310 | 311 | } |
311 | 312 | } |
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) { | |
320 | 321 | if (rule == null) |
321 | 322 | return; |
322 | // if (rule.getFinalizeRule() != null) | |
323 | // findIfVarUsage(rule.getFinalizeRule(), usedIfVars); | |
324 | 323 | Op expr = null; |
325 | if (rule instanceof ExpressionRule) | |
324 | if (rule instanceof ExpressionRule) | |
326 | 325 | expr = ((ExpressionRule) rule).getOp(); |
327 | 326 | else if (rule instanceof ActionRule) |
328 | 327 | expr = ((ActionRule) rule).getOp(); |
335 | 334 | } |
336 | 335 | |
337 | 336 | 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); | |
344 | 338 | } |
345 | 339 | |
346 | 340 | public List<RuleDetails> getRuleDetails() { |
52 | 52 | int cacheId; |
53 | 53 | boolean compiled = false; |
54 | 54 | |
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"); | |
56 | 56 | |
57 | 57 | private RuleIndex index = new RuleIndex(); |
58 | private final Set<String> usedTags = new HashSet<String>(); | |
58 | private final Set<String> usedTags = new HashSet<>(); | |
59 | 59 | |
60 | 60 | @Override |
61 | 61 | public void resolveType(Element el, TypeResult result) { |
85 | 85 | // Get all the rules that could match from the index. |
86 | 86 | BitSet candidates = new BitSet(); |
87 | 87 | 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); | |
91 | 91 | } |
92 | 92 | 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)) { | |
94 | 94 | a.reset(); |
95 | 95 | lastRule = rules[i]; |
96 | 96 | cacheId = lastRule.resolveType(cacheId, el, a); |
97 | 97 | if (a.isResolved()) |
98 | 98 | return cacheId; |
99 | 99 | } |
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); | |
104 | 103 | } |
105 | 104 | return cacheId; |
106 | 105 | } |
162 | 161 | |
163 | 162 | index = newIndex; |
164 | 163 | rules = newIndex.getRules(); |
165 | //System.out.println("Merging used tags: " | |
166 | // + getUsedTags().toString() | |
167 | // + " + " | |
168 | // + rs.getUsedTags()); | |
169 | 164 | addUsedTags(rs.usedTags); |
170 | //System.out.println("Result: " + getUsedTags().toString()); | |
171 | 165 | compiled = false; |
172 | 166 | } |
173 | 167 | |
194 | 188 | * make sure that all rules use the same instance of these common |
195 | 189 | * sub-expressions. |
196 | 190 | */ |
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) { | |
201 | 195 | Op op; |
202 | 196 | if (rule instanceof ExpressionRule) |
203 | op = ((ExpressionRule)rule).getOp(); | |
197 | op = ((ExpressionRule) rule).getOp(); | |
204 | 198 | else if (rule instanceof ActionRule) |
205 | 199 | op = ((ActionRule) rule).getOp(); |
206 | 200 | else { |
207 | 201 | log.error("unexpected rule instance"); |
208 | 202 | continue; |
209 | 203 | } |
210 | if (op instanceof AbstractBinaryOp){ | |
204 | if (op instanceof AbstractBinaryOp) { | |
211 | 205 | AbstractBinaryOp binOp = (AbstractBinaryOp) op; |
212 | 206 | binOp.setFirst(compileOp(tests, binOp.getFirst())); |
213 | 207 | binOp.setSecond(compileOp(tests, binOp.getSecond())); |
214 | 208 | op = compileOp(tests, binOp); |
215 | } else if (op instanceof AbstractOp){ | |
209 | } else if (op instanceof AbstractOp) { | |
216 | 210 | op = compileOp(tests, op); |
217 | } else if (op instanceof LinkedBinaryOp){ | |
211 | } else if (op instanceof LinkedBinaryOp) { | |
218 | 212 | ((LinkedBinaryOp) op).setFirst(compileOp(tests, ((LinkedBinaryOp) op).getFirst())); |
219 | 213 | ((LinkedBinaryOp) op).setSecond(compileOp(tests, ((LinkedBinaryOp) op).getSecond())); |
220 | 214 | } else if (op instanceof LinkedOp) { |
225 | 219 | continue; |
226 | 220 | } |
227 | 221 | if (rule instanceof ExpressionRule) |
228 | ((ExpressionRule)rule).setOp(op); | |
222 | ((ExpressionRule) rule).setOp(op); | |
229 | 223 | else if (rule instanceof ActionRule) |
230 | 224 | ((ActionRule) rule).setOp(op); |
231 | 225 | else { |
232 | 226 | log.error("unexpected rule instance"); |
233 | continue; | |
234 | 227 | } |
235 | 228 | } |
236 | 229 | cacheId = 0; |
295 | 288 | // that we have rules to which the finalize rules can be applied |
296 | 289 | throw new IllegalStateException("First call prepare() before setting the finalize rules"); |
297 | 290 | } |
298 | for (Rule rule : rules){ | |
291 | for (Rule rule : rules) { | |
299 | 292 | if (rule.containsExpression(exp)) |
300 | 293 | return true; |
301 | 294 | } |
302 | if (finalizeRule != null){ | |
303 | if (finalizeRule.containsExpression(exp)) | |
304 | return true; | |
305 | } | |
306 | return false; | |
295 | return finalizeRule != null && finalizeRule.containsExpression(exp); | |
307 | 296 | } |
308 | 297 | |
309 | 298 | public BitSet getRules(Element el) { |
312 | 301 | // new element, invalidate all caches |
313 | 302 | cacheId++; |
314 | 303 | |
315 | // Get all the rules that could match from the index. | |
304 | // Get all the rules that could match from the index. | |
316 | 305 | BitSet candidates = new BitSet(); |
317 | 306 | 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); | |
321 | 310 | } |
322 | 311 | return candidates; |
323 | 312 | } |
39 | 39 | import uk.me.parabola.imgfmt.ExitException; |
40 | 40 | import uk.me.parabola.imgfmt.Utils; |
41 | 41 | import uk.me.parabola.log.Logger; |
42 | import uk.me.parabola.mkgmap.Option; | |
43 | import uk.me.parabola.mkgmap.OptionProcessor; | |
44 | 42 | import uk.me.parabola.mkgmap.Options; |
45 | 43 | import uk.me.parabola.mkgmap.general.LevelInfo; |
46 | 44 | import uk.me.parabola.mkgmap.general.LineAdder; |
47 | import uk.me.parabola.mkgmap.general.MapLine; | |
48 | 45 | import uk.me.parabola.mkgmap.reader.osm.FeatureKind; |
49 | 46 | import uk.me.parabola.mkgmap.reader.osm.Rule; |
50 | 47 | import uk.me.parabola.mkgmap.reader.osm.Style; |
73 | 70 | private static final int VERSION = 1; |
74 | 71 | |
75 | 72 | // 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<>( | |
77 | 74 | Arrays.asList("levels", "overview-levels", "extra-used-tags")); |
78 | 75 | |
79 | 76 | // File names |
93 | 90 | private StyleInfo info = new StyleInfo(); |
94 | 91 | |
95 | 92 | // 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<>(); | |
97 | 94 | |
98 | 95 | // 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<>(); | |
100 | 97 | |
101 | 98 | private final RuleSet lines = new RuleSet(); |
102 | 99 | private final RuleSet polygons = new RuleSet(); |
150 | 147 | |
151 | 148 | // read overlays before the style rules to be able to ignore overlaid "wrong" types. |
152 | 149 | readOverlays(); |
153 | ||
154 | readRules(); | |
150 | ||
151 | readRules(props.getProperty("levels")); | |
155 | 152 | |
156 | 153 | ListIterator<StyleImpl> listIterator = baseStyles.listIterator(baseStyles.size()); |
157 | 154 | while (listIterator.hasPrevious()) |
202 | 199 | LineAdder adder = null; |
203 | 200 | |
204 | 201 | 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); | |
210 | 203 | } |
211 | 204 | return adder; |
212 | 205 | } |
213 | 206 | |
214 | 207 | public Set<String> getUsedTags() { |
215 | Set<String> set = new HashSet<String>(); | |
208 | Set<String> set = new HashSet<>(); | |
216 | 209 | set.addAll(relations.getUsedTags()); |
217 | 210 | set.addAll(lines.getUsedTags()); |
218 | 211 | set.addAll(polygons.getUsedTags()); |
223 | 216 | // around situations that we haven't thought of - the style is expected |
224 | 217 | // to get it right for itself. |
225 | 218 | String s = getOption("extra-used-tags"); |
226 | if (s != null && s.trim().isEmpty() == false) | |
219 | if (s != null && !s.trim().isEmpty()) | |
227 | 220 | set.addAll(Arrays.asList(COMMA_OR_SPACE_PATTERN.split(s))); |
228 | 221 | |
229 | 222 | // 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");) { | |
232 | 224 | if (is != null) { |
233 | 225 | BufferedReader br = new BufferedReader(new InputStreamReader(is)); |
234 | //System.out.println("Got built in list"); | |
226 | // System.out.println("Got built in list"); | |
235 | 227 | String line; |
236 | 228 | while ((line = br.readLine()) != null) { |
237 | 229 | line = line.trim(); |
238 | 230 | if (line.startsWith("#")) |
239 | 231 | continue; |
240 | //System.out.println("adding " + line); | |
232 | // System.out.println("adding " + line); | |
241 | 233 | set.add(line); |
242 | 234 | } |
243 | 235 | } |
244 | 236 | } catch (IOException e) { |
245 | 237 | // the file doesn't exist, this is ok but unlikely |
246 | 238 | System.err.println("warning: built in tag list not found"); |
247 | } finally { | |
248 | Utils.closeFile(is); | |
249 | 239 | } |
250 | 240 | return set; |
251 | 241 | } |
252 | 242 | |
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"); | |
255 | 246 | if (l == null) |
256 | 247 | l = LevelInfo.DEFAULT_LEVELS; |
257 | 248 | 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); | |
262 | 251 | } |
263 | 252 | l = generalOptions.get("overview-levels"); |
264 | 253 | if (l != null){ |
272 | 261 | System.err.println("Warning: Overview level not higher than highest normal level. " + l); |
273 | 262 | } |
274 | 263 | } |
275 | List<LevelInfo> tmp = new ArrayList<LevelInfo>(); | |
264 | List<LevelInfo> tmp = new ArrayList<>(); | |
276 | 265 | tmp.addAll(Arrays.asList(levels)); |
277 | 266 | tmp.addAll(Arrays.asList(ovLevels)); |
278 | 267 | levels = tmp.toArray(new LevelInfo[tmp.size()]); |
319 | 308 | private void readOptions() { |
320 | 309 | try { |
321 | 310 | 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."); | |
335 | 318 | } |
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); | |
336 | 323 | } |
337 | 324 | }); |
338 | 325 | |
351 | 338 | Reader br = new BufferedReader(fileLoader.open(FILE_INFO)); |
352 | 339 | info = new StyleInfo(); |
353 | 340 | |
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); | |
368 | 352 | } |
369 | 353 | }); |
370 | 354 |
108 | 108 | // limit arc lengths to what can be handled by RouteArc |
109 | 109 | private static final int MAX_ARC_LENGTH = 20450000; // (1 << 22) * 16 / 3.2808 ~ 20455030*/ |
110 | 110 | |
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; | |
112 | 115 | |
113 | 116 | // nodeIdMap maps a Coord into a CoordNode |
114 | 117 | private IdentityHashMap<Coord, CoordNode> nodeIdMap = new IdentityHashMap<>(); |
115 | 118 | |
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"; | |
117 | 120 | private final HashMap<Integer, Map<String,MapPoint>> pointMap; |
118 | 121 | |
119 | 122 | /** boundary ways with admin_level=2 */ |
127 | 130 | private HashMap<Long, ConvertedWay> modifiedRoads = new HashMap<>(); |
128 | 131 | private HashSet<Long> deletedRoads = new HashSet<>(); |
129 | 132 | |
130 | private int nextNodeId = 1; | |
131 | 133 | private int nextRoadId = 1; |
132 | 134 | |
133 | 135 | private HousenumberGenerator housenumberGenerator; |
153 | 155 | private final boolean mergeRoads; |
154 | 156 | private final boolean routable; |
155 | 157 | private final Tags styleOptionTags; |
156 | private final static String STYLE_OPTION_PREF = "mkgmap:option:"; | |
158 | private static final String STYLE_OPTION_PREF = "mkgmap:option:"; | |
157 | 159 | private final PrefixSuffixFilter prefixSuffixFilter; |
158 | 160 | |
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; | |
169 | 162 | |
170 | 163 | public StyledConverter(Style style, MapCollector collector, EnhancedProperties props) { |
171 | 164 | this.collector = collector; |
207 | 200 | |
208 | 201 | checkRoundabouts = props.getProperty("check-roundabouts",false); |
209 | 202 | 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 | }; | |
211 | 213 | LineAdder overlayAdder = style.getOverlays(lineAdder); |
212 | 214 | if (overlayAdder != null) |
213 | 215 | lineAdder = overlayAdder; |
214 | 216 | linkPOIsToWays = props.getProperty("link-pois-to-ways", false); |
215 | 217 | |
216 | 218 | // undocumented option - usually used for debugging only |
217 | mergeRoads = props.getProperty("no-mergeroads", false) == false; | |
219 | mergeRoads = !props.getProperty("no-mergeroads", false); | |
218 | 220 | routable = props.containsKey("route"); |
219 | 221 | String styleOption= props.getProperty("style-option",null); |
220 | 222 | styleOptionTags = parseStyleOption(styleOption); |
221 | prefixSuffixFilter = new PrefixSuffixFilter(props); | |
222 | 223 | |
223 | 224 | // control calculation of extra nodes in NOD3 / NOD4 |
224 | 225 | admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2); |
254 | 255 | // flag options used in style but not specified in --style-option |
255 | 256 | if (style.getUsedTags() != null) { |
256 | 257 | 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 + ")"); | |
262 | 261 | } |
263 | 262 | } |
264 | 263 | } |
267 | 266 | |
268 | 267 | /** One type result for ways to avoid recreating one for each way. */ |
269 | 268 | private final WayTypeResult wayTypeResult = new WayTypeResult(); |
270 | private class WayTypeResult implements TypeResult | |
271 | { | |
269 | ||
270 | private class WayTypeResult implements TypeResult { | |
272 | 271 | private Way way; |
273 | 272 | /** flag if the rule was fired */ |
274 | 273 | private boolean matched; |
280 | 279 | |
281 | 280 | public void add(Element el, GType type) { |
282 | 281 | this.matched = true; |
283 | if (type.isContinueSearch()) { | |
282 | if (type.isContinueSearch() && el == way) { | |
284 | 283 | // If not already copied, do so now |
285 | if (el == way) | |
286 | el = way.copy(); | |
284 | el = way.copy(); | |
287 | 285 | } |
288 | 286 | postConvertRules(el, type); |
289 | if (type.isRoad() == false) | |
290 | housenumberGenerator.addWay((Way)el); | |
287 | if (!type.isRoad()) | |
288 | housenumberGenerator.addWay((Way) el); | |
291 | 289 | addConvertedWay((Way) el, type); |
292 | 290 | } |
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 | ||
293 | 397 | |
294 | 398 | /** |
295 | 399 | * Retrieves if a rule of the style matched and the way is converted. |
310 | 414 | * |
311 | 415 | * @param way The OSM way. |
312 | 416 | */ |
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"); | |
315 | 419 | private long lastRoadId = 0; |
316 | 420 | private int lineCacheId = 0; |
317 | 421 | private BitSet routingWarningWasPrinted = new BitSet(); |
354 | 458 | } |
355 | 459 | wayTypeResult.setWay(way); |
356 | 460 | lineCacheId = rules.resolveType(lineCacheId, way, wayTypeResult); |
357 | if (wayTypeResult.isMatched() == false) { | |
461 | if (!wayTypeResult.isMatched()) { | |
358 | 462 | // no match found but we have to keep it for house number processing |
359 | 463 | housenumberGenerator.addWay(way); |
360 | 464 | } |
361 | 465 | if (cycleWay != null){ |
362 | 466 | wayTypeResult.setWay(cycleWay); |
363 | 467 | lineCacheId = rules.resolveType(lineCacheId, cycleWay, wayTypeResult); |
364 | if (wayTypeResult.isMatched() == false) { | |
468 | if (!wayTypeResult.isMatched()) { | |
365 | 469 | // no match found but we have to keep it for house number processing |
366 | 470 | housenumberGenerator.addWay(cycleWay); |
367 | 471 | } |
372 | 476 | } else { |
373 | 477 | // way was added as road, check if we also have non-routable lines for the way |
374 | 478 | // 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()) { | |
378 | 482 | cw.setOverlay(true); |
379 | 483 | 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); | |
387 | 490 | } |
388 | } | |
389 | else | |
491 | } else { | |
390 | 492 | break; |
493 | } | |
391 | 494 | } |
392 | 495 | } |
393 | 496 | } |
397 | 500 | String admLevelString = el.getTag("admin_level"); |
398 | 501 | if (admLevelString != null) { |
399 | 502 | try { |
400 | int al = Integer.valueOf(admLevelString); | |
503 | int al = Integer.parseInt(admLevelString); | |
401 | 504 | return al <= admLevelNod3; |
402 | 505 | } catch (NumberFormatException e) { |
506 | // ignore invalid osm data | |
403 | 507 | } |
404 | 508 | } |
405 | 509 | } |
407 | 511 | } |
408 | 512 | |
409 | 513 | 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"); | |
465 | 515 | |
466 | 516 | /** One type result for nodes to avoid recreating one for each node. */ |
467 | 517 | private NodeTypeResult nodeTypeResult = new NodeTypeResult(); |
478 | 528 | |
479 | 529 | public void add(Element el, GType type) { |
480 | 530 | this.matched = true; |
481 | if (type.isContinueSearch()) { | |
531 | if (type.isContinueSearch() && el == node) { | |
482 | 532 | // If not already copied, do so now |
483 | if (el == node) | |
484 | el = node.copy(); | |
485 | } | |
486 | ||
533 | el = node.copy(); | |
534 | } | |
535 | ||
487 | 536 | postConvertRules(el, type); |
488 | housenumberGenerator.addNode((Node)el); | |
537 | housenumberGenerator.addNode((Node) el); | |
489 | 538 | 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; | |
490 | 612 | } |
491 | 613 | |
492 | 614 | /** |
514 | 636 | |
515 | 637 | nodeTypeResult.setNode(node); |
516 | 638 | nodeRules.resolveType(node, nodeTypeResult); |
517 | if (nodeTypeResult.isMatched() == false) { | |
639 | if (!nodeTypeResult.isMatched()) { | |
518 | 640 | // no match found but we have to keep it for house number processing |
519 | 641 | housenumberGenerator.addNode(node); |
520 | 642 | } |
606 | 728 | */ |
607 | 729 | private void removeRestrictionsWithWay(Level logLevel, Way way, String reason){ |
608 | 730 | 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); | |
613 | 736 | } |
614 | 737 | rr.setInvalid(); |
615 | 738 | restrictions.remove(rr); |
740 | 863 | * road network. |
741 | 864 | */ |
742 | 865 | private void mergeRoads() { |
743 | if (mergeRoads == false) { | |
866 | if (mergeRoads) { | |
867 | RoadMerger merger = new RoadMerger(); | |
868 | roads = merger.merge(roads, restrictions); | |
869 | } else { | |
744 | 870 | log.info("Merging roads is disabled"); |
745 | return; | |
746 | } | |
747 | ||
748 | RoadMerger merger = new RoadMerger(); | |
749 | roads = merger.merge(roads, restrictions); | |
871 | } | |
750 | 872 | } |
751 | 873 | |
752 | 874 | public void end() { |
811 | 933 | if (cw.isValid()) |
812 | 934 | addRoad(cw); |
813 | 935 | } |
814 | housenumberGenerator.generate(lineAdder, nextNodeId); | |
936 | housenumberGenerator.generate(lineAdder); | |
815 | 937 | housenumberGenerator = null; |
816 | 938 | |
817 | 939 | if (routable) |
912 | 1034 | * |
913 | 1035 | */ |
914 | 1036 | private void checkRoundabout(ConvertedWay cw) { |
915 | if (cw.isRoundabout() == false) | |
1037 | if (!cw.isRoundabout()) | |
916 | 1038 | return; |
917 | 1039 | Way way = cw.getWay(); |
918 | 1040 | List<Coord> points = way.getPoints(); |
956 | 1078 | boolean clockwise = dir > 0; |
957 | 1079 | if (points.get(0) == points.get(points.size() - 1)) { |
958 | 1080 | // 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 " | |
963 | 1083 | + centre.toOSMURL() + ")"); |
964 | 1084 | way.reverse(); |
965 | 1085 | } |
966 | } else if (driveOnLeft == true && !clockwise || driveOnLeft == false && clockwise) { | |
1086 | } else if (driveOnLeft && !clockwise || !driveOnLeft && clockwise) { | |
967 | 1087 | // 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 " | |
970 | 1089 | + points.get(0).toOSMURL() + ")"); |
971 | 1090 | } |
972 | 1091 | } |
973 | 1092 | } |
974 | 1093 | } |
975 | ||
976 | 1094 | |
977 | 1095 | /** |
978 | 1096 | * If POI changes access restrictions (e.g. bollards), create corresponding |
996 | 1114 | CoordNode lastNode = null; |
997 | 1115 | for (Coord co: way.getPoints()){ |
998 | 1116 | // not 100% fail safe: points may have been replaced before |
999 | if (co instanceof CoordNode == false) | |
1117 | if (!(co instanceof CoordNode)) | |
1000 | 1118 | continue; |
1001 | 1119 | CoordNode cn = (CoordNode) co; |
1002 | 1120 | if (p.highPrecEquals(cn)){ |
1020 | 1138 | log.error("Did not find CoordPOI node at " + p.toOSMURL() + " in ways " + wayList); |
1021 | 1139 | continue; |
1022 | 1140 | } |
1023 | if (viaIsUnique == false){ | |
1141 | if (!viaIsUnique){ | |
1024 | 1142 | log.error("Found multiple points with equal coords as CoordPOI at " + p.toOSMURL()); |
1025 | 1143 | continue; |
1026 | 1144 | } |
1067 | 1185 | for (long id : rr.getWayIds()) |
1068 | 1186 | wayRelMap.add(id, rr); |
1069 | 1187 | } |
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()); | |
1080 | 1196 | } |
1081 | 1197 | } |
1082 | 1198 | } |
1127 | 1243 | clipper.clipLine(line, lineAdder); |
1128 | 1244 | } |
1129 | 1245 | |
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 | ||
1236 | 1246 | private static final short[] labelTagKeys = { |
1237 | 1247 | TagDict.getInstance().xlate("mkgmap:label:1"), |
1238 | 1248 | TagDict.getInstance().xlate("mkgmap:label:2"), |
1328 | 1338 | /** |
1329 | 1339 | * Add a way to the road network. May call itself recursively and |
1330 | 1340 | * 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 | |
1333 | 1342 | */ |
1334 | 1343 | private void addRoad(ConvertedWay cw) { |
1335 | 1344 | Way way = cw.getWay(); |
1391 | 1400 | splitPoint = prev.makeBetweenPoint(splitPoint, neededLength / dist); |
1392 | 1401 | double newDist = splitPoint.distance(prev); |
1393 | 1402 | segmentLength += newDist - dist; |
1394 | splitPoint.incHighwayCount(); | |
1395 | 1403 | points.add(splitPos, splitPoint); |
1404 | splitPoint.incHighwayCount(); // new point is on highway | |
1396 | 1405 | } |
1397 | 1406 | if ((splitPos + 1) < points.size() |
1398 | 1407 | && safeToSplitWay(points, splitPos, i, points.size() - 1)) { |
1402 | 1411 | } |
1403 | 1412 | } |
1404 | 1413 | 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()); | |
1407 | 1417 | } |
1408 | 1418 | 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()); | |
1411 | 1422 | } |
1412 | 1423 | } |
1413 | 1424 | } |
1421 | 1432 | && points.get(i + 1) instanceof CoordPOI) { |
1422 | 1433 | CoordPOI cp = (CoordPOI) points.get(i + 1); |
1423 | 1434 | 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)); | |
1458 | 1465 | } |
1459 | 1466 | } |
1460 | 1467 | } |
1501 | 1508 | } |
1502 | 1509 | } |
1503 | 1510 | |
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 | */ | |
1504 | 1516 | private void addRoadAfterSplittingLoops(ConvertedWay cw) { |
1505 | 1517 | Way way = cw.getWay(); |
1506 | // make sure the way has nodes at each end | |
1507 | way.getFirstPoint().incHighwayCount(); | |
1508 | way.getLastPoint().incHighwayCount(); | |
1509 | ||
1510 | 1518 | // check if the way is a loop or intersects with itself |
1511 | 1519 | |
1512 | 1520 | boolean wayWasSplit = true; // aka rescan required |
1703 | 1711 | for (;;){ |
1704 | 1712 | int dlat = Math.abs(nextP.getLatitude() - p.getLatitude()); |
1705 | 1713 | 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)); | |
1708 | 1716 | nextP = p.makeBetweenPoint(nextP, frac); |
1709 | 1717 | nextP.incHighwayCount(); |
1710 | 1718 | points.add(i + 1, nextP); |
1711 | 1719 | double newD = p.distance(nextP); |
1712 | 1720 | 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"); | |
1714 | 1724 | d = newD; |
1715 | } else | |
1725 | } else { | |
1716 | 1726 | break; |
1727 | } | |
1717 | 1728 | } |
1718 | 1729 | |
1719 | 1730 | wayBBox.addPoint(nextP); |
1720 | 1731 | |
1721 | if((arcLength + d) > MAX_ARC_LENGTH) { | |
1732 | if ((arcLength + d) > MAX_ARC_LENGTH) { | |
1722 | 1733 | if (i <= 0) |
1723 | 1734 | log.error("internal error: long arc segment was not split", debugWayName); |
1724 | 1735 | assert i > 0 : "long arc segment was not split"; |
1749 | 1760 | arcLength += d; |
1750 | 1761 | } |
1751 | 1762 | } |
1752 | if(p.getHighwayCount() > 1 || p.getOnCountryBorder()) { | |
1763 | if(p.getHighwayCount() > 1 || (routable && p.getOnCountryBorder())) { | |
1753 | 1764 | // 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 | ||
1761 | 1765 | 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 | |
1763 | 1767 | CoordPOI cp = (CoordPOI) p; |
1764 | 1768 | if (cp.getConvertToViaInRouteRestriction()){ |
1765 | 1769 | String wayPOI = way.getTag(WAY_POI_NODE_IDS); |
1800 | 1804 | elementSetup(line, cw.getGType(), way); |
1801 | 1805 | line.setPoints(points); |
1802 | 1806 | MapRoad road = new MapRoad(nextRoadId++, way.getId(), line); |
1803 | if (routable == false) | |
1807 | if (!routable) { | |
1804 | 1808 | road.skipAddToNOD(true); |
1809 | } | |
1805 | 1810 | |
1806 | 1811 | boolean doFlareCheck = true; |
1807 | 1812 | |
1840 | 1845 | if (cw.isCarpool()) |
1841 | 1846 | road.setCarpoolLane(); |
1842 | 1847 | |
1843 | if (cw.isThroughroute() == false) | |
1848 | if (!cw.isThroughroute()) | |
1844 | 1849 | road.setNoThroughRouting(); |
1845 | 1850 | |
1846 | if(cw.isToll()) | |
1851 | if (cw.isToll()) | |
1847 | 1852 | road.setToll(); |
1848 | 1853 | |
1849 | 1854 | // by default, ways are paved |
1850 | if(cw.isUnpaved()) | |
1855 | if (cw.isUnpaved()) | |
1851 | 1856 | road.paved(false); |
1852 | 1857 | |
1853 | 1858 | // by default, way's are not ferry routes |
1854 | if(cw.isFerry()) | |
1859 | if (cw.isFerry()) | |
1855 | 1860 | road.ferry(true); |
1856 | 1861 | |
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 | ||
1857 | 1867 | int numNodes = nodeIndices.size(); |
1858 | if (way.isViaWay() && numNodes > 2){ | |
1868 | if (way.isViaWay() && numNodes > 2) { | |
1859 | 1869 | List<RestrictionRelation> rrList = wayRelMap.get(way.getId()); |
1860 | for (RestrictionRelation rr : rrList){ | |
1870 | for (RestrictionRelation rr : rrList) { | |
1861 | 1871 | rr.updateViaWay(way, nodeIndices); |
1862 | 1872 | } |
1863 | 1873 | } |
1866 | 1876 | // replace Coords that are nodes with CoordNodes |
1867 | 1877 | for(int i = 0; i < numNodes; ++i) { |
1868 | 1878 | 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); | |
1877 | 1892 | } |
1878 | 1893 | } |
1879 | 1894 | |
1893 | 1908 | |
1894 | 1909 | if(trailingWay != null) |
1895 | 1910 | 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(); | |
1905 | 1911 | } |
1906 | 1912 | |
1907 | 1913 | /** |
2003 | 2009 | } |
2004 | 2010 | } |
2005 | 2011 | |
2012 | private static final String[] CONNECTION_TAGS = { "mkgmap:set_unconnected_type", "mkgmap:set_semi_connected_type" }; | |
2013 | ||
2006 | 2014 | /** |
2007 | 2015 | * Detect roads that do not share any node with another road. |
2008 | 2016 | * 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 | ||
2013 | 2023 | // for dead-end-check only: will contain ways with loops (also simply closed ways) |
2014 | 2024 | HashSet<Way> selfConnectors = new HashSet<>(); |
2015 | ||
2025 | ||
2016 | 2026 | // collect nodes that might connect roads |
2017 | 2027 | long lastId = 0; |
2018 | for (ConvertedWay cw :roads){ | |
2028 | for (ConvertedWay cw : roads) { | |
2019 | 2029 | Way way = cw.getWay(); |
2020 | 2030 | if (way.getId() == lastId) |
2021 | 2031 | continue; |
2022 | 2032 | 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) { | |
2025 | 2035 | HashSet<Way> ways = connectors.get(p); |
2026 | if (ways == null){ | |
2036 | if (ways == null) { | |
2027 | 2037 | ways = new HashSet<>(); |
2028 | 2038 | connectors.put(p, ways); |
2029 | 2039 | } |
2033 | 2043 | } |
2034 | 2044 | } |
2035 | 2045 | } |
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 | ||
2039 | 2050 | Iterator<ConvertedWay> iter = roads.iterator(); |
2040 | while(iter.hasNext()){ | |
2051 | while (iter.hasNext()) { | |
2041 | 2052 | ConvertedWay cw = iter.next(); |
2042 | 2053 | if (!cw.isValid()) |
2043 | 2054 | continue; |
2044 | 2055 | 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; | |
2092 | 2195 | } |
2093 | 2196 | } |
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 | } | |
2110 | 2206 | } |
2111 | 2207 | } |
2112 | 2208 | } |
2209 | continue; | |
2113 | 2210 | } |
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 | } | |
2117 | 2226 | } |
2118 | 2227 | } |
2119 | 2228 | } |
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 | ||
2167 | 2238 | /** |
2168 | 2239 | * Make sure that only CoordPOI which affect routing will be treated as |
2169 | 2240 | * nodes in the following routines. |
2188 | 2259 | Node node = cp.getNode(); |
2189 | 2260 | boolean usedInThisWay = false; |
2190 | 2261 | 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; | |
2195 | 2265 | } |
2196 | 2266 | 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) { | |
2200 | 2270 | // node is more restrictive |
2201 | if (p.getHighwayCount() >= 2 || (i != 0 && i != numPoints-1)){ | |
2271 | if (p.getHighwayCount() >= 2 || (i != 0 && i != numPoints - 1)) { | |
2202 | 2272 | usedInThisWay = true; |
2203 | 2273 | 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"); | |
2204 | 2277 | } |
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 | } | |
2210 | 2282 | } |
2211 | if (usedInThisWay){ | |
2283 | if (usedInThisWay) { | |
2212 | 2284 | cp.setUsed(true); |
2213 | wayPOI += "["+ node.getId()+"]"; | |
2285 | wayPOI += "[" + node.getId() + "]"; | |
2214 | 2286 | } |
2215 | 2287 | } |
2216 | 2288 | } |
2217 | 2289 | if (wayPOI.isEmpty()) { |
2218 | 2290 | way.deleteTag("mkgmap:way-has-pois"); |
2219 | 2291 | log.info("ignoring CoordPOI(s) for way", way.toBrowseURL(), "because routing is not affected."); |
2220 | } | |
2221 | else { | |
2292 | } else { | |
2222 | 2293 | way.addTag(WAY_POI_NODE_IDS, wayPOI); |
2223 | 2294 | } |
2224 | 2295 | } |
90 | 90 | throw new SyntaxException(ts, "Unrecognised type command '" + w + '\''); |
91 | 91 | } |
92 | 92 | } |
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 | } | |
94 | 104 | gt.fixLevels(levels); |
95 | 105 | if ("lines".equals(ts.getFileName())){ |
96 | 106 | if(gt.getRoadClass() < 0 || gt.getRoadClass() > 4) |
53 | 53 | public class WrongAngleFixer { |
54 | 54 | private static final Logger log = Logger.getLogger(WrongAngleFixer.class); |
55 | 55 | |
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; | |
59 | 59 | |
60 | 60 | private final Area bbox; |
61 | private final static String DEBUG_PATH = null; | |
61 | private static final String DEBUG_PATH = null; | |
62 | 62 | static final int MODE_ROADS = 0; |
63 | 63 | static final int MODE_LINES = 1; |
64 | 64 | private int mode = MODE_ROADS; |
67 | 67 | |
68 | 68 | public WrongAngleFixer(Area bbox) { |
69 | 69 | 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); | |
80 | 78 | } |
81 | 79 | } |
82 | 80 | |
90 | 88 | * @param deletedRoads Will be enlarged by all roads in roads that were set to null by this method |
91 | 89 | * @param restrictions Map with restriction relations |
92 | 90 | */ |
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) { | |
94 | 93 | printBadAngles("bad_angles_start", roads); |
95 | 94 | writeOSM("roads_orig", roads); |
96 | 95 | writeOSM("lines_orig", lines); |
116 | 115 | * @param convertedWays |
117 | 116 | * @param coordMap |
118 | 117 | */ |
119 | private void replaceDuplicateBoundaryNodes(List<ConvertedWay> convertedWays, Long2ObjectOpenHashMap<Coord> coordMap) { | |
118 | private static void replaceDuplicateBoundaryNodes(List<ConvertedWay> convertedWays, | |
119 | Long2ObjectOpenHashMap<Coord> coordMap) { | |
120 | 120 | for (ConvertedWay cw : convertedWays) { |
121 | if (!cw.isValid() || cw.isOverlay()) | |
121 | if (!cw.isValid() || cw.isOverlay()) | |
122 | 122 | continue; |
123 | 123 | Way way = cw.getWay(); |
124 | 124 | List<Coord> points = way.getPoints(); |
131 | 131 | coordMap.put(Utils.coord2Long(co), co); |
132 | 132 | else { |
133 | 133 | 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); | |
135 | 135 | } |
136 | 136 | points.set(i, repl); |
137 | 137 | } |
139 | 139 | } |
140 | 140 | } |
141 | 141 | |
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 | ||
181 | 142 | /** |
182 | 143 | * Common code to handle replacements of points in ways. Checks for special |
183 | 144 | * cases regarding CoordPOI. |
187 | 148 | * @param replacements the Map containing the replaced points |
188 | 149 | * @return the replacement |
189 | 150 | */ |
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) { | |
192 | 152 | // check if this point is to be replaced because |
193 | 153 | // it was previously merged into another point |
194 | 154 | if (p.isReplaced()) { |
204 | 164 | Node node = cp.getNode(); |
205 | 165 | if (cp.isUsed() && way != null && way.getId() != 0) { |
206 | 166 | 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() + "]")) { | |
208 | 168 | if (replacement instanceof CoordPOI) { |
209 | 169 | Node rNode = ((CoordPOI) replacement).getNode(); |
210 | 170 | 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()); | |
216 | 177 | } |
217 | else | |
218 | log.warn("CoordPOI", node.getId(), | |
219 | "replaced by ignored CoordPOI", | |
220 | rNode.getId(), "in way", | |
221 | way.toBrowseURL()); | |
222 | 178 | } |
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", | |
226 | 181 | way.toBrowseURL()); |
182 | } | |
227 | 183 | } |
228 | 184 | } |
229 | 185 | } |
230 | 186 | return replacement; |
231 | } | |
187 | } | |
232 | 188 | log.error("replacement not found for point " + p.toOSMURL()); |
233 | ||
189 | ||
234 | 190 | } |
235 | 191 | return p; |
236 | 192 | } |
244 | 200 | * @param deletedRoads set of ids of deleted routable ways (modified by this routine) |
245 | 201 | * @param restrictions Map with restriction relations. The restriction relations may be modified by this routine |
246 | 202 | */ |
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) { | |
248 | 205 | // replacements maps those nodes that have been replaced to |
249 | 206 | // the node that replaces them |
250 | 207 | Map<Coord, Coord> replacements = new IdentityHashMap<>(); |
267 | 224 | break; |
268 | 225 | anotherPassRequired = false; |
269 | 226 | 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); | |
271 | 228 | |
272 | 229 | // Step 1: detect points which are parts of line segments with wrong bearings |
273 | 230 | lastWay = null; |
274 | 231 | for (ConvertedWay cw : convertedWays) { |
275 | if (!cw.isValid() || cw.isOverlay()) | |
232 | if (!cw.isValid() || cw.isOverlay()) | |
276 | 233 | continue; |
277 | 234 | 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)) | |
281 | 238 | continue; |
282 | 239 | lastWay = way; |
283 | 240 | List<Coord> points = way.getPoints(); |
284 | ||
241 | ||
285 | 242 | // scan through the way's points looking for line segments with big |
286 | 243 | // bearing errors |
287 | 244 | 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); | |
290 | 247 | boolean hasNonEqualPoints = false; |
291 | 248 | for (int i = 0; i < points.size(); ++i) { |
292 | 249 | Coord p = points.get(i); |
293 | 250 | if (pass == 1) |
294 | 251 | p.setRemove(false); |
295 | 252 | p = getReplacement(p, way, replacements); |
296 | if (i == 0 || i == points.size()-1){ | |
253 | if (i == 0 || i == points.size() - 1) { | |
297 | 254 | p.setEndOfWay(true); |
298 | 255 | } |
299 | ||
256 | ||
300 | 257 | if (prev != null) { |
301 | if (pass == 1 && p.equals(prev) == false) | |
258 | if (pass == 1 && !p.equals(prev)) | |
302 | 259 | 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) { | |
305 | 262 | // bearing error is big |
306 | 263 | p.setPartOfBadAngle(true); |
307 | 264 | prev.setPartOfBadAngle(true); |
309 | 266 | } |
310 | 267 | prev = p; |
311 | 268 | } |
312 | if (pass == 1 && hasNonEqualPoints == false){ | |
269 | if (pass == 1 && !hasNonEqualPoints) { | |
313 | 270 | 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"); | |
315 | 272 | } |
316 | 273 | } |
317 | 274 | // Step 2: collect the line segments that are connected to critical points |
318 | 275 | IdentityHashMap<Coord, CenterOfAngle> centerMap = new IdentityHashMap<>(); |
319 | 276 | 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 | ||
322 | 279 | lastWay = null; |
323 | 280 | for (ConvertedWay cw : convertedWays) { |
324 | if (!cw.isValid() || cw.isOverlay()) | |
281 | if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay)) | |
325 | 282 | continue; |
326 | 283 | 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)) | |
330 | 285 | continue; |
331 | 286 | lastWay = way; |
332 | 287 | |
335 | 290 | // scan through the way's points looking for line segments with big |
336 | 291 | // bearing errors |
337 | 292 | 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); | |
340 | 295 | for (int i = 0; i < points.size(); ++i) { |
341 | 296 | Coord p = points.get(i); |
342 | 297 | if (prev != null) { |
343 | if (p == prev){ | |
298 | if (p == prev) { | |
344 | 299 | points.remove(i); |
345 | 300 | --i; |
346 | 301 | if (mode == MODE_ROADS) |
360 | 315 | // way has only two points, don't merge them |
361 | 316 | coa1.addBadMergeCandidate(coa2); |
362 | 317 | } |
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); | |
370 | 322 | } |
371 | 323 | } |
372 | 324 | } |
375 | 327 | if (pass == 1 && wayHasSpecialPoints) |
376 | 328 | waysWithBearingErrors.add(way); |
377 | 329 | } |
378 | markOverlaps(overlaps,centers); | |
330 | markOverlaps(overlaps, centers); | |
379 | 331 | overlaps.clear(); |
380 | 332 | // Step 3: Update list of ways with bearing errors or points next to them |
381 | 333 | lastWay = null; |
382 | 334 | for (ConvertedWay cw : convertedWays) { |
383 | if (!cw.isValid() || cw.isOverlay()) | |
335 | if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay)) | |
384 | 336 | continue; |
385 | 337 | Way way = cw.getWay(); |
386 | if (way.equals(lastWay)) | |
387 | continue; | |
388 | 338 | 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(); | |
392 | 341 | // 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 | } | |
400 | 348 | } |
401 | 349 | } |
402 | 350 | } |
405 | 353 | // Step 4: try to correct the errors |
406 | 354 | List<CenterOfAngle> checkAgainList = null; |
407 | 355 | boolean tryMerge = false; |
408 | while (true){ | |
356 | while (true) { | |
409 | 357 | checkAgainList = new ArrayList<>(); |
410 | 358 | for (CenterOfAngle coa : centers) { |
411 | 359 | coa.center.setPartOfBadAngle(false); // reset flag for next pass |
412 | 360 | if (coa.getCurrentLocation(replacements) == null) |
413 | 361 | continue; // removed center |
414 | if (coa.isOK(replacements) == false) { | |
362 | if (!coa.isOK(replacements)) { | |
415 | 363 | boolean changed = coa.tryChange(replacements, tryMerge); |
416 | if (changed){ | |
364 | if (changed) { | |
417 | 365 | if (DEBUG_PATH != null) |
418 | 366 | changedPlaces.add(coa.center); |
419 | 367 | continue; |
432 | 380 | boolean lastWayModified = false; |
433 | 381 | ConvertedWay lastConvertedWay = null; |
434 | 382 | for (ConvertedWay cw : convertedWays) { |
435 | if (!cw.isValid() || cw.isOverlay()) | |
383 | if (!cw.isValid() || cw.isOverlay() || !waysWithBearingErrors.contains(cw.getWay())) | |
436 | 384 | continue; |
437 | 385 | Way way = cw.getWay(); |
438 | if (waysWithBearingErrors.contains(way) == false) | |
439 | continue; | |
440 | 386 | List<Coord> points = way.getPoints(); |
441 | 387 | if (way.equals(lastWay)) { |
442 | if (lastWayModified){ | |
388 | if (lastWayModified) { | |
443 | 389 | points.clear(); |
444 | 390 | points.addAll(lastWay.getPoints()); |
445 | 391 | if (cw.isReversed() != lastConvertedWay.isReversed()) |
460 | 406 | points.remove(i); |
461 | 407 | anotherPassRequired = true; |
462 | 408 | lastWayModified = true; |
463 | if (i > 0 && i < points.size()) { | |
409 | if (i > 0 && i < points.size() && points.get(i - 1) == points.get(i)) { | |
464 | 410 | // special case: handle micro loop |
465 | if (points.get(i - 1) == points.get(i)) | |
466 | points.remove(i); | |
467 | } | |
411 | points.remove(i); | |
412 | } | |
468 | 413 | continue; |
469 | 414 | } |
470 | 415 | // check if this point is to be replaced because |
471 | 416 | // it was previously moved |
472 | 417 | Coord replacement = getReplacement(p, way, replacements); |
473 | if (p == replacement) | |
418 | if (p == replacement) | |
474 | 419 | continue; |
475 | 420 | |
476 | if (p.isViaNodeOfRestriction()){ | |
421 | if (p.isViaNodeOfRestriction()) { | |
477 | 422 | // make sure that we find the restriction with the new coord instance |
478 | 423 | replacement.setViaNodeOfRestriction(true); |
479 | 424 | p.setViaNodeOfRestriction(false); |
491 | 436 | points.remove(i); |
492 | 437 | anotherPassRequired = true; |
493 | 438 | } |
494 | if (i -1 >= 0 && points.get(i-1) == p){ | |
439 | if (i - 1 >= 0 && points.get(i - 1) == p) { | |
495 | 440 | points.remove(i); |
496 | 441 | anotherPassRequired = true; |
497 | 442 | } |
498 | 443 | } |
499 | if (lastWayModified && mode == MODE_ROADS){ | |
444 | if (lastWayModified && mode == MODE_ROADS) { | |
500 | 445 | modifiedRoads.put(way.getId(), cw); |
501 | 446 | } |
502 | 447 | } |
532 | 477 | if (points.size() < 2) { |
533 | 478 | if (log.isInfoEnabled()) |
534 | 479 | 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 | } | |
537 | 483 | |
538 | 484 | if (mode == MODE_ROADS) |
539 | 485 | deletedRoads.add(way.getId()); |
541 | 487 | continue; |
542 | 488 | } |
543 | 489 | if (way.equals(lastWay)) { |
544 | if (lastWayModified){ | |
490 | if (lastWayModified) { | |
545 | 491 | points.clear(); |
546 | 492 | points.addAll(lastWay.getPoints()); |
547 | 493 | if (cw.isReversed() != lastConvertedWay.isReversed()) |
548 | 494 | Collections.reverse(points); |
549 | ||
495 | ||
550 | 496 | } |
551 | 497 | continue; |
552 | 498 | } |
557 | 503 | // loop backwards because we may delete points |
558 | 504 | for (int i = points.size() - 2; i >= 0; i--) { |
559 | 505 | Coord p = points.get(i); |
560 | if (p == prev){ | |
506 | if (p == prev) { | |
561 | 507 | points.remove(i); |
562 | 508 | lastWayModified = true; |
563 | 509 | } |
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 | // } | |
568 | 510 | prev = p; |
569 | 511 | } |
570 | 512 | } |
571 | if (mode == MODE_ROADS){ | |
513 | if (mode == MODE_ROADS) { | |
572 | 514 | // treat special case: non-routable ways may be connected to moved |
573 | 515 | // points in roads |
574 | 516 | for (ConvertedWay cw : lines) { |
590 | 532 | } |
591 | 533 | } |
592 | 534 | |
593 | for (RestrictionRelation rr: restrictions){ | |
594 | for (Coord p: rr.getViaCoords()){ | |
535 | for (RestrictionRelation rr : restrictions) { | |
536 | for (Coord p : rr.getViaCoords()) { | |
595 | 537 | Coord replacement = getReplacement(p, null, replacements); |
596 | if (p != replacement){ | |
538 | if (p != replacement) { | |
597 | 539 | rr.replaceViaCoord(p, replacement); |
598 | 540 | } |
599 | 541 | } |
607 | 549 | } |
608 | 550 | if (anotherPassRequired) { |
609 | 551 | 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) { | |
616 | 560 | CenterOfAngle coa = centerMap.get(p); |
617 | 561 | if (coa == null) { |
618 | 562 | coa = new CenterOfAngle(p, centerMap.size() + 1); |
619 | 563 | centerMap.put(p, coa); |
620 | 564 | centers.add(coa); |
621 | 565 | 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 | } | |
630 | 568 | } |
631 | 569 | return coa; |
632 | 570 | } |
633 | 571 | |
634 | 572 | 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()) { | |
636 | 574 | if (entry.getValue().size() > 1) { |
637 | 575 | Coord p = entry.getKey(); |
638 | // log.error("roads",mode==MODE_ROADS,"pass",pass,p,p.toDegreeString()); | |
639 | 576 | for (CenterOfAngle coa : centers) { |
640 | 577 | if (coa.center.equals(p)) { |
641 | 578 | // two different centres are on the same Garmin point and they |
646 | 583 | } |
647 | 584 | } |
648 | 585 | } |
649 | ||
586 | ||
650 | 587 | /** |
651 | 588 | * remove obsolete points in ways. Obsolete are points which are |
652 | 589 | * very close to 180 degrees angles in the real line or wrong points. |
655 | 592 | * @param convertedWays |
656 | 593 | * @param modifiedRoads |
657 | 594 | */ |
658 | private void removeObsoletePoints(List<ConvertedWay> convertedWays, HashMap<Long, ConvertedWay> modifiedRoads){ | |
595 | private void removeObsoletePoints(List<ConvertedWay> convertedWays, Map<Long, ConvertedWay> modifiedRoads) { | |
659 | 596 | ConvertedWay lastConvertedWay = null; |
660 | 597 | int numPointsRemoved = 0; |
661 | 598 | boolean lastWasModified = false; |
668 | 605 | continue; |
669 | 606 | Way way = cw.getWay(); |
670 | 607 | if (lastConvertedWay != null && way.equals(lastConvertedWay.getWay())) { |
671 | if (lastWasModified){ | |
608 | if (lastWasModified) { | |
672 | 609 | List<Coord> points = way.getPoints(); |
673 | 610 | points.clear(); |
674 | 611 | points.addAll(lastConvertedWay.getPoints()); |
688 | 625 | |
689 | 626 | // scan through the way's points looking for points which are |
690 | 627 | // 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++) { | |
692 | 629 | Coord cm = points.get(i); |
693 | if (allowedToRemove(cm) == false){ | |
630 | if (!allowedToRemove(cm)) { | |
694 | 631 | modifiedPoints.add(cm); |
695 | 632 | continue; |
696 | 633 | } |
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) { | |
700 | 637 | // loop, handled by split routine |
701 | 638 | modifiedPoints.add(cm); |
702 | continue; | |
639 | continue; | |
703 | 640 | } |
704 | 641 | |
705 | 642 | boolean keepThis = true; |
706 | 643 | 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) { | |
708 | 645 | double distance = cm.distToLineSegment(c1, c2); |
709 | if (distance >= maxErrorDistance){ | |
646 | if (distance >= maxErrorDistance) { | |
710 | 647 | modifiedPoints.add(cm); |
711 | 648 | continue; |
712 | 649 | } |
713 | 650 | keepThis = false; |
714 | 651 | } else { |
715 | 652 | 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 | |
718 | 655 | keepThis = false; |
719 | } else if (Math.abs(displayedAngle) < 1){ | |
656 | } else if (Math.abs(displayedAngle) < 1) { | |
720 | 657 | // displayed line is nearly straight |
721 | if (c1.getHighwayCount() < 2 && c2.getHighwayCount() < 2){ | |
658 | if (c1.getHighwayCount() < 2 && c2.getHighwayCount() < 2) { | |
722 | 659 | // we can remove the point |
723 | 660 | keepThis = false; |
724 | 661 | } |
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 | |
727 | 666 | keepThis = false; |
728 | 667 | } else if (c1.equals(c2)) { |
729 | 668 | // 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()); | |
731 | 671 | keepThis = false; |
732 | ||
733 | } | |
734 | } | |
735 | if (keepThis){ | |
672 | ||
673 | } | |
674 | } | |
675 | if (keepThis) { | |
736 | 676 | modifiedPoints.add(cm); |
737 | 677 | continue; |
738 | 678 | } |
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) { | |
742 | 684 | obsoletePoints.add(cm); |
743 | 685 | removedInWay.add(cm); |
744 | 686 | } |
745 | 687 | numPointsRemoved++; |
746 | 688 | 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)); | |
751 | 693 | points.clear(); |
752 | 694 | points.addAll(modifiedPoints); |
753 | 695 | if (mode == MODE_ROADS) |
754 | 696 | 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)); | |
766 | 705 | } |
767 | 706 | log.info("Removed", numPointsRemoved, "obsolete points in lines"); |
768 | 707 | } |
792 | 731 | // on almost straight line and therefore obsolete |
793 | 732 | for (int i = points.size() - 2; i >= 1; --i) { |
794 | 733 | 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) { | |
798 | 737 | // loop, handled by split routine |
799 | continue; | |
738 | continue; | |
800 | 739 | } |
801 | 740 | double realAngle = Utils.getAngle(c1, cm, c2); |
802 | 741 | double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2); |
803 | if (Math.abs(displayedAngle-realAngle) > 30){ | |
742 | if (Math.abs(displayedAngle - realAngle) > 30) { | |
804 | 743 | badAngles.add(cm); |
805 | 744 | hasBadAngles = true; |
806 | // badAngles.addAll(cm.getAlternativePositions()); | |
807 | } | |
808 | ||
809 | } | |
810 | if (points.size() > 2){ | |
745 | } | |
746 | ||
747 | } | |
748 | if (points.size() > 2) { | |
811 | 749 | 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) { | |
814 | 752 | Coord cm = points.get(0); |
815 | Coord c1 = points.get(points.size()-2); | |
753 | Coord c1 = points.get(points.size() - 2); | |
816 | 754 | Coord c2 = points.get(1); |
817 | if (c1 == c2){ | |
755 | if (c1 == c2) { | |
818 | 756 | // loop, handled by split routine |
819 | continue; | |
757 | continue; | |
820 | 758 | } |
821 | 759 | double realAngle = Utils.getAngle(c1, cm, c2); |
822 | 760 | double displayedAngle = Utils.getDisplayedAngle(c1, cm, c2); |
823 | if (Math.abs(displayedAngle-realAngle) > 30){ | |
761 | if (Math.abs(displayedAngle - realAngle) > 30) { | |
824 | 762 | badAngles.add(cm); |
825 | 763 | hasBadAngles = true; |
826 | // badAngles.addAll(cm.getAlternativePositions()); | |
827 | 764 | } |
828 | 765 | } |
829 | 766 | } |
844 | 781 | return false; |
845 | 782 | if (mode == MODE_LINES && p.isEndOfWay()) |
846 | 783 | return false; |
847 | if (p instanceof CoordPOI){ | |
848 | if (((CoordPOI) p).isUsed()){ | |
784 | if (p instanceof CoordPOI && ((CoordPOI) p).isUsed()){ | |
849 | 785 | 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(); | |
856 | 788 | } |
857 | 789 | |
858 | 790 | /** |
859 | 791 | * helper class |
860 | 792 | */ |
861 | 793 | private class CenterOfAngle { |
862 | public boolean forceChange; | |
794 | boolean forceChange; | |
863 | 795 | final Coord center; |
864 | 796 | final List<CenterOfAngle> neighbours; |
865 | 797 | final int id; // debugging aid |
869 | 801 | |
870 | 802 | public CenterOfAngle(Coord center, int id) { |
871 | 803 | this.center = center; |
872 | assert center.isReplaced() == false; | |
804 | assert !center.isReplaced(); | |
873 | 805 | this.id = id; |
874 | 806 | neighbours = new ArrayList<>(); |
875 | 807 | } |
876 | 808 | |
877 | 809 | @Override |
878 | 810 | 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() + "]"; | |
880 | 813 | } |
881 | 814 | |
882 | 815 | /** |
884 | 817 | * @param replacements |
885 | 818 | * @return |
886 | 819 | */ |
887 | public Coord getCurrentLocation(Map<Coord, Coord> replacements){ | |
820 | public Coord getCurrentLocation(Map<Coord, Coord> replacements) { | |
888 | 821 | Coord c = getReplacement(center, null, replacements); |
889 | 822 | if (c.isToRemove()) |
890 | 823 | return null; |
902 | 835 | } |
903 | 836 | |
904 | 837 | 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"); | |
907 | 840 | } |
908 | 841 | boolean isNew = true; |
909 | 842 | // we want only different Coord instances here |
925 | 858 | public boolean isOK(Map<Coord, Coord> replacements) { |
926 | 859 | if (forceChange) |
927 | 860 | return false; |
928 | Coord c = getCurrentLocation (replacements); | |
861 | Coord c = getCurrentLocation(replacements); | |
929 | 862 | if (c == null) |
930 | 863 | return true; // removed center: nothing to do |
931 | 864 | for (CenterOfAngle neighbour : neighbours) { |
939 | 872 | return true; |
940 | 873 | } |
941 | 874 | |
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 | ||
942 | 915 | /** |
943 | 916 | * Try whether a move or remove or merge of this centre |
944 | 917 | * fixes bearing problems. |
947 | 920 | * @return true if something was changed |
948 | 921 | */ |
949 | 922 | public boolean tryChange(Map<Coord, Coord> replacements, boolean tryAlsoMerge) { |
950 | if (wasMerged ) { | |
923 | if (wasMerged) { | |
951 | 924 | return false; |
952 | 925 | } |
953 | 926 | Coord currentCenter = getCurrentLocation(replacements); |
966 | 939 | if (!dupCheck.add(n)) { |
967 | 940 | hasDups = true; |
968 | 941 | } |
969 | if (currentCenter.highPrecEquals(n)){ | |
970 | if (currentCenter == n){ | |
942 | if (currentCenter.highPrecEquals(n)) { | |
943 | if (currentCenter == n) { | |
971 | 944 | log.error(id + ": bad neighbour " + neighbour.id + " zero distance"); |
972 | 945 | } |
973 | if (badMergeCandidates != null && badMergeCandidates.contains(neighbour ) | |
946 | if (badMergeCandidates != null && badMergeCandidates.contains(neighbour) | |
974 | 947 | || neighbour.badMergeCandidates != null && neighbour.badMergeCandidates.contains(this)) { |
975 | //not allowed to merge | |
948 | // not allowed to merge | |
976 | 949 | } else { |
977 | 950 | replaceCoord(currentCenter, n, replacements); |
978 | 951 | neighbour.wasMerged = wasMerged = true; |
991 | 964 | if (initialMaxError < MAX_BEARING_ERROR) |
992 | 965 | return false; |
993 | 966 | double removeErr = calcRemoveError(replacements); |
994 | if (removeErr == 0){ | |
967 | if (removeErr == 0) { | |
995 | 968 | currentCenter.setRemove(true); |
996 | 969 | return true; |
997 | 970 | } |
999 | 972 | if (extraPass && tryAlsoMerge && currentCenter.getHighwayCount() > 1) { |
1000 | 973 | // spike |
1001 | 974 | List<Coord> altPositions = currentCenter.getAlternativePositions(); |
1002 | for (Coord altCenter : altPositions){ | |
975 | for (Coord altCenter : altPositions) { | |
1003 | 976 | 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()); | |
1005 | 979 | replaceCoord(currentCenter, altCenter, replacements); |
1006 | 980 | return true; |
1007 | 981 | } |
1018 | 992 | double bestReplErr = initialMaxError; |
1019 | 993 | Coord bestCenterReplacement = null; |
1020 | 994 | List<Coord> altPositions = currentCenter.getAlternativePositions(); |
1021 | for (Coord altCenter : altPositions){ | |
995 | for (Coord altCenter : altPositions) { | |
1022 | 996 | double err = calcBearingError(altCenter, worstNP); |
1023 | 997 | if (err >= bestReplErr) |
1024 | 998 | continue; |
1025 | 999 | // alt. position is improvement, check all neighbours |
1026 | 1000 | 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 | } | |
1031 | 1005 | } |
1032 | 1006 | Coord bestNeighbourReplacement = null; |
1033 | 1007 | // 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) { | |
1036 | 1010 | replaceCoord(currentCenter, altCenter, replacements); |
1037 | for (Coord altN: worstNP.getAlternativePositions()){ | |
1011 | for (Coord altN : worstNP.getAlternativePositions()) { | |
1038 | 1012 | double err = calcBearingError(altCenter, altN); |
1039 | 1013 | if (err >= bestReplErr) |
1040 | 1014 | continue; |
1041 | 1015 | 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 | } | |
1047 | 1021 | } |
1048 | 1022 | replacements.remove(currentCenter); |
1049 | 1023 | currentCenter.setReplaced(false); |
1050 | 1024 | } |
1051 | 1025 | } |
1052 | 1026 | 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) { | |
1054 | 1029 | specialChange = true; |
1055 | 1030 | } |
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) { | |
1058 | 1034 | bestCenterReplacement = null; |
1059 | 1035 | } |
1060 | if (bestCenterReplacement != null){ | |
1036 | if (bestCenterReplacement != null) { | |
1061 | 1037 | replaceCoord(currentCenter, bestCenterReplacement, replacements); |
1062 | 1038 | if (bestNeighbourReplacement != null) |
1063 | 1039 | replaceCoord(worstNP, bestNeighbourReplacement, replacements); |
1064 | 1040 | double modifiedSumErr = calcSumOfErrors(replacements); |
1065 | if (modifiedSumErr < initialSumErr){ | |
1041 | if (modifiedSumErr < initialSumErr) { | |
1066 | 1042 | if (specialChange) |
1067 | log.debug("pass",pass,"special repl",this); | |
1043 | log.debug("pass", pass, "special repl", this); | |
1068 | 1044 | return true; |
1069 | 1045 | } |
1070 | 1046 | // revert changes |
1072 | 1048 | currentCenter.setReplaced(false); |
1073 | 1049 | replacements.remove(worstNP); |
1074 | 1050 | worstNP.setReplaced(false); |
1075 | bestCenterReplacement = null; | |
1076 | } | |
1077 | } | |
1078 | if (removeErr < MAX_BEARING_ERROR){ | |
1051 | } | |
1052 | } | |
1053 | if (removeErr < MAX_BEARING_ERROR) { | |
1079 | 1054 | // createGPX(gpxPath+id+"_rem", replacements); |
1080 | 1055 | currentCenter.setRemove(true); |
1081 | 1056 | return true; |
1100 | 1075 | * @return true if merge is okay |
1101 | 1076 | */ |
1102 | 1077 | 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) | |
1104 | 1079 | || neighbour.badMergeCandidates != null && neighbour.badMergeCandidates.contains(this)) { |
1105 | 1080 | return false; // not allowed to merge |
1106 | 1081 | } |
1110 | 1085 | // 1) both points are via nodes |
1111 | 1086 | // 2) both nodes are boundary nodes with non-equal coords |
1112 | 1087 | // 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()))) | |
1122 | 1092 | return false; |
1123 | 1093 | Coord mergePoint; |
1124 | 1094 | |
1132 | 1102 | mergePoint = c.makeBetweenPoint(n, 0.5); |
1133 | 1103 | double err = 0; |
1134 | 1104 | |
1135 | if (c.equals(n) == false){ | |
1105 | if (!c.equals(n)) { | |
1136 | 1106 | 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 { | |
1140 | 1110 | if (err >= MAX_BEARING_ERROR) |
1141 | 1111 | 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) { | |
1143 | 1113 | return false; // improvement too small |
1144 | 1114 | } |
1145 | 1115 | } |
1153 | 1123 | } |
1154 | 1124 | } |
1155 | 1125 | 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++) { | |
1157 | 1127 | mergePoint.incHighwayCount(); |
1128 | } | |
1158 | 1129 | if (c != mergePoint) |
1159 | 1130 | replaceCoord(c, mergePoint, replacements); |
1160 | if (n != mergePoint){ | |
1131 | if (n != mergePoint) { | |
1161 | 1132 | replaceCoord(n, mergePoint, replacements); |
1162 | 1133 | } |
1163 | // createGPX(gpxPath+id+"_merged", replacements); | |
1134 | // createGPX(gpxPath+id+"_merged", replacements); | |
1164 | 1135 | // neighbour.createGPX(gpxPath+neighbour.id+"_merged_w_"+id, replacements); |
1165 | 1136 | neighbour.wasMerged = wasMerged = true; |
1166 | 1137 | return true; |
1174 | 1145 | * @param replacements s |
1175 | 1146 | * @return true if a nearly straight line exists |
1176 | 1147 | */ |
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) { | |
1179 | 1149 | Coord c = getCurrentLocation(replacements); |
1180 | 1150 | for (CenterOfAngle neighbour : neighbours) { |
1181 | if (neighbour == other) | |
1151 | if (neighbour == other) | |
1182 | 1152 | continue; |
1183 | 1153 | 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 | } | |
1194 | 1164 | } |
1195 | 1165 | return false; |
1196 | 1166 | } |
1205 | 1175 | private double calcMergeErr(CenterOfAngle other, Coord mergePoint, Map<Coord, Coord> replacements) { |
1206 | 1176 | double maxErr = 0; |
1207 | 1177 | for (CenterOfAngle neighbour : neighbours) { |
1208 | if (neighbour == other) | |
1178 | if (neighbour == other) | |
1209 | 1179 | continue; |
1210 | 1180 | Coord n = neighbour.getCurrentLocation(replacements); |
1211 | if (n != null){ | |
1181 | if (n != null) { | |
1212 | 1182 | double err = calcBearingError(mergePoint, n); |
1213 | 1183 | if (err > maxErr) |
1214 | 1184 | maxErr = err; |
1215 | 1185 | } |
1216 | 1186 | } |
1217 | 1187 | for (CenterOfAngle othersNeighbour : other.neighbours) { |
1218 | if (othersNeighbour == this) | |
1188 | if (othersNeighbour == this) | |
1219 | 1189 | continue; |
1220 | 1190 | Coord n = othersNeighbour.getCurrentLocation(replacements); |
1221 | if (n != null){ | |
1191 | if (n != null) { | |
1222 | 1192 | double err = calcBearingError(mergePoint, n); |
1223 | 1193 | if (err > maxErr) |
1224 | 1194 | maxErr = err; |
1234 | 1204 | * @param replacement see toRepl |
1235 | 1205 | * @return error [0..180] or Double.MAX_VALUE in case of equal points |
1236 | 1206 | */ |
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) { | |
1239 | 1208 | double maxErr = 0; |
1240 | 1209 | Coord c = getCurrentLocation(replacements); |
1241 | 1210 | for (CenterOfAngle neighbour : neighbours) { |
1247 | 1216 | err = calcBearingError(replacement, n); |
1248 | 1217 | else if (n == toRepl) |
1249 | 1218 | err = calcBearingError(c, replacement); |
1250 | else | |
1219 | else | |
1251 | 1220 | err = calcBearingError(c, n); |
1252 | 1221 | if (err == Double.MAX_VALUE) |
1253 | 1222 | return err; |
1263 | 1232 | * @return |
1264 | 1233 | */ |
1265 | 1234 | private double calcSumOfErrors(Map<Coord, Coord> replacements) { |
1266 | double SumErr = 0; | |
1235 | double sumErr = 0; | |
1267 | 1236 | Coord c = getCurrentLocation(replacements); |
1268 | 1237 | for (CenterOfAngle neighbour : neighbours) { |
1269 | 1238 | Coord n = neighbour.getCurrentLocation(replacements); |
1272 | 1241 | double err = calcBearingError(c, n); |
1273 | 1242 | if (err == Double.MAX_VALUE) |
1274 | 1243 | return err; |
1275 | SumErr += err; | |
1276 | } | |
1277 | return SumErr; | |
1244 | sumErr += err; | |
1245 | } | |
1246 | return sumErr; | |
1278 | 1247 | } |
1279 | 1248 | |
1280 | 1249 | /** |
1283 | 1252 | * @return Double.MAX_VALUE if centre must not be deleted, else [0..180] |
1284 | 1253 | */ |
1285 | 1254 | private double calcRemoveError(Map<Coord, Coord> replacements) { |
1286 | if (allowedToRemove(center) == false) | |
1255 | if (!allowedToRemove(center)) | |
1287 | 1256 | return Double.MAX_VALUE; |
1288 | 1257 | if (neighbours.size() > 2) |
1289 | 1258 | return Double.MAX_VALUE; |
1294 | 1263 | Coord n = neighbour.getCurrentLocation(replacements); |
1295 | 1264 | if (n == null) |
1296 | 1265 | 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()) { | |
1299 | 1269 | return 0; |
1300 | if (c.getDistToDisplayedPoint() >= n.getDistToDisplayedPoint()) { | |
1301 | return 0; | |
1302 | 1270 | } |
1303 | 1271 | return Double.MAX_VALUE; |
1304 | 1272 | } |
1305 | 1273 | outerPoints[i] = n; |
1306 | 1274 | } |
1307 | if (neighbours.size() < 2 ) | |
1275 | if (neighbours.size() < 2) | |
1308 | 1276 | return Double.MAX_VALUE; |
1309 | 1277 | |
1310 | 1278 | for (int i = 0; i < neighbours.size(); i++) { |
1317 | 1285 | if (Math.abs( dsplAngle ) < 3) |
1318 | 1286 | return Double.MAX_VALUE; |
1319 | 1287 | 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; | |
1322 | 1289 | } |
1323 | 1290 | |
1324 | 1291 | @SuppressWarnings("unused") |
1335 | 1302 | Coord nc = getReplacement(n.center, null, replacements); |
1336 | 1303 | if (nc == null) |
1337 | 1304 | 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 | } | |
1350 | 1315 | } |
1351 | 1316 | |
1352 | private void writeOSM(String name, List<ConvertedWay> convertedWays){ | |
1317 | private void writeOSM(String name, List<ConvertedWay> convertedWays) { | |
1353 | 1318 | if (DEBUG_PATH != null) { |
1354 | 1319 | //TODO: comment before release |
1355 | 1320 | // uk.me.parabola.mkgmap.osmstyle.optional.DebugWriter.writeOSM(bbox, DEBUG_PATH, name, convertedWays); |
1356 | 1321 | } |
1357 | 1322 | } |
1358 | 1323 | |
1359 | private static double calcBearingError(Coord p1, Coord p2){ | |
1324 | private static double calcBearingError(Coord p1, Coord p2) { | |
1360 | 1325 | if (p1.equals(p2) || p1.highPrecEquals(p2)) { |
1361 | 1326 | return Double.MAX_VALUE; |
1362 | 1327 | } |
1363 | 1328 | double realBearing = p1.bearingTo(p2); |
1364 | 1329 | double displayedBearing = p1.getDisplayedCoord().bearingTo(p2.getDisplayedCoord()); |
1365 | 1330 | double err = displayedBearing - realBearing; |
1366 | while(err > 180) | |
1331 | while (err > 180) | |
1367 | 1332 | err -= 360; |
1368 | while(err < -180) | |
1333 | while (err < -180) | |
1369 | 1334 | err += 360; |
1370 | 1335 | return Math.abs(err); |
1371 | 1336 | } |
1372 | ||
1373 | 1337 | |
1374 | 1338 | /** |
1375 | 1339 | * Calculate the rounding error tolerance for a given point. |
1376 | 1340 | * The latitude error may be max higher. Maybe this should be |
1377 | 1341 | * @param p0 |
1378 | * @return | |
1342 | * @return the rounding error tolerance for a given point | |
1379 | 1343 | */ |
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; | |
1384 | 1347 | } |
1385 | 1348 | |
1386 | 1349 | /** |
1397 | 1360 | int n = points.size(); |
1398 | 1361 | // scan through the way's points looking for points which are |
1399 | 1362 | // 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++) { | |
1401 | 1364 | 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); | |
1406 | 1369 | } |
1407 | 1370 | 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 | |
1412 | 1375 | } else { |
1413 | 1376 | continue; |
1414 | 1377 | } |
1415 | 1378 | } |
1416 | Coord c2 = points.get(i+1); | |
1379 | Coord c2 = points.get(i + 1); | |
1417 | 1380 | 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) { | |
1419 | 1382 | continue; |
1420 | 1383 | } |
1421 | 1384 | 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) { | |
1423 | 1386 | double distance = cm.distToLineSegment(c1, c2); |
1424 | 1387 | if (distance < maxErrorDistance) |
1425 | 1388 | continue; |
1426 | 1389 | } |
1427 | 1390 | modifiedPoints.add(cm); |
1428 | 1391 | } |
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)) | |
1430 | 1393 | modifiedPoints.add(modifiedPoints.get(0)); |
1431 | 1394 | return modifiedPoints; |
1432 | 1395 | } |
1438 | 1401 | * @param convertedWays list of ways to filter |
1439 | 1402 | * @param modifiedRoads if ways are routable we add the modified ways to this map |
1440 | 1403 | */ |
1441 | private void prepWithDouglasPeucker(List<ConvertedWay> convertedWays, HashMap<Long, ConvertedWay> modifiedRoads) { | |
1404 | private void prepWithDouglasPeucker(List<ConvertedWay> convertedWays, Map<Long, ConvertedWay> modifiedRoads) { | |
1442 | 1405 | // we don't want to remove end points of ways |
1443 | 1406 | markEndPoints(convertedWays); |
1444 | 1407 | double maxErrorDistance = 0.05; |
1445 | 1408 | Way lastWay = null; |
1446 | 1409 | for (ConvertedWay cw : convertedWays) { |
1447 | if (!cw.isValid() || cw.isOverlay()) | |
1410 | if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay)) | |
1448 | 1411 | continue; |
1449 | 1412 | Way way = cw.getWay(); |
1450 | if (way.equals(lastWay)) | |
1451 | continue; | |
1452 | 1413 | lastWay = way; |
1453 | 1414 | List<Coord> points = way.getPoints(); |
1454 | 1415 | List<Coord> coords = new ArrayList<>(points); |
1477 | 1438 | } |
1478 | 1439 | } |
1479 | 1440 | |
1480 | private void markEndPoints(List<ConvertedWay> convertedWays) { | |
1441 | private static void markEndPoints(List<ConvertedWay> convertedWays) { | |
1481 | 1442 | Way lastWay = null; |
1482 | 1443 | for (ConvertedWay cw : convertedWays) { |
1483 | if (!cw.isValid() || cw.isOverlay()) | |
1444 | if (!cw.isValid() || cw.isOverlay() || cw.getWay().equals(lastWay)) | |
1484 | 1445 | continue; |
1485 | 1446 | Way way = cw.getWay(); |
1486 | if (way.equals(lastWay)) | |
1487 | continue; | |
1488 | 1447 | lastWay = way; |
1489 | 1448 | way.getFirstPoint().setEndOfWay(true); |
1490 | 1449 | way.getLastPoint().setEndOfWay(true); |
168 | 168 | */ |
169 | 169 | public int setNumbers(List<HousenumberMatch> housenumbers, int startSegment, int endSegment, boolean left) { |
170 | 170 | int assignedNumbers = 0; |
171 | startInRoad = startSegment; | |
172 | endInRoad = endSegment; | |
171 | 173 | if (housenumbers.isEmpty() == false) { |
172 | 174 | RoadSide rs = new RoadSide(); |
173 | 175 | if (left) |
190 | 192 | if (maxN >= 0) { |
191 | 193 | assignedNumbers = maxN + 1; |
192 | 194 | 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(); | |
199 | 197 | } |
200 | 198 | } |
201 | 199 | 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 | } | |
202 | 209 | } |
203 | 210 | |
204 | 211 | /** |
332 | 339 | |
333 | 340 | /** |
334 | 341 | * 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 | |
336 | 343 | */ |
337 | 344 | public List<Numbers> getNumberList() { |
345 | List<Numbers> list = new ArrayList<>(); | |
338 | 346 | // 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; | |
348 | 349 | |
349 | List<Numbers> list = new ArrayList<>(); | |
350 | 350 | 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 | } | |
355 | 358 | if (log.isInfoEnabled()) { |
356 | if (headerWasReported == false){ | |
359 | if (headerWasReported == false) { | |
357 | 360 | MapRoad road = curr.getRoad(); |
358 | 361 | if (road.getStreet() == null && road.getName() == null) |
359 | 362 | log.info("final numbers for", road, curr.housenumberRoad.getName(), "in", road.getCity()); |
360 | else | |
363 | else | |
361 | 364 | log.info("final numbers for", road, "in", road.getCity()); |
362 | 365 | headerWasReported = true; |
363 | 366 | } |
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)); | |
367 | 371 | } |
368 | 372 | } |
369 | 373 | return list; |
370 | 374 | } |
371 | 375 | |
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 | ||
372 | 385 | public ExtNumbers checkSingleChainSegments(String streetName, boolean removeGaps) { |
373 | 386 | ExtNumbers curr = this; |
374 | 387 | ExtNumbers head = this; |
1111 | 1124 | * @param startPos |
1112 | 1125 | */ |
1113 | 1126 | private void increaseNodeIndexes(int startPos){ |
1114 | if (hasNumbers() == false) | |
1115 | return; | |
1116 | 1127 | if (startInRoad > startPos){ |
1117 | 1128 | startInRoad++; |
1118 | 1129 | endInRoad++; |
1869 | 1880 | return sortedByDistToLine; |
1870 | 1881 | } |
1871 | 1882 | |
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 | ||
1907 | 1883 | private static int countOccurence(List<HousenumberMatch> houses, int num){ |
1908 | 1884 | int count = 0; |
1909 | 1885 | for (HousenumberMatch house : houses){ |
462 | 462 | } |
463 | 463 | } |
464 | 464 | } |
465 | road.getPoints().get(0).setNumberNode(true); | |
466 | road.getPoints().get(road.getPoints().size() - 1).setNumberNode(true); | |
465 | 467 | firstRoadSameOSMWay = road; |
466 | 468 | 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()); | |
470 | 471 | } |
471 | 472 | } |
472 | 473 | } |
607 | 608 | |
608 | 609 | |
609 | 610 | /** |
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) { | |
615 | 615 | if (numbersEnabled) { |
616 | 616 | MultiHashMap<MapRoad,HousenumberMatch> initialHousesForRoads = findClosestRoadsToHouse(); |
617 | 617 | identifyServiceRoads(); |
26 | 26 | import java.util.Map.Entry; |
27 | 27 | |
28 | 28 | import uk.me.parabola.imgfmt.app.Coord; |
29 | import uk.me.parabola.imgfmt.app.CoordNode; | |
30 | 29 | import uk.me.parabola.log.Logger; |
31 | 30 | import uk.me.parabola.mkgmap.general.CityInfo; |
32 | 31 | import uk.me.parabola.mkgmap.general.MapRoad; |
111 | 110 | int prevNodePos = 0; |
112 | 111 | extNumbersHead = null; |
113 | 112 | ExtNumbers currNumbers = null; |
113 | assert road.getPoints().get(0).isNumberNode(); | |
114 | 114 | for (Coord p : road.getPoints()) { |
115 | if (currNodePos == 0) { | |
116 | if (road.skipAddToNOD() == false) | |
117 | assert p instanceof CoordNode; | |
118 | } | |
119 | ||
120 | 115 | // An ordinary point in the road. |
121 | 116 | if (p.isNumberNode() == false) { |
122 | 117 | currNodePos++; |
15 | 15 | */ |
16 | 16 | package uk.me.parabola.mkgmap.reader; |
17 | 17 | |
18 | import java.util.ArrayList; | |
19 | 18 | import java.util.List; |
20 | 19 | |
21 | 20 | import uk.me.parabola.imgfmt.app.Area; |
22 | import uk.me.parabola.imgfmt.app.Coord; | |
21 | import uk.me.parabola.imgfmt.app.net.RoadNetwork; | |
23 | 22 | import uk.me.parabola.imgfmt.app.trergn.Overview; |
24 | 23 | import uk.me.parabola.mkgmap.general.MapDataSource; |
25 | 24 | import uk.me.parabola.mkgmap.general.MapDetails; |
26 | 25 | import uk.me.parabola.mkgmap.general.MapLine; |
27 | 26 | import uk.me.parabola.mkgmap.general.MapPoint; |
28 | 27 | import uk.me.parabola.mkgmap.general.MapShape; |
29 | import uk.me.parabola.imgfmt.app.net.RoadNetwork; | |
30 | 28 | import uk.me.parabola.util.Configurable; |
31 | 29 | import uk.me.parabola.util.EnhancedProperties; |
32 | 30 | |
113 | 111 | } |
114 | 112 | |
115 | 113 | 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())); | |
122 | 114 | MapLine boundary = new MapLine(); |
123 | 115 | boundary.setType(type); |
124 | 116 | if(name != null) |
125 | 117 | boundary.setName(name); |
126 | 118 | boundary.setMinResolution(0); // On all levels |
127 | boundary.setPoints(coords); | |
119 | boundary.setPoints(area.toCoords()); | |
128 | 120 | mapper.addLine(boundary); |
129 | 121 | } |
130 | 122 |
26 | 26 | */ |
27 | 27 | public class HGTConverter { |
28 | 28 | 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); | |
30 | 30 | private short[] noHeights = { HGTReader.UNDEF }; |
31 | 31 | private HGTReader[][] readers; |
32 | 32 | private final int minLat32; |
46 | 46 | private int statRdrNull; |
47 | 47 | private int statRdrRes; |
48 | 48 | |
49 | private InterpolationMethod interpolationMethod = InterpolationMethod.Bicubic; | |
49 | private InterpolationMethod interpolationMethod = InterpolationMethod.BICUBIC; | |
50 | 50 | |
51 | 51 | public enum InterpolationMethod { |
52 | 52 | /** faster, smoothing, less precise */ |
53 | Bilinear , | |
53 | BILINEAR, | |
54 | 54 | /** slower, higher precision */ |
55 | Bicubic, | |
55 | BICUBIC, | |
56 | 56 | /** bicubic for high resolution, else bilinear */ |
57 | Automatic | |
58 | }; | |
57 | AUTOMATIC | |
58 | } | |
59 | 59 | |
60 | 60 | /** |
61 | 61 | * Class to extract elevation information from SRTM files in hgt format. |
99 | 99 | } |
100 | 100 | } |
101 | 101 | res = maxRes; // we use the highest available res |
102 | return; | |
103 | 102 | } |
104 | 103 | |
105 | 104 | /** |
108 | 107 | */ |
109 | 108 | public void setInterpolationMethod(InterpolationMethod interpolationMethod) { |
110 | 109 | this.interpolationMethod = interpolationMethod; |
111 | useComplexInterpolation = (interpolationMethod != InterpolationMethod.Bilinear); | |
110 | useComplexInterpolation = (interpolationMethod != InterpolationMethod.BILINEAR); | |
112 | 111 | } |
113 | 112 | |
114 | 113 | /** |
126 | 125 | // no reader : ocean or missing file |
127 | 126 | return outsidePolygonHeight; |
128 | 127 | } |
129 | int res = rdr.getRes(); | |
128 | int detectedRes = rdr.getRes(); | |
130 | 129 | rdr.prepRead(); |
131 | if (res <= 0) | |
130 | if (detectedRes <= 0) | |
132 | 131 | return 0; // assumed to be an area in the ocean |
133 | 132 | lastRow = row; |
134 | 133 | |
135 | double scale = res * FACTOR; | |
134 | double scale = detectedRes * FACTOR; | |
136 | 135 | |
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; | |
139 | 138 | int xLeft = (int) x1; |
140 | 139 | int yBottom = (int) y1; |
141 | 140 | double qx = x1 - xLeft; |
182 | 181 | * can use HGTreaders near the current one |
183 | 182 | */ |
184 | 183 | private boolean fillArray(HGTReader rdr, int row, int col, int xLeft, int yBottom) { |
185 | int res = rdr.getRes(); | |
184 | int detectedRes = rdr.getRes(); | |
186 | 185 | int minX = 0; |
187 | 186 | int minY = 0; |
188 | 187 | int maxX = 3; |
195 | 194 | return false; |
196 | 195 | minX = 1; |
197 | 196 | inside = false; |
198 | } else if (xLeft == res - 1) { | |
197 | } else if (xLeft == detectedRes - 1) { | |
199 | 198 | if (col + 1 >= readers[0].length) |
200 | 199 | return false; |
201 | 200 | maxX = 2; |
206 | 205 | return false; |
207 | 206 | minY = 1; |
208 | 207 | inside = false; |
209 | } else if (yBottom == res - 1) { | |
208 | } else if (yBottom == detectedRes - 1) { | |
210 | 209 | if (row + 1 >= readers.length) |
211 | 210 | return false; |
212 | 211 | maxY = 2; |
228 | 227 | return true; |
229 | 228 | |
230 | 229 | // fill data from adjacent readers, down and up |
231 | if (xLeft > 0 && xLeft < res - 1) { | |
230 | if (xLeft > 0 && xLeft < detectedRes - 1) { | |
232 | 231 | if (yBottom == 0) { // bottom edge |
233 | HGTReader rdrBB = prepReader(res, row - 1, col); | |
232 | HGTReader rdrBB = prepReader(detectedRes, row - 1, col); | |
234 | 233 | if (rdrBB == null) |
235 | 234 | return false; |
236 | 235 | 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); | |
238 | 237 | if (h == HGTReader.UNDEF) |
239 | 238 | return false; |
240 | 239 | eleArray[x][0] = h; |
241 | 240 | } |
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); | |
244 | 243 | if (rdrTT == null) |
245 | 244 | return false; |
246 | 245 | for (int x = 0; x <= 3; x++) { |
253 | 252 | } |
254 | 253 | |
255 | 254 | // fill data from adjacent readers, left and right |
256 | if (yBottom > 0 && yBottom < res - 1) { | |
255 | if (yBottom > 0 && yBottom < detectedRes - 1) { | |
257 | 256 | if (xLeft == 0) { // left edgge |
258 | HGTReader rdrLL = prepReader(res, row, col - 1); | |
257 | HGTReader rdrLL = prepReader(detectedRes, row, col - 1); | |
259 | 258 | if (rdrLL == null) |
260 | 259 | return false; |
261 | 260 | 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); | |
263 | 262 | if (h == HGTReader.UNDEF) |
264 | 263 | return false; |
265 | 264 | eleArray[0][y] = h; |
266 | 265 | } |
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); | |
269 | 268 | if (rdrRR == null) |
270 | 269 | return false; |
271 | 270 | for (int y = 0; y <= 3; y++) { |
280 | 279 | // fill data from adjacent readers, corners |
281 | 280 | if (xLeft == 0) { |
282 | 281 | if (yBottom == 0) { // left bottom corner |
283 | HGTReader rdrLL = prepReader(res, row, col - 1); | |
282 | HGTReader rdrLL = prepReader(detectedRes, row, col - 1); | |
284 | 283 | if (rdrLL == null) |
285 | 284 | return false; |
286 | 285 | 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); | |
288 | 287 | if (h == HGTReader.UNDEF) |
289 | 288 | return false; |
290 | 289 | eleArray[0][y] = h; |
291 | 290 | } |
292 | 291 | |
293 | HGTReader rdrBB = prepReader(res, row - 1, col); | |
292 | HGTReader rdrBB = prepReader(detectedRes, row - 1, col); | |
294 | 293 | if (rdrBB == null) |
295 | 294 | return false; |
296 | 295 | 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); | |
298 | 297 | if (h == HGTReader.UNDEF) |
299 | 298 | return false; |
300 | 299 | eleArray[x][0] = h; |
301 | 300 | } |
302 | 301 | |
303 | HGTReader rdrLB = prepReader(res, row - 1, col - 1); | |
302 | HGTReader rdrLB = prepReader(detectedRes, row - 1, col - 1); | |
304 | 303 | if (rdrLB == null) |
305 | 304 | return false; |
306 | h = rdrLB.ele(res - 1, res - 1); | |
305 | h = rdrLB.ele(detectedRes - 1, detectedRes - 1); | |
307 | 306 | if (h == HGTReader.UNDEF) |
308 | 307 | return false; |
309 | 308 | 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); | |
312 | 311 | if (rdrLL == null) |
313 | 312 | return false; |
314 | 313 | 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); | |
316 | 315 | if (h == HGTReader.UNDEF) |
317 | 316 | return false; |
318 | 317 | eleArray[0][y] = h; |
319 | 318 | } |
320 | 319 | |
321 | HGTReader rdrTT = prepReader(res, row + 1, col); | |
320 | HGTReader rdrTT = prepReader(detectedRes, row + 1, col); | |
322 | 321 | if (rdrTT == null) |
323 | 322 | return false; |
324 | 323 | for (int x = 1; x <= 3; x++) { |
328 | 327 | eleArray[x][3] = h; |
329 | 328 | } |
330 | 329 | |
331 | HGTReader rdrLT = prepReader(res, row + 1, col - 1); | |
330 | HGTReader rdrLT = prepReader(detectedRes, row + 1, col - 1); | |
332 | 331 | if (rdrLT == null) |
333 | 332 | return false; |
334 | h = rdrLT.ele(res - 1, 1); | |
333 | h = rdrLT.ele(detectedRes - 1, 1); | |
335 | 334 | if (h == HGTReader.UNDEF) |
336 | 335 | return false; |
337 | 336 | eleArray[0][3] = h; |
338 | 337 | } |
339 | } else if (xLeft == res - 1) { | |
338 | } else if (xLeft == detectedRes - 1) { | |
340 | 339 | if (yBottom == 0) { // right bottom corner |
341 | HGTReader rdrRR = prepReader(res, row, col + 1); | |
340 | HGTReader rdrRR = prepReader(detectedRes, row, col + 1); | |
342 | 341 | if (rdrRR == null) |
343 | 342 | return false; |
344 | 343 | for (int y = 1; y <= 3; y++) { |
348 | 347 | eleArray[3][y] = h; |
349 | 348 | } |
350 | 349 | |
351 | HGTReader rdrBB = prepReader(res, row - 1, col); | |
350 | HGTReader rdrBB = prepReader(detectedRes, row - 1, col); | |
352 | 351 | if (rdrBB == null) |
353 | 352 | return false; |
354 | 353 | 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); | |
356 | 355 | if (h == HGTReader.UNDEF) |
357 | 356 | return false; |
358 | 357 | eleArray[x][0] = h; |
359 | 358 | } |
360 | 359 | |
361 | HGTReader rdrRB = prepReader(res, row - 1, col + 1); | |
360 | HGTReader rdrRB = prepReader(detectedRes, row - 1, col + 1); | |
362 | 361 | if (rdrRB == null) |
363 | 362 | return false; |
364 | h = rdrRB.ele(1, res - 1); | |
363 | h = rdrRB.ele(1, detectedRes - 1); | |
365 | 364 | if (h == HGTReader.UNDEF) |
366 | 365 | return false; |
367 | 366 | 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); | |
370 | 369 | if (rdrRR == null) |
371 | 370 | return false; |
372 | 371 | for (int y = 0; y <= 2; y++) { |
376 | 375 | eleArray[3][y] = h; |
377 | 376 | } |
378 | 377 | |
379 | HGTReader rdrTT = prepReader(res, row + 1, col); | |
378 | HGTReader rdrTT = prepReader(detectedRes, row + 1, col); | |
380 | 379 | if (rdrTT == null) |
381 | 380 | return false; |
382 | 381 | for (int x = 0; x <= 2; x++) { |
386 | 385 | eleArray[x][3] = h; |
387 | 386 | } |
388 | 387 | |
389 | HGTReader rdrRT = prepReader(res, row + 1, col + 1); | |
388 | HGTReader rdrRT = prepReader(detectedRes, row + 1, col + 1); | |
390 | 389 | if (rdrRT == null) |
391 | 390 | return false; |
392 | 391 | h = rdrRT.ele(1, 1); |
467 | 466 | return (short) Math.round((1.0D - qx) * hlt + qx * hrt); |
468 | 467 | if (hrt != HGTReader.UNDEF && hrb != HGTReader.UNDEF && qx > 0.5D) //right edge |
469 | 468 | 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 | |
471 | 469 | // nearest value |
472 | 470 | return (short)((qx < 0.5D)? ((qy < 0.5D)? hlb: hlt): ((qy < 0.5D)? hrb: hrt)); |
473 | 471 | } |
480 | 478 | return (short) Math.round((1.0D - qx) * hlb + qx * hrb); |
481 | 479 | if (hlb != HGTReader.UNDEF && hlt != HGTReader.UNDEF && qx < 0.5D) //left edge |
482 | 480 | 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 | |
484 | 481 | // nearest value |
485 | 482 | return (short) ((qx < 0.5D) ? ((qy < 0.5D) ? hlb : hlt) : ((qy < 0.5D) ? hrb : hrt)); |
486 | 483 | } |
493 | 490 | return (short) Math.round((1.0D - qx) * hlt + qx * hrt); |
494 | 491 | if (hlt != HGTReader.UNDEF && hlb != HGTReader.UNDEF && qx < 0.5D) //left edge |
495 | 492 | 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 | |
497 | 493 | // nearest value |
498 | 494 | return (short) ((qx < 0.5D) ? ((qy < 0.5D) ? hlb : hlt) : ((qy < 0.5D) ? hrb : hrt)); |
499 | 495 | } |
506 | 502 | return (short) Math.round((1.0D - qx) * hlb + qx * hrb); |
507 | 503 | if (hrb != HGTReader.UNDEF && hrt != HGTReader.UNDEF && qx > 0.5D) //right edge |
508 | 504 | 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 | |
510 | 505 | // nearest value |
511 | 506 | return (short) ((qx < 0.5D) ? ((qy < 0.5D) ? hlb : hlt) : ((qy < 0.5D) ? hrb : hrt)); |
512 | 507 | } |
520 | 515 | double hxb = (1.0D - qx)*hlb + qx*hrb; |
521 | 516 | return (short) Math.round((1.0D - qy) * hxb + qy * hxt); |
522 | 517 | } |
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 | // } | |
532 | 518 | |
533 | 519 | public int getHighestRes() { |
534 | 520 | return res; |
567 | 553 | clearStat(); |
568 | 554 | pointsDistanceLat = pointDist; |
569 | 555 | pointsDistanceLon = pointDist; |
570 | if (InterpolationMethod.Automatic.equals(interpolationMethod)) { | |
556 | if (InterpolationMethod.AUTOMATIC.equals(interpolationMethod)) { | |
571 | 557 | if (res > 0) { |
572 | 558 | int distHGTx3 = (1 << 29)/(45 / 3 * res); // 3 * HGT points distance in DEM units |
573 | 559 | if (distHGTx3 + 20 > pointDist) { // account for rounding of pointDist and distHGTx3 |
602 | 588 | * a an array with one value for each point, in the order top -> down and left -> right. |
603 | 589 | */ |
604 | 590 | public short[] getHeights(int lat32, int lon32, int height, int width) { |
605 | short[] realHeights = noHeights; | |
606 | ||
607 | 591 | java.awt.geom.Area testArea = null; |
608 | 592 | if (demArea != null) { |
609 | 593 | // we have a bounding polygon |
620 | 604 | } |
621 | 605 | } |
622 | 606 | |
623 | realHeights = new short[width * height]; | |
607 | short[] realHeights = new short[width * height]; | |
624 | 608 | int count = 0; |
625 | 609 | int py = lat32; |
626 | 610 | for (int y = 0; y < height; y++) { |
12 | 12 | package uk.me.parabola.mkgmap.reader.hgt; |
13 | 13 | |
14 | 14 | import java.io.BufferedReader; |
15 | import java.io.FileNotFoundException; | |
16 | 15 | import java.io.FileReader; |
17 | 16 | import java.io.IOException; |
18 | 17 | import java.io.InputStream; |
20 | 19 | import java.util.BitSet; |
21 | 20 | import java.util.regex.Matcher; |
22 | 21 | import java.util.regex.Pattern; |
22 | ||
23 | 23 | import uk.me.parabola.log.Logger; |
24 | 24 | |
25 | 25 | /** |
29 | 29 | public class HGTList { |
30 | 30 | private static final Logger log = Logger.getLogger(HGTList.class); |
31 | 31 | |
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; | |
34 | 34 | |
35 | private HGTList() | |
36 | { | |
35 | private HGTList() { | |
37 | 36 | try { |
38 | 37 | knownHgt = loadConfig(); |
39 | 38 | } catch (IOException e) { |
44 | 43 | public static HGTList get() { |
45 | 44 | return instance; |
46 | 45 | } |
46 | ||
47 | 47 | public BitSet getKnownHGT() { |
48 | 48 | return knownHgt; |
49 | 49 | } |
50 | 50 | |
51 | private BitSet loadConfig() throws IOException | |
52 | { | |
51 | private BitSet loadConfig() throws IOException { | |
53 | 52 | try { |
54 | 53 | String name = "hgt/known-hgt.txt"; |
55 | 54 | BitSet bs = compileHGTList(name); |
85 | 84 | * @param filename the path to the human readable file |
86 | 85 | * @return the {@link BitSet} |
87 | 86 | * @throws IOException |
88 | * @throws FileNotFoundException | |
89 | 87 | */ |
90 | private static BitSet compileHGTList(String filename) throws FileNotFoundException, IOException { | |
88 | private static BitSet compileHGTList(String filename) throws IOException { | |
91 | 89 | final Pattern hgtPattern = Pattern.compile("([sSnN])(\\d{2})([eEwW])(\\d{3}).*"); |
92 | 90 | try (BufferedReader br = new BufferedReader(new FileReader(filename))) { |
93 | 91 | BitSet bs = new BitSet(180*360); |
120 | 118 | } |
121 | 119 | } |
122 | 120 | |
123 | private static int getBitSetPos (int lat, int lon) { | |
121 | private static int getBitSetPos(int lat, int lon) { | |
124 | 122 | assert lat >= -90 && lat < 90 && lon >= -180 && lon < 180; |
125 | 123 | return (90 + lat) * 360 + lon + 180; |
126 | 124 | } |
127 | ||
125 | ||
128 | 126 | public synchronized boolean shouldExist(int lat, int lon) { |
129 | 127 | if (knownHgt != null) |
130 | 128 | return knownHgt.get(getBitSetPos(lat, lon)); |
131 | 129 | return false; |
132 | 130 | } |
133 | ||
131 | ||
134 | 132 | public static void main(String[] args) throws IOException { |
135 | 133 | if (args.length >= 2 && "compile".equals(args[0])) { |
136 | 134 | BitSet bs = compileHGTList(args[1]); |
137 | 135 | 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 | } | |
142 | 140 | } else { |
143 | 141 | System.out.println("usage: HGTList compile hgt-list [outfile name]"); |
144 | } | |
145 | ||
142 | } | |
146 | 143 | } |
147 | 144 | |
148 | 145 | } |
40 | 40 | public class HGTReader { |
41 | 41 | private static final Logger log = Logger.getLogger(HGTReader.class); |
42 | 42 | |
43 | public static final short UNDEF = Short.MIN_VALUE; | |
43 | 44 | private ByteBuffer buffer; |
44 | 45 | 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; | |
49 | 49 | private long count; |
50 | 50 | |
51 | 51 | |
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<>(); | |
54 | 54 | |
55 | 55 | /** |
56 | 56 | * Class to read a single HGT file. |
122 | 122 | res = -1; |
123 | 123 | path = null; |
124 | 124 | 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); | |
131 | 126 | } |
132 | 127 | HGTList hgtList = HGTList.get(); |
133 | 128 | if (hgtList != null) { |
134 | 129 | if (hgtList.shouldExist(lat, lon)) |
135 | 130 | System.err.println(this.getClass().getSimpleName() + ": file " + fileName + " not found but it should exist. Height values will be 0."); |
136 | return; | |
137 | 131 | } else { |
138 | 132 | log.warn("file " + fileName + " not found. Is expected to cover sea."); |
139 | 133 | } |
216 | 210 | * @return resolution (typically 1200 for 3'' or 3600 for 1'') |
217 | 211 | */ |
218 | 212 | 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) | |
221 | 215 | return (int) (numVals - 1); |
222 | 216 | log.error("file", fname, "has unexpected size", size, "and is ignored"); |
223 | 217 | return -1; |
24 | 24 | import java.util.Set; |
25 | 25 | import java.util.concurrent.atomic.AtomicBoolean; |
26 | 26 | |
27 | import uk.me.parabola.imgfmt.FormatException; | |
28 | 27 | import uk.me.parabola.imgfmt.app.Area; |
29 | 28 | import uk.me.parabola.imgfmt.app.Coord; |
30 | 29 | import uk.me.parabola.log.Logger; |
32 | 31 | |
33 | 32 | public final class CoastlineFileLoader { |
34 | 33 | |
35 | private static final Logger log = Logger | |
36 | .getLogger(CoastlineFileLoader.class); | |
34 | private static final Logger log = Logger.getLogger(CoastlineFileLoader.class); | |
37 | 35 | |
38 | 36 | private final Set<String> coastlineFiles; |
39 | private final Collection<CoastlineWay> coastlines = new ArrayList<CoastlineWay>(); | |
37 | private final Collection<CoastlineWay> coastlines = new ArrayList<>(); | |
40 | 38 | |
41 | 39 | private final AtomicBoolean coastlinesLoaded = new AtomicBoolean(false); |
42 | 40 | private final AtomicBoolean loadingStarted = new AtomicBoolean(false); |
44 | 42 | private final EnhancedProperties coastConfig; |
45 | 43 | |
46 | 44 | private CoastlineFileLoader() { |
47 | this.coastlineFiles = new HashSet<String>(); | |
45 | this.coastlineFiles = new HashSet<>(); | |
48 | 46 | this.coastConfig = new EnhancedProperties(); |
49 | 47 | } |
50 | 48 | |
68 | 66 | } |
69 | 67 | } |
70 | 68 | |
71 | private OsmMapDataSource loadFromFile(String name) | |
72 | throws FileNotFoundException, FormatException { | |
69 | private OsmMapDataSource loadFromFile(String name) throws FileNotFoundException { | |
73 | 70 | OsmMapDataSource src = new OsmCoastDataSource(); |
74 | 71 | src.config(getConfig()); |
75 | 72 | log.info("Started loading coastlines from", name); |
78 | 75 | return src; |
79 | 76 | } |
80 | 77 | |
81 | private Collection<Way> loadFile(String filename) | |
82 | throws FileNotFoundException { | |
78 | private Collection<Way> loadFile(String filename) throws FileNotFoundException { | |
83 | 79 | OsmMapDataSource src = loadFromFile(filename); |
84 | 80 | return src.getElementSaver().getWays().values(); |
85 | 81 | } |
95 | 91 | int nBefore = coastlines.size(); |
96 | 92 | |
97 | 93 | 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."); | |
100 | 95 | |
101 | 96 | ArrayList<Way> ways = SeaGenerator.joinWays(loadedCoastlines); |
102 | 97 | ListIterator<Way> wayIter = ways.listIterator(); |
104 | 99 | while (wayIter.hasNext()) { |
105 | 100 | Way way = wayIter.next(); |
106 | 101 | wayIter.remove(); |
107 | coastlines.add(new CoastlineWay(way.getId(), way | |
108 | .getPoints())); | |
102 | coastlines.add(new CoastlineWay(way.getId(), way.getPoints())); | |
109 | 103 | } |
110 | 104 | |
111 | log.info((coastlines.size() - nBefore), | |
112 | "coastlines loaded from", coastlineFile); | |
105 | log.info((coastlines.size() - nBefore), "coastlines loaded from", coastlineFile); | |
113 | 106 | } catch (FileNotFoundException exp) { |
114 | 107 | log.error("Coastline file " + coastlineFile + " not found."); |
115 | 108 | } catch (Exception exp) { |
121 | 114 | } |
122 | 115 | |
123 | 116 | public Collection<Way> getCoastlines(Area bbox) { |
124 | if (coastlinesLoaded.get() == false) { | |
117 | if (!coastlinesLoaded.get()) { | |
125 | 118 | synchronized (this) { |
126 | 119 | loadCoastlines(); |
127 | 120 | } |
128 | 121 | } |
129 | Collection<Way> ways = new ArrayList<Way>(); | |
122 | Collection<Way> ways = new ArrayList<>(); | |
130 | 123 | for (CoastlineWay w : coastlines) { |
131 | 124 | if (w.getBbox().intersects(bbox)) { |
132 | 125 | Way x = new Way(w.getOriginalId(), w.getPoints()); |
144 | 137 | public CoastlineWay(long id, List<Coord> points) { |
145 | 138 | super(id, points); |
146 | 139 | 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); | |
149 | 141 | } |
150 | 142 | |
151 | 143 | 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"); | |
154 | 145 | bbox = Area.getBBox(points); |
155 | 146 | } |
156 | 147 | |
169 | 160 | } |
170 | 161 | |
171 | 162 | @Override |
172 | public Map<String, String> getTagsWithPrefix(String prefix, | |
173 | boolean removePrefix) { | |
163 | public Map<String, String> getTagsWithPrefix(String prefix, boolean removePrefix) { | |
174 | 164 | if ("natural".startsWith(prefix)) { |
175 | 165 | if (removePrefix) { |
176 | return Collections.singletonMap( | |
177 | "natural".substring(prefix.length()), "coastline"); | |
166 | return Collections.singletonMap("natural".substring(prefix.length()), "coastline"); | |
178 | 167 | } else { |
179 | 168 | return Collections.singletonMap("natural", "coastline"); |
180 | 169 | } |
15 | 15 | package uk.me.parabola.mkgmap.reader.osm; |
16 | 16 | |
17 | 17 | import java.util.Collections; |
18 | import java.util.Iterator; | |
19 | 18 | import java.util.Map; |
20 | 19 | |
21 | 20 | import uk.me.parabola.imgfmt.app.Label; |
51 | 50 | * @param val Its value. |
52 | 51 | */ |
53 | 52 | 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; | |
70 | 63 | } |
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 | } | |
72 | 70 | } |
71 | addTag(key, val.intern()); | |
73 | 72 | } |
74 | 73 | |
75 | 74 | /** |
161 | 160 | * @return <code>true</code> if the tag value is a boolean tag with a "positive" value |
162 | 161 | */ |
163 | 162 | 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)); | |
172 | 165 | } |
173 | 166 | |
174 | 167 | /** |
198 | 191 | * @return <code>true</code> if the tag value is a boolean tag with a "negative" value |
199 | 192 | */ |
200 | 193 | 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)); | |
209 | 196 | } |
210 | 197 | |
211 | 198 | public long getId() { |
272 | 259 | } |
273 | 260 | |
274 | 261 | /** |
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 | /** | |
275 | 269 | * @return a Map iterator for the key + value pairs |
276 | 270 | */ |
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 | */ | |
291 | 271 | 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(); | |
299 | 273 | } |
300 | 274 | |
301 | 275 | protected String kind() { |
15 | 15 | import java.util.concurrent.atomic.AtomicLong; |
16 | 16 | |
17 | 17 | public class FakeIdGenerator { |
18 | ||
18 | ||
19 | private FakeIdGenerator () { | |
20 | // private constructor to hide the implicit public one | |
21 | } | |
19 | 22 | private static final long START_ID = 1L << 62; |
20 | 23 | |
21 | 24 | private static final AtomicLong fakeId = new AtomicLong(START_ID); |
38 | 41 | * @return a unique id |
39 | 42 | */ |
40 | 43 | 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(); | |
46 | 45 | } |
47 | 46 | |
48 | 47 | public static boolean isFakeId(long id) { |
20 | 20 | import uk.me.parabola.imgfmt.ExitException; |
21 | 21 | import uk.me.parabola.log.Logger; |
22 | 22 | import uk.me.parabola.mkgmap.general.LevelInfo; |
23 | import uk.me.parabola.mkgmap.general.MapPoint; | |
23 | 24 | |
24 | 25 | /** |
25 | 26 | * Holds the garmin type of an element and all the information that |
56 | 57 | private boolean propogateActionsOnContinue; |
57 | 58 | |
58 | 59 | public static boolean checkType(FeatureKind featureKind, int type) { |
59 | if (type >= 0x010000){ | |
60 | if (type >= 0x010000) { | |
60 | 61 | if ((type & 0xff) > 0x1f) |
61 | 62 | return false; |
62 | 63 | } else { |
63 | if (featureKind == FeatureKind.POLYLINE && type > 0x3f) | |
64 | if (featureKind == FeatureKind.POLYLINE && type > 0x3f | |
65 | || (featureKind == FeatureKind.POLYGON && (type > 0x7f || type == 0x4a))) { | |
64 | 66 | 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) | |
69 | 69 | return false; |
70 | int subtype = type & 0xff; | |
71 | if (subtype > 0x1f || MapPoint.isCityType(type) && subtype != 0) { | |
72 | return false; | |
73 | } | |
70 | 74 | } |
71 | 75 | } |
72 | 76 | return true; |
76 | 80 | this.featureKind = featureKind; |
77 | 81 | try { |
78 | 82 | int t = Integer.decode(type); |
79 | if (featureKind == FeatureKind.POLYGON){ | |
83 | if (featureKind == FeatureKind.POLYGON && t >= 0x100 && t < 0x10000 && (t & 0xff) == 0) { | |
80 | 84 | // allow 0xYY00 instead of 0xYY |
81 | if (t >= 0x100 && t < 0x10000 && (t & 0xff) == 0) | |
82 | t >>= 8; | |
85 | t >>= 8; | |
83 | 86 | } |
84 | 87 | this.type = t; |
85 | 88 | } catch (NumberFormatException e) { |
156 | 159 | fmt.format(" road_class=%d road_speed=%d", roadClass, roadSpeed); |
157 | 160 | |
158 | 161 | if (continueSearch) |
159 | fmt.format(" continue"); | |
162 | sb.append(" continue"); | |
160 | 163 | if (propogateActionsOnContinue) |
161 | fmt.format(" propagate"); | |
164 | sb.append(" propagate"); | |
162 | 165 | sb.append(']'); |
163 | 166 | String res = sb.toString(); |
164 | 167 | fmt.close(); |
29 | 29 | * |
30 | 30 | * Some of this would be much better done in a style file or by extending the style system. |
31 | 31 | */ |
32 | public class HighwayHooks extends OsmReadingHooksAdaptor { | |
32 | public class HighwayHooks implements OsmReadingHooks { | |
33 | 33 | private static final Logger log = Logger.getLogger(HighwayHooks.class); |
34 | 34 | |
35 | 35 | private final List<Way> motorways = new ArrayList<>(); |
41 | 41 | |
42 | 42 | private Node currentNodeInWay; |
43 | 43 | |
44 | @Override | |
44 | 45 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
45 | 46 | this.saver = saver; |
46 | 47 | if(props.getProperty("make-all-cycleways", false)) { |
73 | 74 | return usedTags; |
74 | 75 | } |
75 | 76 | |
77 | @Override | |
76 | 78 | public void onAddNode(Node node) { |
77 | 79 | String val = node.getTag("highway"); |
78 | 80 | if (val != null && (val.equals("motorway_junction") || val.equals("services"))) { |
81 | 83 | } |
82 | 84 | } |
83 | 85 | |
86 | @Override | |
84 | 87 | public void onCoordAddedToWay(Way way, long id, Coord co) { |
85 | 88 | if (!linkPOIsToWays) |
86 | 89 | return; |
130 | 133 | } |
131 | 134 | } |
132 | 135 | |
136 | @Override | |
133 | 137 | public void onAddWay(Way way) { |
134 | 138 | String highway = way.getTag("highway"); |
135 | 139 | if (highway != null || "ferry".equals(way.getTag("route"))) { |
136 | 140 | // if the way is a roundabout but isn't already |
137 | 141 | // 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"); | |
142 | 144 | } |
143 | 145 | |
144 | 146 | if (makeOppositeCycleways && !"cycleway".equals(highway)){ |
145 | 147 | String onewayTag = way.getTag("oneway"); |
146 | 148 | 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))) | |
148 | 150 | oneway = true; |
149 | 151 | if (oneway){ |
150 | 152 | String cycleway = way.getTag("cycleway"); |
151 | boolean addCycleWay = false; | |
152 | 153 | // 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"); | |
155 | 163 | } |
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"); | |
167 | 164 | } |
168 | 165 | } |
169 | 166 | } |
172 | 169 | motorways.add(way); |
173 | 170 | } |
174 | 171 | |
172 | @Override | |
175 | 173 | public void end() { |
176 | 174 | finishExits(); |
177 | 175 | exits.clear(); |
26 | 26 | * @author GerdP |
27 | 27 | * |
28 | 28 | */ |
29 | public class HousenumberHooks extends OsmReadingHooksAdaptor { | |
29 | public class HousenumberHooks implements OsmReadingHooks { | |
30 | 30 | private static final Logger log = Logger.getLogger(HousenumberHooks.class); |
31 | 31 | |
32 | 32 | private ElementSaver saver; |
33 | private Node currentNodeInWay; | |
34 | 33 | private final List<Node> nodes = new ArrayList<>(); |
35 | 34 | private boolean clearNodes; |
36 | 35 | |
42 | 41 | @Override |
43 | 42 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
44 | 43 | this.saver = saver; |
45 | if (props.getProperty("addr-interpolation", true) == false) | |
44 | if (!props.getProperty("addr-interpolation", true)) | |
46 | 45 | return false; |
47 | 46 | return (props.getProperty("housenumbers", false)); |
48 | 47 | } |
63 | 62 | nodes.clear(); |
64 | 63 | clearNodes = false; |
65 | 64 | } |
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) | |
70 | 67 | return; |
71 | 68 | // this node might be part of a way that has the addr:interpolation tag |
72 | 69 | nodes.add(currentNodeInWay); |
40 | 40 | * destination. |
41 | 41 | * @author WanMil |
42 | 42 | */ |
43 | public class LinkDestinationHook extends OsmReadingHooksAdaptor { | |
43 | public class LinkDestinationHook implements OsmReadingHooks { | |
44 | 44 | private static final Logger log = Logger.getLogger(LinkDestinationHook.class); |
45 | 45 | |
46 | 46 | private ElementSaver saver; |
47 | 47 | |
48 | 48 | /** 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<>(); | |
50 | 50 | /** 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( | |
54 | 54 | "motorway", "trunk", "primary", "secondary", "tertiary", |
55 | 55 | "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( | |
57 | 57 | "motorway_link", "trunk_link", "primary_link", "secondary_link", "tertiary_link")); |
58 | 58 | |
59 | 59 | |
63 | 63 | private NameFinder nameFinder; |
64 | 64 | |
65 | 65 | /** 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<>(); | |
67 | 67 | |
68 | 68 | private boolean processDestinations; |
69 | 69 | private boolean processExits; |
70 | 70 | |
71 | @Override | |
71 | 72 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
72 | 73 | this.saver = saver; |
73 | 74 | nameFinder = new NameFinder(props); |
115 | 116 | for (Coord c : points) { |
116 | 117 | Set<Way> ways = adjacentWays.get(c); |
117 | 118 | if (ways == null) { |
118 | ways = new HashSet<Way>(4); | |
119 | ways = new HashSet<>(4); | |
119 | 120 | adjacentWays.put(c, ways); |
120 | 121 | } |
121 | 122 | ways.add(w); |
136 | 137 | destLanesTag = directedDestinationLanes; |
137 | 138 | destSourceTagKey += ":" + directionSuffix; |
138 | 139 | } |
139 | if (destLanesTag != null && destLanesTag.contains("|") == false) { | |
140 | if (destLanesTag != null && !destLanesTag.contains("|")) { | |
140 | 141 | // the destination:lanes tag contains no | => no lane specific information |
141 | 142 | // use this tag as destination tag |
142 | 143 | destinationTag = destLanesTag; |
153 | 154 | } |
154 | 155 | |
155 | 156 | } |
156 | if (destinationTag != null){ | |
157 | if (destinationTag != null) { | |
157 | 158 | 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()); | |
164 | 167 | } |
165 | 168 | } |
166 | 169 | destinationLinkWays.put(w.getId(), w); |
189 | 192 | for (Coord c : w.getPoints()) { |
190 | 193 | Set<Way> ways = wayNodes.get(c); |
191 | 194 | if (ways == null) { |
192 | ways = new HashSet<Way>(4); | |
195 | ways = new HashSet<>(4); | |
193 | 196 | wayNodes.put(c, ways); |
194 | 197 | } |
195 | 198 | ways.add(w); |
360 | 363 | } |
361 | 364 | |
362 | 365 | /** |
366 | * TODO: move to Coord class? | |
363 | 367 | * Retrieve a list of all Coords that are the direct neighbours of |
364 | 368 | * the given Coord. A neighbours latitude and longitude does not differ |
365 | 369 | * more than one Garmin unit from the given Coord. |
367 | 371 | * @return all neighbours of c |
368 | 372 | */ |
369 | 373 | 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++) { | |
372 | 376 | 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 | } | |
377 | 380 | } |
378 | 381 | } |
379 | 382 | return neighbours; |
387 | 390 | * motorway exit |
388 | 391 | */ |
389 | 392 | 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); | |
396 | 395 | } |
397 | 396 | |
398 | 397 | /** |
403 | 402 | * @return a list of all coords an the connection ways |
404 | 403 | */ |
405 | 404 | 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<>(); | |
407 | 406 | |
408 | 407 | Set<Way> connectedWays = wayNodes.get(node); |
409 | 408 | for (Way w : connectedWays) { |
452 | 451 | private void processWays() { |
453 | 452 | // remove the adjacent links from the destinationLinkWays list |
454 | 453 | // to avoid duplicate dest_hints |
455 | Queue<Way> linksWithDestination = new ArrayDeque<Way>(); | |
454 | Queue<Way> linksWithDestination = new ArrayDeque<>(); | |
456 | 455 | linksWithDestination.addAll(destinationLinkWays.values()); |
457 | 456 | log.debug(destinationLinkWays.size(),"links with destination tag"); |
458 | while (linksWithDestination.isEmpty()== false) { | |
457 | while (!linksWithDestination.isEmpty()) { | |
459 | 458 | Way linkWay = linksWithDestination.poll(); |
460 | 459 | String destination = linkWay.getTag("mkgmap:dest_hint_work"); |
461 | 460 | if (log.isDebugEnabled()) |
477 | 476 | |
478 | 477 | // remove the way from destination handling only if both ways are connected with start/end points |
479 | 478 | // 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) | |
482 | 481 | && connectedWay.getTag("highway").endsWith("_link") |
483 | 482 | && destination.equals(nextDest)) { |
484 | 483 | // do not use this way because there is another link before that with the same destination |
605 | 604 | } |
606 | 605 | if (exitNode.getTag("ref") != null) |
607 | 606 | 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")); | |
612 | 609 | } |
613 | 610 | if (nameFinder.getName(exitNode) != null){ |
614 | 611 | hintWay.addTag("mkgmap:exit_hint_name", nameFinder.getName(exitNode)); |
625 | 622 | |
626 | 623 | if (processDestinations) { |
627 | 624 | // use link ways only |
628 | while (destinationLinkWays.isEmpty() == false) { | |
625 | while (!destinationLinkWays.isEmpty()) { | |
629 | 626 | Way w = destinationLinkWays.values().iterator().next(); |
630 | 627 | destinationLinkWays.remove(w.getId()); |
631 | 628 | if (isNotOneway(w)) { |
715 | 712 | */ |
716 | 713 | private double getAngle(Coord cCenter, Coord c1, Coord c2) |
717 | 714 | { |
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); | |
728 | 725 | } |
729 | 726 | |
730 | 727 | /** |
739 | 736 | nameFinder = null; |
740 | 737 | } |
741 | 738 | |
739 | @Override | |
742 | 740 | public Set<String> getUsedTags() { |
743 | 741 | if (!(processDestinations || processExits)) |
744 | 742 | return Collections.emptySet(); |
746 | 744 | // to be able to copy the value to the destination tag |
747 | 745 | // Do not load destination because it makes sense only if the tag is |
748 | 746 | // referenced in the style file |
749 | Set<String> tags = new HashSet<String>(); | |
747 | Set<String> tags = new HashSet<>(); | |
750 | 748 | tags.add("highway"); |
751 | 749 | tags.add("destination"); |
752 | 750 | tags.add("destination:lanes"); |
762 | 760 | return tags; |
763 | 761 | } |
764 | 762 | |
763 | @Override | |
765 | 764 | public void end() { |
766 | 765 | log.info("LinkDestinationHook started"); |
767 | 766 | |
788 | 787 | // check if oneway is set implicitly by the highway type (motorway and motorway_link) |
789 | 788 | String onewayTag = w.getTag("oneway"); |
790 | 789 | 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")); | |
796 | 792 | } |
797 | 793 | |
798 | 794 | /** |
810 | 806 | * @return <code>true</code> way is not oneway |
811 | 807 | */ |
812 | 808 | 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)); | |
816 | 810 | } |
817 | 811 | |
818 | 812 | /** Private length function without caching */ |
819 | 813 | private LengthFunction length = new LengthFunction() { |
814 | @Override | |
820 | 815 | public boolean isCached() { |
821 | 816 | return false; |
822 | 817 | } |
24 | 24 | import uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryUtil; |
25 | 25 | import uk.me.parabola.util.EnhancedProperties; |
26 | 26 | |
27 | public class LocationHook extends OsmReadingHooksAdaptor { | |
27 | public class LocationHook implements OsmReadingHooks { | |
28 | 28 | private static final Logger log = Logger.getLogger(LocationHook.class); |
29 | 29 | // the resulting assignments are logged with this extra logger |
30 | 30 | // so that it is possible to log only the results of the location hook |
52 | 52 | |
53 | 53 | private EnhancedProperties props; |
54 | 54 | |
55 | @Override | |
55 | 56 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
56 | 57 | boundaryDirName = props.getProperty("bounds"); |
57 | 58 | |
69 | 70 | // checking of the boundary dir is expensive |
70 | 71 | // check once only and reuse the result |
71 | 72 | if (boundaryDirName.equals(checkedBoundaryDirName)) { |
72 | if (checkBoundaryDirOk == false) { | |
73 | if (!checkBoundaryDirOk) { | |
73 | 74 | log.error("Disable LocationHook because bounds directory is unusable. Dir: "+boundaryDirName); |
74 | 75 | return false; |
75 | 76 | } |
78 | 79 | checkBoundaryDirOk = false; |
79 | 80 | |
80 | 81 | List<String> boundaryFiles = BoundaryUtil.getBoundaryDirContent(boundaryDirName); |
81 | if (boundaryFiles == null || boundaryFiles.size() == 0) { | |
82 | if (boundaryFiles == null || boundaryFiles.isEmpty()) { | |
82 | 83 | log.error("LocationHook is disabled because no bounds files are available. Dir: " |
83 | 84 | + boundaryDirName); |
84 | 85 | return false; |
91 | 92 | return true; |
92 | 93 | } |
93 | 94 | |
95 | @Override | |
94 | 96 | public void end() { |
95 | 97 | long t1 = System.currentTimeMillis(); |
96 | 98 | log.info("Starting with location hook"); |
119 | 121 | private void processLocationRelevantElements() { |
120 | 122 | // process all nodes that might be converted to a garmin node (tagcount > 0) |
121 | 123 | 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)); | |
128 | 128 | } |
129 | 129 | } |
130 | 130 |
161 | 161 | |
162 | 162 | // the inner areas of the cut point have been processed |
163 | 163 | // they are no longer needed |
164 | ||
164 | 165 | for (Area cutArea : cutPoint.getAreas()) { |
165 | 166 | ListIterator<Area> areaIter = areaCutData.innerAreas.listIterator(); |
166 | 167 | while (areaIter.hasNext()) { |
171 | 172 | } |
172 | 173 | } |
173 | 174 | } |
174 | // remove all does not seem to work. It removes more than the identical areas. | |
175 | // areaCutData.innerAreas.removeAll(cutPoint.getAreas()); | |
176 | 175 | |
177 | 176 | if (areaCutData.outerArea.isSingular()) { |
178 | 177 | // the area is singular |
575 | 574 | if (minAspectRatio == null) { |
576 | 575 | // first get the left/upper cut |
577 | 576 | 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); | |
581 | 580 | |
582 | 581 | // second get the right/lower cut |
583 | 582 | 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); | |
587 | 586 | |
588 | 587 | // get the minimum |
589 | 588 | minAspectRatio = Math.min(ar1, ar2); |
590 | 589 | } |
591 | 590 | return minAspectRatio; |
592 | 591 | } |
592 | ||
593 | 593 | |
594 | 594 | public int compareTo(CutPoint o) { |
595 | 595 | if (this == o) { |
596 | 596 | return 0; |
597 | 597 | } |
598 | 598 | // 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; | |
612 | 605 | // handle the special case that a cut has no area |
613 | 606 | if (getNumberOfAreas() == 0) { |
614 | if (o.getNumberOfAreas() == 0) { | |
615 | return 0; | |
616 | } else { | |
617 | return -1; | |
618 | } | |
607 | return o.getNumberOfAreas() == 0 ? 0 : -1; | |
619 | 608 | } else if (o.getNumberOfAreas() == 0) { |
620 | 609 | return 1; |
621 | 610 | } |
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; | |
630 | 615 | double dAR = getMinAspectRatio() - o.getMinAspectRatio(); |
631 | 616 | if (dAR != 0) { |
632 | 617 | return (dAR > 0 ? 1 : -1); |
633 | 618 | } |
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; | |
642 | 622 | // 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(); | |
650 | 628 | |
651 | 629 | } |
652 | 630 | |
665 | 643 | } |
666 | 644 | } |
667 | 645 | |
668 | private static enum CoordinateAxis { | |
646 | private enum CoordinateAxis { | |
669 | 647 | LATITUDE(false), LONGITUDE(true); |
670 | 648 | |
671 | 649 | private CoordinateAxis(boolean useX) { |
16 | 16 | import uk.me.parabola.log.Logger; |
17 | 17 | import uk.me.parabola.util.EnhancedProperties; |
18 | 18 | |
19 | public class MultiPolygonFinishHook extends OsmReadingHooksAdaptor { | |
19 | public class MultiPolygonFinishHook implements OsmReadingHooks { | |
20 | 20 | private static final Logger log = Logger.getLogger(MultiPolygonFinishHook.class); |
21 | 21 | |
22 | 22 | private ElementSaver saver; |
23 | ||
24 | public MultiPolygonFinishHook() { | |
25 | } | |
26 | 23 | |
24 | @Override | |
27 | 25 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
28 | 26 | this.saver = saver; |
29 | 27 | return true; |
30 | 28 | } |
31 | 29 | |
30 | @Override | |
32 | 31 | public void end() { |
33 | 32 | long t1 = System.currentTimeMillis(); |
34 | 33 | log.info("Finishing multipolygons"); |
21 | 21 | import java.util.BitSet; |
22 | 22 | import java.util.Collection; |
23 | 23 | import java.util.Collections; |
24 | import java.util.Comparator; | |
25 | 24 | import java.util.HashMap; |
26 | 25 | import java.util.HashSet; |
27 | 26 | import java.util.IdentityHashMap; |
55 | 54 | public static final String STYLE_FILTER_LINE = "polyline"; |
56 | 55 | public static final String STYLE_FILTER_POLYGON = "polygon"; |
57 | 56 | |
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"); | |
61 | 61 | private final Map<Long, Way> tileWayMap; |
62 | 62 | private final Map<Long, String> roleMap = new HashMap<>(); |
63 | 63 | |
192 | 192 | joinable = true; |
193 | 193 | } else if (joinWay.getLastPoint() == tempWay.getFirstPoint()) { |
194 | 194 | insIdx = joinWay.getPoints().size(); |
195 | reverseTempWay = false; | |
196 | 195 | firstTmpIdx = 1; |
197 | 196 | joinable = true; |
198 | 197 | } else if (joinWay.getFirstPoint() == tempWay.getLastPoint()) { |
199 | 198 | insIdx = 0; |
200 | reverseTempWay = false; | |
201 | 199 | firstTmpIdx = 0; |
202 | 200 | joinable = true; |
203 | 201 | } else if (joinWay.getLastPoint() == tempWay.getLastPoint()) { |
237 | 235 | * @return a list of closed ways |
238 | 236 | */ |
239 | 237 | 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 | |
242 | 239 | |
243 | 240 | ArrayList<JoinedWay> joinedWays = new ArrayList<>(); |
244 | 241 | if (segments == null || segments.isEmpty()) { |
252 | 249 | JoinedWay jw = new JoinedWay(orgSegment); |
253 | 250 | roleMap.put(jw.getId(), getRole(orgSegment)); |
254 | 251 | if (orgSegment.isClosed()) { |
255 | if (orgSegment.isComplete() == false) { | |
252 | if (!orgSegment.isComplete()) { | |
256 | 253 | // the way is closed in planet but some points are missing in this tile |
257 | 254 | // we can close it artificially |
258 | 255 | if (log.isDebugEnabled()) |
298 | 295 | // if a role is not 'inner' or 'outer' then it is used as |
299 | 296 | // universal |
300 | 297 | // 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)) | |
305 | 300 | || (joinRole != null && joinRole.equals(tempRole))) { |
306 | 301 | // the roles are matching => try to join both ways |
307 | 302 | joined = joinWays(joinWay, tempWay, false); |
313 | 308 | // role |
314 | 309 | // or if the alternative way is shorter then check if |
315 | 310 | // 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; | |
324 | 316 | } |
325 | 317 | } |
326 | 318 | |
374 | 366 | * |
375 | 367 | * @param wayList |
376 | 368 | * a list of ways |
369 | * @param maxCloseDist max distance between ends for artificial close | |
370 | * | |
377 | 371 | */ |
378 | 372 | protected void closeWays(ArrayList<JoinedWay> wayList, double maxCloseDist) { |
379 | 373 | for (JoinedWay way : wayList) { |
383 | 377 | Coord p1 = way.getFirstPoint(); |
384 | 378 | Coord p2 = way.getLastPoint(); |
385 | 379 | |
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; | |
408 | 394 | } |
409 | 395 | |
410 | 396 | Line2D closingLine = new Line2D.Float(p1.getLongitude(), p1 |
415 | 401 | // don't use the first and the last point |
416 | 402 | // the closing line can intersect only in one point or complete. |
417 | 403 | // 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; | |
427 | 409 | } |
428 | 410 | lastPoint = thisPoint; |
429 | 411 | } |
459 | 441 | // sometimes the connection of both points cannot be done directly but with an intermediate point |
460 | 442 | public Coord imC; |
461 | 443 | public double distance; |
462 | ||
463 | 444 | public ConnectionData() { |
464 | 445 | |
465 | 446 | } |
469 | 450 | List<JoinedWay> unclosed = new ArrayList<>(); |
470 | 451 | |
471 | 452 | for (JoinedWay w : allWays) { |
472 | if (w.hasIdenticalEndPoints() == false) { | |
453 | if (!w.hasIdenticalEndPoints()) { | |
473 | 454 | unclosed.add(w); |
474 | 455 | } |
475 | 456 | } |
476 | 457 | // try to connect ways lying outside or on the bbox |
477 | 458 | 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"); | |
479 | 460 | Map<Coord, JoinedWay> outOfBboxPoints = new IdentityHashMap<>(); |
480 | 461 | |
481 | 462 | // check all ways for endpoints outside or on the bbox |
482 | 463 | for (JoinedWay w : unclosed) { |
483 | 464 | Coord c1 = w.getFirstPoint(); |
484 | 465 | 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"); | |
487 | 468 | outOfBboxPoints.put(c1, w); |
488 | 469 | } |
489 | 470 | |
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"); | |
492 | 473 | outOfBboxPoints.put(c2, w); |
493 | 474 | } |
494 | 475 | } |
495 | 476 | |
496 | 477 | 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."); | |
498 | 479 | return false; |
499 | 480 | } |
500 | 481 | |
508 | 489 | cd.w1 = outOfBboxPoints.get(cd.c1); |
509 | 490 | cd.w2 = outOfBboxPoints.get(cd.c2); |
510 | 491 | |
511 | if (lineCutsBbox(cd.c1, cd.c2 )) { | |
492 | if (lineCutsBbox(cd.c1, cd.c2)) { | |
512 | 493 | // Check if the way can be closed with one additional point |
513 | 494 | // outside the bounding box. |
514 | 495 | // The additional point is combination of the coords of both endpoints. |
516 | 497 | // not cut the bounding box. |
517 | 498 | // This can be removed when the splitter guarantees to provide logical complete |
518 | 499 | // 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)) { | |
526 | 504 | 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)) { | |
529 | 506 | cd.imC = edgePoint1; |
530 | 507 | } else { |
531 | 508 | // both endpoints are on opposite sides of the bounding box |
549 | 526 | (o1, o2) -> Double.compare(o1.distance, o2.distance)); |
550 | 527 | |
551 | 528 | 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); | |
553 | 530 | if (minCon.imC != null) |
554 | 531 | minCon.w1.getPoints().add(minCon.imC); |
555 | 532 | minCon.w1.closeWayArtificially(); |
575 | 552 | |
576 | 553 | |
577 | 554 | /** |
578 | * Removes all ways non closed ways from the given list ( | |
555 | * Removes all non closed ways from the given list. | |
579 | 556 | * <code>{@link Way#hasIdenticalEndPoints()} == false</code>) |
580 | 557 | * |
581 | 558 | * @param wayList |
632 | 609 | } |
633 | 610 | } |
634 | 611 | |
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; | |
640 | 615 | } |
641 | 616 | |
642 | 617 | if (remove) { |
719 | 694 | } |
720 | 695 | // sort by role and then by number of points, this improves performance |
721 | 696 | // in the routines which add the polygons to areas |
722 | if (polygonStatusList.size() > 2){ | |
697 | if (polygonStatusList.size() > 2) { | |
723 | 698 | polygonStatusList.sort((o1, o2) -> { |
724 | 699 | if (o1.outer != o2.outer) |
725 | 700 | return (o1.outer) ? -1 : 1; |
736 | 711 | protected List<Way> getSourceWays() { |
737 | 712 | ArrayList<Way> allWays = new ArrayList<>(); |
738 | 713 | |
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()); | |
743 | 719 | } 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(), | |
749 | 725 | "in multipolygon", toBrowseURL(), toTagString()); |
750 | 726 | } |
751 | 727 | } |
772 | 748 | List<Way> allWays = getSourceWays(); |
773 | 749 | |
774 | 750 | // 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."); | |
777 | 753 | return; |
778 | 754 | } |
779 | 755 | |
800 | 776 | if (polygons.isEmpty()) { |
801 | 777 | // do nothing |
802 | 778 | 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."); | |
808 | 782 | } |
809 | 783 | tagOuterWays(); |
810 | 784 | cleanup(); |
939 | 913 | } |
940 | 914 | } while (!holesOk); |
941 | 915 | |
942 | ArrayList<PolygonStatus> holes = getPolygonStatus(holeIndexes, | |
943 | (currentPolygon.outer ? "inner" : "outer")); | |
916 | ArrayList<PolygonStatus> holes = getPolygonStatus(holeIndexes, (currentPolygon.outer ? "inner" : "outer")); | |
944 | 917 | |
945 | 918 | // these polygons must all be checked for holes |
946 | 919 | polygonWorkingQueue.addAll(holes); |
967 | 940 | |
968 | 941 | // check if the polygon is an outer polygon or |
969 | 942 | // if there are some holes |
970 | boolean processPolygon = currentPolygon.outer | |
971 | || (holes.isEmpty()==false); | |
943 | boolean processPolygon = currentPolygon.outer || (!holes.isEmpty()); | |
972 | 944 | |
973 | 945 | if (processPolygon) { |
974 | 946 | List<Way> singularOuterPolygons; |
985 | 957 | singularOuterPolygons = cutter.cutOutInnerPolygons(currentPolygon.polygon, innerWays); |
986 | 958 | } |
987 | 959 | |
988 | if (singularOuterPolygons.isEmpty()==false) { | |
960 | if (!singularOuterPolygons.isEmpty()) { | |
989 | 961 | // handle the tagging |
990 | 962 | if (currentPolygon.outer && hasStyleRelevantTags(this)) { |
991 | 963 | // use the tags of the multipolygon |
1024 | 996 | // put the cut out polygons to the |
1025 | 997 | // final way map |
1026 | 998 | if (log.isDebugEnabled()) |
1027 | log.debug(mpWay.getId(),mpWay.toTagString()); | |
999 | log.debug(mpWay.getId(), mpWay.toTagString()); | |
1028 | 1000 | |
1029 | 1001 | mpWay.setFullArea(fullArea); |
1030 | 1002 | // mark this polygons so that only polygon style rules are applied |
1031 | 1003 | mpWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_POLYGON); |
1032 | mpWay.addTag(MP_CREATED_TAG, "true"); | |
1004 | mpWay.addTag(MP_CREATED_TAG_KEY, "true"); | |
1033 | 1005 | |
1034 | 1006 | if (currentPolygon.outer) { |
1035 | mpWay.addTag("mkgmap:mp_role", "outer"); | |
1007 | mpWay.addTag(MP_ROLE_TAG_KEY, "outer"); | |
1036 | 1008 | if (isAreaSizeCalculated()) |
1037 | 1009 | mpAreaSize += calcAreaSize(mpWay.getPoints()); |
1038 | 1010 | } else { |
1039 | mpWay.addTag("mkgmap:mp_role", "inner"); | |
1011 | mpWay.addTag(MP_ROLE_TAG_KEY, "inner"); | |
1040 | 1012 | } |
1041 | 1013 | |
1042 | 1014 | getMpPolygons().put(mpWay.getId(), mpWay); |
1045 | 1017 | } |
1046 | 1018 | } |
1047 | 1019 | |
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)) { | |
1050 | 1022 | log.warn("Multipolygon", toBrowseURL(), toTagString(), "contains errors."); |
1051 | 1023 | |
1052 | 1024 | BitSet outerUnusedPolys = new BitSet(); |
1079 | 1051 | } |
1080 | 1052 | } |
1081 | 1053 | |
1082 | if (hasStyleRelevantTags(this) == false) { | |
1054 | if (!hasStyleRelevantTags(this)) { | |
1083 | 1055 | // add tags to the multipolygon that are taken from the outer ways |
1084 | 1056 | // they may be required by some hooks (e.g. Area2POIHook) |
1085 | 1057 | for (Entry<String, String> tags : outerTags.entrySet()) { |
1099 | 1071 | Way lineTagWay = new Way(getOriginalId(), orgOuterWay.getPoints()); |
1100 | 1072 | lineTagWay.setFakeId(); |
1101 | 1073 | lineTagWay.addTag(STYLE_FILTER_TAG, STYLE_FILTER_LINE); |
1102 | lineTagWay.addTag(MP_CREATED_TAG, "true"); | |
1074 | lineTagWay.addTag(MP_CREATED_TAG_KEY, "true"); | |
1103 | 1075 | if (mpAreaSizeStr != null) { |
1104 | 1076 | // 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); | |
1106 | 1078 | } |
1107 | 1079 | for (Entry<String,String> tag : outerTags.entrySet()) { |
1108 | 1080 | lineTagWay.addTag(tag.getKey(), tag.getValue()); |
1132 | 1104 | if (isAreaSizeCalculated()) { |
1133 | 1105 | // assign the area size of the whole multipolygon to all outer polygons |
1134 | 1106 | String mpAreaSizeStr = String.format(Locale.US, "%.3f", mpAreaSize); |
1135 | addTag("mkgmap:cache_area_size", mpAreaSizeStr); | |
1107 | addTag(CACHE_AREA_SIZE_TAG_KEY, mpAreaSizeStr); | |
1136 | 1108 | 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); | |
1139 | 1111 | } |
1140 | 1112 | } |
1141 | 1113 | } |
1148 | 1120 | |
1149 | 1121 | if (largestOuterPolygon != null) { |
1150 | 1122 | // 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())) { | |
1153 | 1125 | // yes => use the label node as reference point |
1154 | cOfG = ((Node)r_e.getValue()).getLocation(); | |
1126 | cOfG = ((Node) entry.getValue()).getLocation(); | |
1155 | 1127 | break; |
1156 | } | |
1128 | } | |
1157 | 1129 | } |
1158 | 1130 | |
1159 | 1131 | if (cOfG == null) { |
1231 | 1203 | } |
1232 | 1204 | } |
1233 | 1205 | |
1234 | private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons, | |
1235 | BitSet innerPolygons) { | |
1206 | private void runWrongInnerPolygonCheck(BitSet unfinishedPolygons, BitSet innerPolygons) { | |
1236 | 1207 | // find all unfinished inner polygons that are not contained by any |
1237 | 1208 | BitSet wrongInnerPolygons = findOutmostPolygons(unfinishedPolygons, innerPolygons); |
1238 | 1209 | if (log.isDebugEnabled()) { |
1303 | 1274 | * @return <code>true</code> has style relevant tags |
1304 | 1275 | */ |
1305 | 1276 | 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; | |
1310 | 1279 | } |
1311 | 1280 | |
1312 | 1281 | for (Map.Entry<String, String> tagEntry : element.getTagEntryIterator()) { |
1313 | 1282 | String tagName = tagEntry.getKey(); |
1314 | 1283 | // all tags are style relevant |
1315 | 1284 | // 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:"); | |
1318 | 1287 | if (isStyleRelevant) { |
1319 | 1288 | return true; |
1320 | 1289 | } |
1430 | 1399 | int i = 0; |
1431 | 1400 | boolean noContained = true; |
1432 | 1401 | for (BitSet b : containsMatrix) { |
1433 | if (b.isEmpty()==false) { | |
1402 | if (!b.isEmpty()) { | |
1434 | 1403 | log.debug(i,"contains",b); |
1435 | 1404 | noContained = false; |
1436 | 1405 | } |
1511 | 1480 | allOnLine = false; |
1512 | 1481 | break; |
1513 | 1482 | } |
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; | |
1523 | 1487 | } |
1524 | 1488 | } |
1525 | 1489 | |
1543 | 1507 | // box => polygon1 may contain polygon2 |
1544 | 1508 | onePointContained = true; |
1545 | 1509 | 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; | |
1554 | 1514 | } |
1555 | 1515 | } |
1556 | 1516 | } |
1561 | 1521 | } |
1562 | 1522 | |
1563 | 1523 | Iterator<Coord> it1 = polygon1.getWay().getPoints().iterator(); |
1564 | Coord p1_1 = it1.next(); | |
1524 | Coord p11 = it1.next(); | |
1565 | 1525 | |
1566 | 1526 | 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)) { | |
1571 | 1531 | // don't check it - this segment of the outer polygon |
1572 | 1532 | // definitely does not intersect the way |
1573 | 1533 | continue; |
1574 | 1534 | } |
1575 | 1535 | |
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()); | |
1580 | 1540 | |
1581 | 1541 | // check all lines of way1 and way2 for intersections |
1582 | 1542 | Iterator<Coord> it2 = polygon2.getPoints().iterator(); |
1583 | Coord p2_1 = it2.next(); | |
1543 | Coord p21 = it2.next(); | |
1584 | 1544 | |
1585 | 1545 | // for speedup we divide the area around the second line into |
1586 | 1546 | // a 3x3 matrix with lon(-1,0,1) and lat(-1,0,1). |
1587 | 1547 | // -1 means below min lon/lat of bbox line p1_1-p1_2 |
1588 | 1548 | // 0 means inside the bounding box of the line p1_1-p1_2 |
1589 | 1549 | // 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; | |
1594 | 1552 | |
1595 | 1553 | int prevLonField = lonField; |
1596 | 1554 | int prevLatField = latField; |
1597 | 1555 | |
1598 | 1556 | while (it2.hasNext()) { |
1599 | Coord p2_2 = p2_1; | |
1600 | p2_1 = it2.next(); | |
1557 | Coord p22 = p21; | |
1558 | p21 = it2.next(); | |
1601 | 1559 | |
1602 | 1560 | int changes = 0; |
1603 | 1561 | // 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)) { | |
1606 | 1564 | 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)) { | |
1612 | 1569 | 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; | |
1615 | 1571 | } |
1616 | 1572 | |
1617 | 1573 | // an intersection is possible if |
1621 | 1577 | || (latField == 0 && lonField == 0) |
1622 | 1578 | || (prevLatField == 0 && prevLonField == 0); |
1623 | 1579 | |
1624 | boolean intersects = intersectionPossible | |
1625 | && linesCutEachOther(p1_1, p1_2, p2_1, p2_2); | |
1580 | boolean intersects = intersectionPossible && linesCutEachOther(p11, p12, p21, p22); | |
1626 | 1581 | |
1627 | 1582 | if (intersects) { |
1628 | 1583 | if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext()) |
1632 | 1587 | // closing segment causes the intersection |
1633 | 1588 | log.info("Polygon", polygon1, "may contain polygon", polygon2, |
1634 | 1589 | ". 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))) { | |
1639 | 1594 | // at least one point is outside the bounding box |
1640 | 1595 | // we ignore the intersection because the ways may not |
1641 | 1596 | // be complete |
1676 | 1631 | } |
1677 | 1632 | |
1678 | 1633 | 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())) { | |
1694 | 1639 | continue; |
1695 | 1640 | } |
1696 | 1641 | |
1710 | 1655 | return false; |
1711 | 1656 | } |
1712 | 1657 | |
1713 | private boolean lineCutsBbox(Coord p1_1, Coord p1_2) { | |
1658 | private boolean lineCutsBbox(Coord p1, Coord p2) { | |
1714 | 1659 | Coord nw = new Coord(tileBounds.getMaxLat(), tileBounds.getMinLong()); |
1715 | 1660 | Coord sw = new Coord(tileBounds.getMinLat(), tileBounds.getMinLong()); |
1716 | 1661 | Coord se = new Coord(tileBounds.getMinLat(), tileBounds.getMaxLong()); |
1717 | 1662 | 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); | |
1722 | 1667 | } |
1723 | 1668 | |
1724 | 1669 | /** |
1726 | 1671 | * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa. |
1727 | 1672 | * This is a form of intersection check where it is allowed that one line ends on the |
1728 | 1673 | * 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 | |
1733 | 1678 | * @return true if both lines intersect somewhere in the middle of each other |
1734 | 1679 | */ |
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(); | |
1741 | 1686 | |
1742 | 1687 | long denominator = ((height2 * width1) - (width2 * height1)); |
1743 | 1688 | if (denominator == 0) { |
1746 | 1691 | return false; |
1747 | 1692 | } |
1748 | 1693 | |
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; | |
1754 | 1698 | if (isx <= 0 || isx >= 1) { |
1755 | 1699 | return false; |
1756 | 1700 | } |
1757 | 1701 | |
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); | |
1765 | 1705 | } |
1766 | 1706 | |
1767 | 1707 | private List<JoinedWay> getWaysFromPolygonList(BitSet selection) { |
1808 | 1748 | * @param fakeWay a way composed by other ways with faked ids |
1809 | 1749 | */ |
1810 | 1750 | private void logFakeWayDetails(Level logLevel, JoinedWay fakeWay) { |
1811 | if (log.isLoggable(logLevel) == false) { | |
1751 | if (!log.isLoggable(logLevel)) { | |
1812 | 1752 | return; |
1813 | 1753 | } |
1814 | 1754 | |
1815 | 1755 | // only log if this is an artificial multipolygon |
1816 | if (FakeIdGenerator.isFakeId(getId()) == false) { | |
1756 | if (!FakeIdGenerator.isFakeId(getId())) { | |
1817 | 1757 | return; |
1818 | 1758 | } |
1819 | 1759 | |
1824 | 1764 | } |
1825 | 1765 | } |
1826 | 1766 | |
1827 | if (containsOrgFakeWay == false) { | |
1767 | if (!containsOrgFakeWay) { | |
1828 | 1768 | return; |
1829 | 1769 | } |
1830 | 1770 | |
1867 | 1807 | Way lineTagWay = new Way(getOriginalId(), orgOuterWay.getPoints()); |
1868 | 1808 | lineTagWay.setFakeId(); |
1869 | 1809 | 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()) { | |
1872 | 1812 | lineTagWay.addTag(tag.getKey(), tag.getValue()); |
1873 | 1813 | |
1874 | 1814 | // remove the tag from the original way if it has the same value |
1909 | 1849 | * @param tagvalue |
1910 | 1850 | * the value of the tag to be removed |
1911 | 1851 | */ |
1912 | private void removeTagInOrgWays(JoinedWay way, String tagname, | |
1913 | String tagvalue) { | |
1852 | private void removeTagInOrgWays(JoinedWay way, String tagname, String tagvalue) { | |
1914 | 1853 | for (Way w : way.getOriginalWays()) { |
1915 | 1854 | if (w instanceof JoinedWay) { |
1916 | 1855 | // remove the tags recursively |
1977 | 1916 | * @return the size of the area (unitless) |
1978 | 1917 | */ |
1979 | 1918 | 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)) { | |
1981 | 1920 | return 0; // line or not closed |
1982 | 1921 | } |
1983 | 1922 | long area = 0; |
1990 | 1929 | * (c1.getHighPrecLat() - c2.getHighPrecLat()); |
1991 | 1930 | } |
1992 | 1931 | // 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)); | |
1994 | 1933 | return Math.abs(areaSize); |
1995 | 1934 | } |
1996 | 1935 | |
2028 | 1967 | updateBounds(point); |
2029 | 1968 | } |
2030 | 1969 | |
1970 | @Override | |
2031 | 1971 | public void addPoint(Coord point) { |
2032 | 1972 | super.addPoint(point); |
2033 | 1973 | updateBounds(point); |
2035 | 1975 | |
2036 | 1976 | private void updateBounds(List<Coord> pointList) { |
2037 | 1977 | 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) { | |
2043 | 1983 | updateBounds(other.minLat,other.minLon); |
2044 | 1984 | updateBounds(other.maxLat,other.maxLon); |
2045 | 1985 | } |
2077 | 2017 | * bounding box; <code>false</code> else |
2078 | 2018 | */ |
2079 | 2019 | 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()); | |
2084 | 2024 | } |
2085 | 2025 | |
2086 | 2026 | public Rectangle getBounds() { |
2105 | 2045 | for (Way w : ((JoinedWay) way).getOriginalWays()) { |
2106 | 2046 | addWay(w); |
2107 | 2047 | } |
2108 | updateBounds((JoinedWay)way); | |
2048 | updateBounds((JoinedWay) way); | |
2109 | 2049 | } else { |
2110 | 2050 | if (log.isDebugEnabled()) { |
2111 | 2051 | log.debug("Joined", this.getId(), "with", way.getId()); |
2123 | 2063 | return closedArtificially; |
2124 | 2064 | } |
2125 | 2065 | |
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<>(); | |
2128 | 2068 | boolean first = true; |
2129 | 2069 | for (Way way : ways) { |
2130 | 2070 | if (first) { |
2138 | 2078 | ArrayList<String> tagsToRemove = null; |
2139 | 2079 | for (Map.Entry<String, String> tag : mergedTags.entrySet()) { |
2140 | 2080 | String wayTagValue = way.getTag(tag.getKey()); |
2141 | if (!tag.getValue().equals(wayTagValue)) { | |
2081 | if (wayTagValue != null && !tag.getValue().equals(wayTagValue)) { | |
2142 | 2082 | // 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<>(); | |
2148 | 2085 | } |
2086 | tagsToRemove.add(tag.getKey()); | |
2149 | 2087 | } |
2150 | 2088 | } |
2151 | if (tagsToRemove!=null) { | |
2089 | if (tagsToRemove != null) { | |
2152 | 2090 | for (String tag : tagsToRemove) { |
2153 | 2091 | mergedTags.remove(tag); |
2154 | 2092 | } |
2163 | 2101 | */ |
2164 | 2102 | public void mergeTagsFromOrgWays() { |
2165 | 2103 | 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"); | |
2167 | 2105 | } |
2168 | 2106 | removeAllTags(); |
2169 | 2107 | |
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()); | |
2173 | 2111 | } |
2174 | 2112 | } |
2175 | 2113 | |
2187 | 2125 | return MultiPolygonRelation.calcAreaSize(getPoints()); |
2188 | 2126 | } |
2189 | 2127 | |
2128 | @Override | |
2190 | 2129 | public String toString() { |
2191 | 2130 | StringBuilder sb = new StringBuilder(200); |
2192 | 2131 | sb.append(getId()); |
2222 | 2161 | } |
2223 | 2162 | |
2224 | 2163 | public String toString() { |
2225 | return polygon+"_"+outer; | |
2164 | return polygon + "_" + outer; | |
2226 | 2165 | } |
2227 | 2166 | } |
2228 | 2167 | } |
39 | 39 | return "NODE: " + getId() + " @ " + location.toDegreeString(); |
40 | 40 | } |
41 | 41 | |
42 | @Override | |
42 | 43 | public String kind() { |
43 | 44 | return "node"; |
44 | 45 | } |
45 | 46 | |
47 | @Override | |
46 | 48 | public Node copy() { |
47 | 49 | Node dup = new Node(getId(), location); |
48 | 50 | dup.copyIds(this); |
35 | 35 | import java.util.function.UnaryOperator; |
36 | 36 | |
37 | 37 | import uk.me.parabola.imgfmt.ExitException; |
38 | import uk.me.parabola.imgfmt.FormatException; | |
39 | 38 | import uk.me.parabola.imgfmt.Utils; |
40 | 39 | import uk.me.parabola.log.Logger; |
41 | 40 | import uk.me.parabola.mkgmap.Version; |
136 | 135 | // First try command line, then style, then our default. |
137 | 136 | String levelSpec = getConfig().getProperty(optionName); |
138 | 137 | 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); | |
144 | 141 | } |
145 | 142 | return levelSpec; |
146 | 143 | } |
147 | 144 | |
148 | 145 | @Override |
149 | public void load(String name, boolean addBackground) throws FileNotFoundException, FormatException { | |
146 | public void load(String name, boolean addBackground) throws FileNotFoundException { | |
150 | 147 | InputStream is = Utils.openFile(name); |
151 | 148 | parse(is, name); |
152 | 149 | elementSaver.finishLoading(); |
196 | 193 | catch (Exception e) { |
197 | 194 | throw new ExitException("Error reading copyright file " + copyrightFileName, e); |
198 | 195 | } |
199 | if ((copyrightArray.size() > 0) && copyrightArray.get(0).startsWith("\ufeff")) | |
196 | if (!copyrightArray.isEmpty() && copyrightArray.get(0).startsWith("\ufeff")) | |
200 | 197 | copyrightArray.set(0, copyrightArray.get(0).substring(1)); |
201 | 198 | UnaryOperator<String> replaceVariables = s -> s.replace("$MKGMAP_VERSION$", Version.VERSION) |
202 | 199 | .replace("$JAVA_VERSION$", System.getProperty("java.version")) |
270 | 267 | OsmReadingHooks hooks; |
271 | 268 | switch (plugins.size()) { |
272 | 269 | case 0: |
273 | hooks = new OsmReadingHooksAdaptor(); | |
270 | hooks = new NullHook(); | |
274 | 271 | break; |
275 | 272 | case 1: |
276 | 273 | hooks = plugins.get(0); |
285 | 282 | usedTags.addAll(hooks.getUsedTags()); |
286 | 283 | return hooks; |
287 | 284 | } |
285 | ||
286 | /** do nothing hook */ | |
287 | private class NullHook implements OsmReadingHooks {} | |
288 | 288 | |
289 | 289 | private static Map<String, Set<String>> readDeleteTagsFile(String fileName) { |
290 | 290 | Map<String, Set<String>> deletedTags = new HashMap<>(); |
331 | 331 | */ |
332 | 332 | protected void createConverter() { |
333 | 333 | EnhancedProperties props = getConfig(); |
334 | Style style = StyleImpl.readStyle(props); | |
335 | setStyle(style); | |
334 | setStyle(StyleImpl.readStyle(props)); | |
336 | 335 | |
337 | 336 | usedTags.addAll(style.getUsedTags()); |
338 | 337 | usedTags.addAll(NameFinder.getNameTags(props)); |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.reader.osm; |
13 | 13 | |
14 | import java.util.Collections; | |
14 | 15 | import java.util.Set; |
15 | 16 | |
16 | 17 | import uk.me.parabola.imgfmt.app.Coord; |
51 | 52 | * @return If you return false then this set of hooks will not be used. So if they |
52 | 53 | * are not needed based on the options supplied you can disable it. |
53 | 54 | */ |
54 | public boolean init(ElementSaver saver, EnhancedProperties props); | |
55 | public default boolean init(ElementSaver saver, EnhancedProperties props) { | |
56 | return true; | |
57 | } | |
55 | 58 | |
56 | 59 | /** |
57 | 60 | * Retrieves the tags that are used by this hook. Tags that are used only if they are referenced |
59 | 62 | * |
60 | 63 | * @return the tag names used by this hook |
61 | 64 | */ |
62 | public Set<String> getUsedTags(); | |
65 | public default Set<String> getUsedTags() { | |
66 | return Collections.emptySet(); | |
67 | } | |
63 | 68 | |
64 | 69 | /** |
65 | 70 | * Called on adding a node to the saver and just before it is added. You can modify |
67 | 72 | * |
68 | 73 | * @param node The node to be added. |
69 | 74 | */ |
70 | void onAddNode(Node node); | |
75 | default void onAddNode(Node node) {} | |
71 | 76 | |
72 | 77 | /** |
73 | 78 | * Add the given way. The way must be complete, call after the end tag |
75 | 80 | * |
76 | 81 | * @param way The osm way. |
77 | 82 | */ |
78 | public void onAddWay(Way way); | |
83 | public default void onAddWay(Way way) {} | |
79 | 84 | |
80 | 85 | /** |
81 | 86 | * This is called whenever a node is added to a way. A node is something with tags, not just a Coord. |
86 | 91 | * @param coordId The coordinate id of the node that is being added. |
87 | 92 | * @param co The coordinate. |
88 | 93 | */ |
89 | public void onCoordAddedToWay(Way way, long coordId, Coord co); | |
94 | public default void onCoordAddedToWay(Way way, long coordId, Coord co) {} | |
90 | 95 | |
91 | 96 | /** |
92 | 97 | * Called after the file has been read. Can be used to add more elements to the saver |
93 | 98 | * based on information stored up. |
94 | 99 | */ |
95 | public void end(); | |
100 | public default void end() {} | |
101 | ||
96 | 102 | } |
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 | } |
11 | 11 | */ |
12 | 12 | package uk.me.parabola.mkgmap.reader.osm; |
13 | 13 | |
14 | import java.util.ArrayList; | |
15 | 14 | import java.util.Arrays; |
16 | 15 | import java.util.HashSet; |
17 | import java.util.List; | |
18 | 16 | import java.util.Set; |
19 | 17 | |
20 | 18 | import uk.me.parabola.imgfmt.app.Coord; |
30 | 28 | */ |
31 | 29 | public class OsmReadingHooksChain implements OsmReadingHooks { |
32 | 30 | |
33 | private static final OsmReadingHooks[] NO_HOOKS = new OsmReadingHooks[0]; | |
34 | ||
35 | private OsmReadingHooks[] readingHooks = NO_HOOKS; | |
31 | private OsmReadingHooks[] readingHooks = {}; // no default hooks | |
36 | 32 | |
37 | 33 | /** |
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. | |
40 | 36 | */ |
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; | |
45 | 40 | } |
46 | 41 | |
42 | @Override | |
47 | 43 | public Set<String> getUsedTags() { |
48 | HashSet<String> usedTags = new HashSet<String>(); | |
44 | HashSet<String> usedTags = new HashSet<>(); | |
49 | 45 | for (int i = 0; i < readingHooks.length; i++) |
50 | 46 | usedTags.addAll(readingHooks[i].getUsedTags()); |
51 | 47 | return usedTags; |
52 | 48 | } |
53 | 49 | |
50 | @Override | |
54 | 51 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
55 | 52 | for (int i = 0; i < readingHooks.length; i++) |
56 | 53 | readingHooks[i].init(saver, props); |
57 | 54 | return true; |
58 | 55 | } |
59 | 56 | |
57 | @Override | |
60 | 58 | public void onAddNode(Node node) { |
61 | 59 | for (int i = 0; i < readingHooks.length; i++) |
62 | 60 | readingHooks[i].onAddNode(node); |
63 | 61 | } |
64 | 62 | |
63 | @Override | |
65 | 64 | public void onCoordAddedToWay(Way way, long coordId, Coord co) { |
66 | 65 | for (int i = 0; i < readingHooks.length; i++) |
67 | 66 | readingHooks[i].onCoordAddedToWay(way, coordId, co); |
68 | 67 | } |
69 | 68 | |
69 | @Override | |
70 | 70 | public void onAddWay(Way way) { |
71 | 71 | for (int i = 0; i < readingHooks.length; i++) |
72 | 72 | readingHooks[i].onAddWay(way); |
73 | 73 | } |
74 | 74 | |
75 | @Override | |
75 | 76 | public void end() { |
76 | 77 | for (int i = 0; i < readingHooks.length; i++) |
77 | 78 | readingHooks[i].end(); |
59 | 59 | * </ul> |
60 | 60 | * @author WanMil |
61 | 61 | */ |
62 | public class POIGeneratorHook extends OsmReadingHooksAdaptor { | |
62 | public class POIGeneratorHook implements OsmReadingHooks { | |
63 | 63 | private static final Logger log = Logger.getLogger(POIGeneratorHook.class); |
64 | 64 | |
65 | 65 | private List<Entry<String,String>> poiPlacementTags; |
208 | 208 | } |
209 | 209 | |
210 | 210 | // 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)) { | |
212 | 212 | if (log.isDebugEnabled()) |
213 | 213 | log.debug("MP processed: Do not create POI for", w.toTagString()); |
214 | 214 | continue; |
10 | 10 | * @author Rene_A |
11 | 11 | */ |
12 | 12 | 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<>(); | |
14 | 14 | // if set, one or more tags were ignored because they are not used in the style or in mkgmap |
15 | 15 | private boolean tagsIncomplete; |
16 | 16 | |
35 | 35 | return elements; |
36 | 36 | } |
37 | 37 | |
38 | @Override | |
38 | 39 | public String kind() { |
39 | 40 | return "relation"; |
40 | 41 | } |
19 | 19 | * This hook applies the relation rules of the style system. |
20 | 20 | * @author WanMil |
21 | 21 | */ |
22 | public class RelationStyleHook extends OsmReadingHooksAdaptor { | |
22 | public class RelationStyleHook implements OsmReadingHooks { | |
23 | 23 | |
24 | 24 | private Style style; |
25 | 25 | private ElementSaver saver; |
26 | 26 | private NameFinder nameFinder; |
27 | 27 | |
28 | public RelationStyleHook() { | |
29 | } | |
30 | ||
31 | 28 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
32 | 29 | this.saver = saver; |
33 | 30 | nameFinder = new NameFinder(props); |
34 | return super.init(saver, props); | |
31 | return true; | |
35 | 32 | } |
36 | 33 | |
37 | 34 | public void setStyle(Style style){ |
47 | 44 | ((RestrictionRelation) rel).eval(saver.getBoundingBox()); |
48 | 45 | } |
49 | 46 | } |
50 | super.end(); | |
51 | 47 | } |
52 | ||
53 | ||
54 | 48 | |
55 | 49 | } |
30 | 30 | * @author Gerd Petermann |
31 | 31 | * |
32 | 32 | */ |
33 | public class ResidentialHook extends OsmReadingHooksAdaptor { | |
33 | public class ResidentialHook implements OsmReadingHooks { | |
34 | 34 | private static final Logger log = Logger.getLogger(ResidentialHook.class); |
35 | 35 | |
36 | 36 | |
39 | 39 | private ElementSaver saver; |
40 | 40 | private NameFinder nameFinder; |
41 | 41 | |
42 | @Override | |
42 | 43 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
43 | if (props.getProperty("residential-hook", true) == false) | |
44 | if (!props.getProperty("residential-hook", true)) | |
44 | 45 | return false; |
45 | 46 | this.nameFinder = new NameFinder(props); |
46 | 47 | this.saver = saver; |
47 | 48 | return true; |
48 | 49 | } |
49 | 50 | |
51 | @Override | |
50 | 52 | public void end() { |
51 | 53 | log.info("Starting with residential hook"); |
52 | 54 | |
57 | 59 | |
58 | 60 | // process all nodes that might be converted to a garmin node (tagcount > 0) |
59 | 61 | 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); | |
64 | 64 | } |
65 | 65 | } |
66 | 66 |
25 | 25 | * @author WanMil |
26 | 26 | * |
27 | 27 | */ |
28 | public class RoutingHook extends OsmReadingHooksAdaptor { | |
28 | public class RoutingHook implements OsmReadingHooks { | |
29 | 29 | |
30 | 30 | private final Set<String> usedTags; |
31 | 31 | |
32 | 32 | public RoutingHook() { |
33 | usedTags = new HashSet<String>(); | |
33 | usedTags = new HashSet<>(); | |
34 | 34 | usedTags.add("except"); |
35 | 35 | usedTags.add("restriction"); |
36 | 36 | usedTags.add("restriction:foot"); |
73 | 73 | return props.containsKey("route"); |
74 | 74 | } |
75 | 75 | |
76 | ||
76 | @Override | |
77 | 77 | public Set<String> getUsedTags() { |
78 | 78 | return usedTags; |
79 | 79 | } |
80 | ||
81 | 80 | |
82 | 81 | } |
56 | 56 | * Should pick one that works well and make it the default. |
57 | 57 | * |
58 | 58 | */ |
59 | public class SeaGenerator extends OsmReadingHooksAdaptor { | |
59 | public class SeaGenerator implements OsmReadingHooks { | |
60 | 60 | private static final Logger log = Logger.getLogger(SeaGenerator.class); |
61 | 61 | |
62 | 62 | private boolean generateSeaUsingMP = true; |
70 | 70 | private void fillQuadTrees() { |
71 | 71 | final AtomicBoolean isLand = new AtomicBoolean(false); |
72 | 72 | 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); | |
83 | 80 | } |
84 | 81 | }; |
85 | 82 | for (Way way : getTileWayMap().values()) { |
24 | 24 | * |
25 | 25 | */ |
26 | 26 | public class TagDict{ |
27 | private final static TagDict INSTANCE = new TagDict(); | |
27 | private static final TagDict INSTANCE = new TagDict(); | |
28 | 28 | private final HashMap<String,Short> map = new HashMap<>(); |
29 | 29 | private final ArrayList<String> list = new ArrayList<>(); |
30 | 30 | |
62 | 62 | } |
63 | 63 | String s = keyString; |
64 | 64 | map.put(s, size); |
65 | //System.out.println(""+x + ":" + s); | |
66 | 65 | list.add(s); |
67 | 66 | return size; |
68 | 67 | } |
30 | 30 | * Use this if you don't want to save the results. Only likely to be |
31 | 31 | * used for the test cases. |
32 | 32 | */ |
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 | |
36 | 35 | }; |
37 | 36 | } |
29 | 29 | * |
30 | 30 | * @author WanMil |
31 | 31 | */ |
32 | public class UnusedElementsRemoverHook extends OsmReadingHooksAdaptor { | |
32 | public class UnusedElementsRemoverHook implements OsmReadingHooks { | |
33 | 33 | private static final Logger log = Logger.getLogger(UnusedElementsRemoverHook.class); |
34 | 34 | |
35 | 35 | private ElementSaver saver; |
37 | 37 | /** node with tags of this list must not be removed */ |
38 | 38 | private List<Entry<String,String>> areasToPoiNodeTags; |
39 | 39 | |
40 | public UnusedElementsRemoverHook() { | |
41 | } | |
42 | ||
40 | @Override | |
43 | 41 | public boolean init(ElementSaver saver, EnhancedProperties props) { |
44 | 42 | this.saver = saver; |
45 | 43 | |
54 | 52 | return true; |
55 | 53 | } |
56 | 54 | |
55 | @Override | |
57 | 56 | public void end() { |
58 | 57 | long t1 = System.currentTimeMillis(); |
59 | 58 | log.info("Removing unused elements"); |
72 | 71 | } |
73 | 72 | |
74 | 73 | // check if the node is within the tile bounding box |
75 | if (bbox.contains(node.getLocation()) == false) { | |
74 | if (!bbox.contains(node.getLocation())) { | |
76 | 75 | boolean removeNode = true; |
77 | 76 | |
78 | 77 | for (Entry<String, String> tag : areasToPoiNodeTags) { |
79 | 78 | // check if the node has a tag used by the POIGeneratorHook |
80 | 79 | 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; | |
89 | 86 | } |
90 | 87 | } |
91 | 88 | if (removeNode) { |
125 | 122 | if (bbox.contains(c)) { |
126 | 123 | coordInBbox = true; |
127 | 124 | 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); | |
138 | 132 | } |
133 | coordInBbox = true; | |
134 | break; | |
139 | 135 | } |
140 | 136 | |
141 | 137 | prevC = c; |
142 | 138 | } |
143 | if (coordInBbox==false) { | |
139 | if (!coordInBbox) { | |
144 | 140 | // no coord of the way is within the bounding box |
145 | 141 | // check if the way possibly covers the bounding box completely |
146 | 142 | Area wayBbox = Area.getBBox(way.getPoints()); |
233 | 233 | |
234 | 234 | if(points.size() < 3 || !points.get(0).equals(points.get(points.size() - 1))) |
235 | 235 | 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))) { | |
237 | 237 | log.error("Way.clockwise was called for way that is not closed in high precision"); |
238 | 238 | } |
239 | 239 |
20 | 20 | private String id; // the id of the OSM relation (was kept in tag "mkgmap:boundaryid") |
21 | 21 | |
22 | 22 | private final Tags tags; |
23 | private transient Area area; | |
23 | private Area area; | |
24 | 24 | |
25 | 25 | public Boundary(Area area, Tags tags, String id) { |
26 | 26 | this.area = new Area(area); |
71 | 71 | private final Node root; |
72 | 72 | // the bounding box of the quadtree |
73 | 73 | private final Rectangle bbox; |
74 | private final String bbox_key; | |
74 | private final String bboxKey; | |
75 | 75 | |
76 | 76 | // tags that can be returned in the get method |
77 | public final static String[] mkgmapTagsArray = { | |
77 | public static final String[] mkgmapTagsArray = { | |
78 | 78 | "mkgmap:admin_level1", |
79 | 79 | "mkgmap:admin_level2", |
80 | 80 | "mkgmap:admin_level3", |
90 | 90 | "mkgmap:other" // for use in residential hook |
91 | 91 | }; |
92 | 92 | // 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; | |
94 | 94 | // 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; | |
96 | 96 | |
97 | 97 | /** |
98 | 98 | * Create a quadtree with the data in an open stream. |
111 | 111 | this.bbox = new Rectangle(fileBbox.getMinLong(), fileBbox.getMinLat(), |
112 | 112 | fileBbox.getMaxLong() - fileBbox.getMinLong(), fileBbox.getMaxLat() |
113 | 113 | - 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); | |
115 | 115 | root = new Node(this.bbox); |
116 | 116 | |
117 | 117 | readStreamQuadTreeFormat(inpStream,searchBbox); |
132 | 132 | this.bbox = new Rectangle(givenBbox.getMinLong(), givenBbox.getMinLat(), |
133 | 133 | givenBbox.getMaxLong() - givenBbox.getMinLong(), |
134 | 134 | 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); | |
136 | 136 | |
137 | 137 | root = new Node(this.bbox); |
138 | 138 | // extract the location relevant tags |
139 | 139 | preparedLocationInfo = preparer.getPreparedLocationInfo(boundaries); |
140 | if (boundaries == null || boundaries.size() == 0) | |
140 | if (boundaries == null || boundaries.isEmpty()) | |
141 | 141 | return; |
142 | 142 | |
143 | 143 | |
210 | 210 | * @param other the other instance of BoundaryQuadTree |
211 | 211 | */ |
212 | 212 | public void merge(BoundaryQuadTree other){ |
213 | if (bbox.equals(other.bbox) == false){ | |
213 | if (!bbox.equals(other.bbox)){ | |
214 | 214 | log.error("Cannot merge tree with different bounding box"); |
215 | 215 | return; |
216 | 216 | } |
217 | 217 | for (Entry <String, BoundaryLocationInfo> entry : other.preparedLocationInfo.entrySet()){ |
218 | if (this.preparedLocationInfo.containsKey(entry.getKey()) == false){ | |
218 | if (!this.preparedLocationInfo.containsKey(entry.getKey())){ | |
219 | 219 | this.preparedLocationInfo.put(entry.getKey(),entry.getValue()); |
220 | 220 | } |
221 | 221 | } |
222 | 222 | // add the others tags |
223 | 223 | for (Entry <String, Tags> entry : other.boundaryTags.entrySet()){ |
224 | if (this.boundaryTags.containsKey(entry.getKey()) == false){ | |
224 | if (!this.boundaryTags.containsKey(entry.getKey())){ | |
225 | 225 | this.boundaryTags.put(entry.getKey(),entry.getValue()); |
226 | 226 | } |
227 | 227 | } |
362 | 362 | refs = null; |
363 | 363 | Area area = BoundaryUtil.readAreaAsPath(inpStream); |
364 | 364 | |
365 | if (area != null && area.isEmpty() == false) | |
365 | if (area != null && !area.isEmpty()) | |
366 | 366 | root.add(area, refs, id, treePath); |
367 | 367 | else { |
368 | 368 | log.warn(refs,id,treePath,"invalid or empty or too small area"); |
378 | 378 | } |
379 | 379 | } catch (EOFException exp) { |
380 | 380 | // it's always thrown at the end of the file |
381 | // log.error("Got EOF at the end of the file"); | |
382 | 381 | } |
383 | 382 | } |
384 | 383 | |
464 | 463 | * Sample: r1184826;6:r62579;4:r62372;2:r51477 |
465 | 464 | */ |
466 | 465 | private String getBoundaryNames(Coord co) { |
467 | if (this.bounds.contains(co) == false) | |
466 | if (!this.bounds.contains(co)) | |
468 | 467 | return null; |
469 | 468 | if (isLeaf){ |
470 | if (nodes == null || nodes.size() == 0) | |
469 | if (nodes == null || nodes.isEmpty()) | |
471 | 470 | return null; |
472 | 471 | int lon = co.getLongitude(); |
473 | 472 | 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; | |
482 | 478 | } |
483 | 479 | } |
484 | 480 | } |
499 | 495 | * The returned Tags must not be modified by the caller. |
500 | 496 | */ |
501 | 497 | private Tags get(Coord co/*, String treePath*/){ |
502 | if (this.bounds.contains(co) == false) | |
498 | if (!this.bounds.contains(co)) | |
503 | 499 | return null; |
504 | 500 | if (isLeaf){ |
505 | if (nodes == null || nodes.size() == 0) | |
501 | if (nodes == null || nodes.isEmpty()) | |
506 | 502 | return null; |
507 | 503 | int lon = co.getLongitude(); |
508 | 504 | 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; | |
514 | 508 | } |
515 | 509 | } |
516 | 510 | } |
535 | 529 | if (treePath.equals(DEBUG_TREEPATH)){ |
536 | 530 | nodeElem.saveGPX(prefix,treePath); |
537 | 531 | } |
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()); | |
546 | 541 | ++n; |
547 | 542 | } |
548 | 543 | } |
579 | 574 | tmpNodeElem.saveGPX("intersection_rect",treePath); |
580 | 575 | } |
581 | 576 | } |
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 | } | |
587 | 582 | } |
588 | 583 | } |
589 | 584 | return ok; |
600 | 595 | private void add(Area area, String refs, String boundaryId, String treePath){ |
601 | 596 | Node node = this; |
602 | 597 | String path = treePath; |
603 | while(path.isEmpty() == false){ | |
598 | while (!path.isEmpty()) { | |
604 | 599 | int idx = Integer.parseInt(path.substring(0, 1)); |
605 | 600 | path = path.substring(1); |
606 | 601 | if (node.childs == null) |
686 | 681 | private Area getCoveredArea(Integer admLevel, String treePath){ |
687 | 682 | HashMap<String,List<Area>> areas = new HashMap<>(); |
688 | 683 | this.getAreas(areas, treePath, admLevel); |
689 | if (areas.isEmpty() == false){ | |
684 | if (!areas.isEmpty()) { | |
690 | 685 | Path2D.Double path = new Path2D.Double(PathIterator.WIND_NON_ZERO, 1024 * 1024); |
691 | 686 | for (Entry <String, List<Area>> entry : areas.entrySet()){ |
692 | 687 | for (Area area: entry.getValue()){ |
693 | 688 | path.append(area, false); |
694 | 689 | } |
695 | 690 | } |
696 | Area combinedArea = new Area(path); | |
697 | return combinedArea; | |
691 | return new Area(path); | |
698 | 692 | } |
699 | 693 | return new Area(); |
700 | 694 | } |
707 | 701 | */ |
708 | 702 | private void getAreas(Map<String, List<Area>> areas, String treePath, Integer admLevel){ |
709 | 703 | 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); | |
712 | 706 | } |
713 | 707 | return; |
714 | 708 | } |
715 | if (nodes == null || nodes.size() == 0) | |
709 | if (nodes == null || nodes.isEmpty()) | |
716 | 710 | return; |
717 | 711 | |
718 | 712 | Short testMask = null; |
722 | 716 | String id = nodeElem.boundaryId; |
723 | 717 | if (testMask != null && (nodeElem.tagMask & testMask) == 0) |
724 | 718 | continue; |
725 | List<Area> aList = areas.get(id); | |
726 | 719 | 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); | |
732 | 721 | if (testMask != null) |
733 | 722 | continue; |
734 | 723 | |
742 | 731 | continue; |
743 | 732 | } |
744 | 733 | id = relParts[1]; |
745 | aList = areas.get(id); | |
746 | 734 | 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); | |
752 | 736 | } |
753 | 737 | } |
754 | 738 | } |
763 | 747 | * @param treePath Identifies the position in the tree |
764 | 748 | */ |
765 | 749 | private void makeDistinct(String treePath){ |
766 | if (isLeaf == false || nodes == null || nodes.size() <= 1) | |
750 | if (!isLeaf || nodes == null || nodes.size() <= 1) | |
767 | 751 | return; |
768 | 752 | if (DEBUG){ |
769 | 753 | printNodes("start", treePath); |
787 | 771 | // detect intersection of areas, merge tag info |
788 | 772 | for (int i=0; i < nodes.size(); i++){ |
789 | 773 | 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()) | |
799 | 783 | break; |
800 | 784 | NodeElem currElem = reworked.get(j); |
801 | 785 | if (currElem.srcPos == i || currElem.getArea().isEmpty()) |
818 | 802 | Area toAddMinusCurr = new Area(toAdd.getArea()); |
819 | 803 | toAddMinusCurr.subtract(currElem.getArea()); |
820 | 804 | |
821 | if (toAddMinusCurr.isEmpty()){ | |
805 | if (toAddMinusCurr.isEmpty() && | |
806 | toAdd.tagMask == POSTCODE_ONLY) { | |
822 | 807 | // 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; | |
829 | 812 | } |
830 | 813 | |
831 | 814 | // test if toAdd contains usable tag(s) |
832 | 815 | String chkMsg = currElem.checkAddTags(toAdd, bounds); |
833 | 816 | // 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); | |
840 | 823 | } |
841 | 824 | log.warn(chkMsg); |
842 | 825 | } |
876 | 859 | } |
877 | 860 | nodes = reworked; |
878 | 861 | // free memory for nodes with empty or too small areas |
879 | removeEmptyAreas(treePath); | |
862 | removeEmptyAreas(); | |
880 | 863 | |
881 | 864 | long dt = System.currentTimeMillis()-t1; |
882 | 865 | if (dt > 1000){ |
883 | log.info(bbox_key, " : makeDistinct required long time:", dt, "ms"); | |
866 | log.info(bboxKey, " : makeDistinct required long time:", dt, "ms"); | |
884 | 867 | } |
885 | 868 | if (DEBUG) |
886 | 869 | printNodes("end", treePath); |
921 | 904 | NodeElem lastNode = nodes.get(nodes.size()-1); |
922 | 905 | NodeElem prevNode = nodes.get(nodes.size()-2); |
923 | 906 | // 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()) { | |
925 | 909 | // two areas are rectangles, it is likely that they are equal to the bounding box |
926 | 910 | // In this case we add the tags to the existing area instead of creating a new one |
927 | 911 | if (prevNode.getArea().equals(lastNode.getArea())){ |
937 | 921 | * The mergeBoundaries() algorithm can create empty |
938 | 922 | * areas (points, lines, or extremely small intersections). |
939 | 923 | * These are removed here. |
940 | * @param treePath | |
941 | */ | |
942 | private void removeEmptyAreas(String treePath){ | |
924 | */ | |
925 | private void removeEmptyAreas() { | |
943 | 926 | for (int j = nodes.size()-1; j >= 0 ; j--){ |
944 | 927 | boolean removeThis = false; |
945 | 928 | NodeElem chkRemove = nodes.get(j); |
946 | if (chkRemove.isValid() == false) | |
929 | if (!chkRemove.isValid()) | |
947 | 930 | removeThis = true; |
948 | else if (this.bbox.intersects(chkRemove.getArea().getBounds2D()) == false){ | |
931 | else if (!this.bbox.intersects(chkRemove.getArea().getBounds2D())) { | |
949 | 932 | // we might get here because of errors in java.awt.geom.Area |
950 | 933 | // sometimes, Area.subtract() seems to produce an area which |
951 | 934 | // lies outside of original areas |
952 | 935 | removeThis = true; |
953 | }else if (!isWritable(chkRemove.getArea())){ | |
936 | } else if (!isWritable(chkRemove.getArea())) { | |
954 | 937 | removeThis = true; |
955 | 938 | } |
956 | if (removeThis){ | |
939 | if (removeThis) { | |
957 | 940 | nodes.remove(j); |
958 | 941 | } |
959 | 942 | } |
986 | 969 | * distribute the data. |
987 | 970 | */ |
988 | 971 | private void split(String treePath){ |
989 | if (isLeaf == true){ | |
972 | if (isLeaf) { | |
990 | 973 | if (nodes == null) |
991 | 974 | return; |
992 | 975 | if (DEBUG){ |
1000 | 983 | return ; |
1001 | 984 | } |
1002 | 985 | |
1003 | // mergeLastRectangles(); | |
1004 | 986 | allocChilds(); |
1005 | for (NodeElem nodeElem: nodes){ | |
987 | for (NodeElem nodeElem : nodes) { | |
1006 | 988 | Rectangle shapeBBox = nodeElem.shape.getBounds(); |
1007 | for (int i = 0; i < 4; i++){ | |
989 | for (int i = 0; i < 4; i++) { | |
1008 | 990 | if (childs[i].bbox.intersects(shapeBBox)) |
1009 | 991 | childs[i].add(nodeElem.shape, nodeElem.boundaryId, nodeElem.locationDataSrc, DO_CLIP); |
1010 | 992 | } |
1087 | 1069 | * @return false if either the area is not usable or |
1088 | 1070 | * the tags should be ignored. |
1089 | 1071 | */ |
1090 | private boolean isValid(){ | |
1072 | private boolean isValid() { | |
1091 | 1073 | if (tagMask == 0) |
1092 | 1074 | return false; |
1093 | 1075 | 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 | ||
1099 | 1081 | /** |
1100 | 1082 | * Add the location relevant data of another NodeElem |
1101 | 1083 | * @param toAdd the other NodeElem |
1102 | 1084 | */ |
1103 | private void addLocInfo(NodeElem toAdd){ | |
1085 | private void addLocInfo(NodeElem toAdd) { | |
1104 | 1086 | addLocationDataString(toAdd); |
1105 | addMissingTags(toAdd.locTags); | |
1087 | addMissingTags(toAdd.locTags); | |
1106 | 1088 | tagMask |= toAdd.tagMask; |
1107 | 1089 | } |
1108 | 1090 | |
1117 | 1099 | locTags = new Tags(); |
1118 | 1100 | tagMask = 0; |
1119 | 1101 | BoundaryLocationInfo bInfo = preparedLocationInfo.get(boundaryId); |
1120 | if (bInfo == null){ | |
1102 | if (bInfo == null) { | |
1121 | 1103 | log.error("unknown boundaryId " + boundaryId); |
1122 | 1104 | return; |
1123 | 1105 | } |
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()); | |
1130 | 1112 | } |
1131 | 1113 | if (locTags.size() == 0 && bInfo.getName() != null) { |
1132 | 1114 | locTags.put("mkgmap:other", bInfo.getName()); |
1133 | 1115 | } |
1134 | if (locationDataSrc != null && locationDataSrc.isEmpty() == false){ | |
1116 | if (locationDataSrc != null && !locationDataSrc.isEmpty()) { | |
1135 | 1117 | // the common format of refInfo is |
1136 | 1118 | // 2:r19884;4:r20039;6:r998818 |
1137 | 1119 | String[] relBounds = locationDataSrc.split(Pattern.quote(";")); |
1154 | 1136 | } |
1155 | 1137 | String addZip = addInfo.getZip(); |
1156 | 1138 | |
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); | |
1164 | 1144 | } |
1165 | 1145 | } |
1166 | 1146 | } |
1291 | 1271 | String errMsg = null; |
1292 | 1272 | int errAdmLevel = 0; |
1293 | 1273 | // 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++) { | |
1295 | 1275 | 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) { | |
1298 | 1278 | 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))) { | |
1300 | 1280 | errMsg = "different " + zipKey; |
1301 | 1281 | break; |
1302 | 1282 | } |
1303 | 1283 | } else if (testMask == NAME_ONLY) { |
1304 | 1284 | break; // happens with ResidentialHook, silently ignore it |
1305 | 1285 | } else { |
1306 | errAdmLevel = k+1; | |
1307 | errMsg = new String ("same admin_level (" + errAdmLevel + ")"); | |
1286 | errAdmLevel = k + 1; | |
1287 | errMsg = "same admin_level (" + errAdmLevel + ")"; | |
1308 | 1288 | break; |
1309 | 1289 | } |
1310 | 1290 | } |
1311 | 1291 | } |
1312 | if (errMsg != null){ | |
1292 | if (errMsg != null) { | |
1313 | 1293 | String url = bounds.getCenter().toOSMURL() + "&"; |
1314 | 1294 | url += (other.boundaryId.startsWith("w")) ? "way" : "relation"; |
1315 | 1295 | 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 + " "; | |
1318 | 1299 | if (errAdmLevel != 0 && this.locationDataSrc != null) |
1319 | 1300 | errMsg += this.locationDataSrc; |
1320 | 1301 | } |
1391 | 1372 | return false; |
1392 | 1373 | Path2D.Double path = new Path2D.Double(area); |
1393 | 1374 | Area testArea = new Area(path); |
1394 | if (testArea.isEmpty()){ | |
1395 | return false; | |
1396 | } | |
1397 | return true; | |
1375 | return !testArea.isEmpty(); | |
1398 | 1376 | } |
1399 | 1377 | } |
15 | 15 | */ |
16 | 16 | package uk.me.parabola.mkgmap.reader.polish; |
17 | 17 | |
18 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; | |
19 | ||
20 | ||
21 | 18 | import java.io.BufferedReader; |
22 | 19 | import java.io.FileNotFoundException; |
23 | 20 | import java.io.IOException; |
37 | 34 | import java.util.Map; |
38 | 35 | |
39 | 36 | import uk.me.parabola.imgfmt.FormatException; |
37 | import uk.me.parabola.imgfmt.MapFailedException; | |
40 | 38 | import uk.me.parabola.imgfmt.Utils; |
41 | 39 | import uk.me.parabola.imgfmt.app.Area; |
42 | 40 | import uk.me.parabola.imgfmt.app.Coord; |
53 | 51 | import uk.me.parabola.mkgmap.general.MapElement; |
54 | 52 | import uk.me.parabola.mkgmap.general.MapLine; |
55 | 53 | import uk.me.parabola.mkgmap.general.MapPoint; |
54 | import uk.me.parabola.mkgmap.general.MapRoad; | |
56 | 55 | import uk.me.parabola.mkgmap.general.MapShape; |
57 | 56 | import uk.me.parabola.mkgmap.general.ZipCodeInfo; |
58 | 57 | import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource; |
59 | 58 | import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; |
59 | import uk.me.parabola.mkgmap.reader.osm.GType; | |
60 | 60 | import uk.me.parabola.mkgmap.reader.osm.GeneralRelation; |
61 | 61 | import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation; |
62 | 62 | import uk.me.parabola.mkgmap.reader.osm.Way; |
112 | 112 | |
113 | 113 | private boolean havePolygon4B; |
114 | 114 | |
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 | ||
115 | 119 | // Use to decode labels if they are not in cp1252 |
116 | 120 | private CharsetDecoder dec; |
117 | 121 | |
118 | Long2ObjectOpenHashMap<Coord> coordMap = new Long2ObjectOpenHashMap<>(); | |
119 | 122 | public boolean isFileSupported(String name) { |
120 | 123 | // Supported if the extension is .mp |
121 | 124 | return name.endsWith(".mp") || name.endsWith(".MP") || name.endsWith(".mp.gz"); |
162 | 165 | |
163 | 166 | if (addBackground && !havePolygon4B) |
164 | 167 | addBackground(); |
165 | coordMap = null; | |
166 | 168 | } |
167 | 169 | |
168 | 170 | public LevelInfo[] mapLevels() { |
234 | 236 | section = S_RESTRICTION; |
235 | 237 | } |
236 | 238 | else |
237 | System.out.println("Ignoring unrecognised section: " + name); | |
239 | log.info("Ignoring unrecognised section: " + name); | |
238 | 240 | } |
239 | 241 | |
240 | 242 | /** |
248 | 250 | break; |
249 | 251 | |
250 | 252 | 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 | ||
255 | 262 | break; |
256 | 263 | case S_POLYLINE: |
257 | 264 | if (!lineStringMap.isEmpty()) { |
262 | 269 | setResolution(origPolyline, level); |
263 | 270 | for (List<Coord> points : entry.getValue()) { |
264 | 271 | polyline = origPolyline.copy(); |
272 | if (!routing && GType.isRoutableLineType(polyline.getType())) { | |
273 | roadHelper.setRoadId(++roadIdGenerated); | |
274 | } | |
265 | 275 | if (roadHelper.isRoad() && level == 0) { |
266 | 276 | 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); | |
268 | 282 | } |
269 | 283 | else { |
270 | 284 | if(extraAttributes != null && polyline.hasExtendedType()) |
411 | 425 | private void point(String name, String value) { |
412 | 426 | if (name.equals("Type")) { |
413 | 427 | int type = Integer.decode(value); |
428 | if (type <= 0xff) | |
429 | type <<= 8; | |
414 | 430 | point.setType(type); |
415 | 431 | } else if (name.equals("SubType")) { |
416 | 432 | int subtype = Integer.decode(value); |
417 | 433 | int type = point.getType(); |
418 | if (type <= 0xff) | |
419 | point.setType((type << 8) | subtype); | |
434 | point.setType(type | subtype); | |
420 | 435 | } else if (name.startsWith("Data") || name.startsWith("Origin")) { |
421 | 436 | Coord co = makeCoord(value); |
422 | 437 | setResolution(point, name); |
451 | 466 | fixElevation(); |
452 | 467 | } |
453 | 468 | } 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); | |
454 | 471 | roadHelper.setRoadId(Integer.parseInt(value)); |
455 | 472 | } else if (name.startsWith("Nod")) { |
456 | 473 | roadHelper.addNode(value); |
476 | 493 | public Numbers parseNumbers(String spec) { |
477 | 494 | Numbers nums = new Numbers(); |
478 | 495 | String[] strings = spec.split(","); |
479 | nums.setNodeNumber(Integer.parseInt(strings[0])); | |
496 | nums.setPolishIndex(Integer.parseInt(strings[0])); | |
480 | 497 | NumberStyle numberStyle = NumberStyle.fromChar(strings[1]); |
481 | 498 | int start = Integer.parseInt(strings[2]); |
482 | 499 | int end = Integer.parseInt(strings[3]); |
821 | 838 | } else if ("Numbering".equals(name)) { |
822 | 839 | // ignore |
823 | 840 | } else if ("Routing".equals(name)) { |
824 | // ignore | |
841 | if ("Y".equals(value)) { | |
842 | routing = true; // don't generate roadid | |
843 | } | |
825 | 844 | } else if ("CountryName".equalsIgnoreCase(name)) { |
826 | 845 | defaultCountry = value; |
827 | 846 | } else if ("RegionName".equalsIgnoreCase(name)) { |
828 | 847 | defaultRegion = value; |
829 | 848 | } else { |
830 | System.out.println("'IMG ID' section: ignoring " + name + " " + value); | |
849 | log.info("'IMG ID' section: ignoring " + name + " " + value); | |
831 | 850 | } |
832 | 851 | |
833 | 852 | } |
847 | 866 | |
848 | 867 | Double f1 = Double.valueOf(fields[i]); |
849 | 868 | 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); | |
857 | 870 | } |
858 | 871 | |
859 | 872 | private ExtTypeAttributes makeExtTypeAttributes() { |
934 | 947 | } else if (name.equalsIgnoreCase("RestrParam")) { |
935 | 948 | restriction.setExceptMask(getRestrictionExceptionMask(value)); |
936 | 949 | } else if (name.equalsIgnoreCase("Time")) { |
937 | // Do nothing for now | |
950 | log.info("Time in restriction definition is ignored " + restriction); | |
938 | 951 | } |
939 | 952 | } |
940 | 953 | } catch (NumberFormatException ex) { // This exception means that this restriction is not properly defined. |
47 | 47 | |
48 | 48 | @Override |
49 | 49 | public String toString() { |
50 | return "TurnRestriction" + trafficNodes; | |
50 | return "TurnRestriction" + Arrays.toString(trafficNodes); | |
51 | 51 | } |
52 | 52 | |
53 | 53 | public void setTrafficPoints(String idsList) { |
19 | 19 | import java.util.HashMap; |
20 | 20 | import java.util.List; |
21 | 21 | import java.util.Map; |
22 | import java.util.SortedMap; | |
23 | import java.util.TreeMap; | |
22 | 24 | |
23 | 25 | import uk.me.parabola.imgfmt.MapFailedException; |
24 | 26 | import uk.me.parabola.imgfmt.app.Coord; |
25 | 27 | import uk.me.parabola.imgfmt.app.CoordNode; |
26 | 28 | import uk.me.parabola.imgfmt.app.net.AccessTagsAndBits; |
27 | import uk.me.parabola.imgfmt.app.net.NumberStyle; | |
28 | 29 | import uk.me.parabola.imgfmt.app.net.Numbers; |
29 | 30 | import uk.me.parabola.log.Logger; |
30 | 31 | import uk.me.parabola.mkgmap.general.MapLine; |
54 | 55 | private boolean toll; |
55 | 56 | |
56 | 57 | private byte mkgmapAccess; |
57 | private List<Numbers> numbers; | |
58 | private SortedMap<Integer, Numbers> numbersMap; | |
58 | 59 | |
59 | 60 | public RoadHelper() { |
60 | 61 | clear(); |
68 | 69 | roadClass = 0; |
69 | 70 | oneway = false; |
70 | 71 | toll = false; |
71 | numbers = null; | |
72 | numbersMap = null; | |
72 | 73 | } |
73 | 74 | |
74 | 75 | public void setRoadId(int roadId) { |
97 | 98 | roadClass = 0; |
98 | 99 | if (roadClass > 4) |
99 | 100 | 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; | |
102 | 103 | byte noAccess = 0; |
103 | 104 | for (int j = 0; j < f.length - 4; j++){ |
104 | 105 | if (Integer.parseInt(f[4+j]) == 0) |
133 | 134 | road.setToll(); |
134 | 135 | road.setAccess(mkgmapAccess); |
135 | 136 | |
136 | if (numbers != null && !numbers.isEmpty()) { | |
137 | convertNodesForHouseNumbers(road); | |
138 | road.setNumbers(numbers); | |
139 | } | |
140 | ||
141 | 137 | List<Coord> points = road.getPoints(); |
142 | ||
143 | 138 | for (NodeIndex ni : nodes) { |
144 | 139 | int n = ni.index; |
145 | 140 | if (log.isDebugEnabled()) |
160 | 155 | log.warn("Inconsistant node ids"); |
161 | 156 | } |
162 | 157 | } |
158 | if (numbersMap != null) { | |
159 | if (numbersMap.values().stream().anyMatch(n -> !n.isEmpty())) { | |
160 | convertNodesForHouseNumbers(road); | |
161 | } else { | |
162 | numbersMap = null; | |
163 | } | |
164 | } | |
163 | 165 | |
164 | 166 | return road; |
165 | 167 | } |
166 | 168 | |
167 | 169 | /** |
168 | * Make sure that each node that is referenced by the house | |
170 | * Make sure that each node which is referenced by the house | |
169 | 171 | * numbers is a number node. Some of them will later be changed |
170 | 172 | * to routing nodes. |
171 | 173 | * Only called if numbers is non-null and not empty. |
172 | 174 | */ |
173 | 175 | 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())); | |
180 | 202 | } |
181 | 203 | |
182 | 204 | public boolean isRoad() { |
188 | 210 | } |
189 | 211 | |
190 | 212 | 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); | |
195 | 216 | } |
196 | 217 | |
197 | 218 | private static class NodeIndex { |
38 | 38 | * variables BASE_LAT and BASE_LONG set to something just SW of where you |
39 | 39 | * are then the map generated will be located near where you are. Otherwise |
40 | 40 | * 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 | |
42 | 45 | * |
43 | 46 | * 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 | |
45 | 48 | * |
46 | 49 | * You can then use the find facility of your GPS to |
47 | 50 | * show the near-by points. When viewing a category the menu key will allow |
96 | 99 | |
97 | 100 | drawTestMap(mapper, baseLat, baseLong, false); |
98 | 101 | // 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 | |
99 | 103 | baseLat += (MAX_POINT_SUB_TYPE + 4) * ELEMENT_SPACING; // assume taller than lines and areas |
100 | 104 | drawBackground(mapper, baseLat, baseLong, MAX_POINT_SUB_TYPE + 3, MAX_POINT_TYPE + MAX_LINE_TYPE_X + MAX_SHAPE_TYPE_X + 4); |
101 | 105 | drawTestMap(mapper, baseLat, baseLong, true); |
102 | 106 | } |
103 | 107 | |
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) { | |
105 | 113 | 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 | |
107 | 115 | shape.setMinResolution(10); |
108 | 116 | shape.setName("background"); |
109 | 117 | |
159 | 167 | double lat = slat + 0.004; |
160 | 168 | double lon = slon + 0.002; |
161 | 169 | |
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; | |
174 | 175 | |
175 | 176 | MapPoint point = new MapPoint(); |
176 | 177 | |
178 | 179 | double baseLong = lon + maintype * ELEMENT_SPACING; |
179 | 180 | |
180 | 181 | point.setMinResolution(10); |
181 | if (subtype < 0 ? hasBackground : !hasBackground) | |
182 | point.setName(GType.formatType(type)); | |
182 | if (!hasBackground) | |
183 | point.setName(makeName(type, true)); | |
183 | 184 | point.setLocation(new Coord(baseLat, baseLong)); |
184 | 185 | point.setType(type); |
185 | 186 | |
186 | 187 | mapper.addPoint(point); |
187 | ||
188 | ||
188 | 189 | 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()); | |
190 | 192 | mapper.addToBounds(point.getLocation()); // XXX shouldn't be needed. |
191 | if (maintype == 0) | |
192 | break; | |
193 | 193 | } |
194 | 194 | } |
195 | 195 | } |
205 | 205 | MapLine line = new MapLine(); |
206 | 206 | line.setMinResolution(10); |
207 | 207 | if (!hasBackground) |
208 | line.setName(GType.formatType(type)); | |
208 | line.setName(makeName(type, false)); | |
209 | 209 | |
210 | 210 | double baseLat = lat + y * ELEMENT_SPACING; |
211 | 211 | double baseLong = lon + x * ELEMENT_SPACING; |
215 | 215 | coords.add(co); |
216 | 216 | mapper.addToBounds(co); |
217 | 217 | 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()); | |
219 | 219 | |
220 | 220 | co = new Coord(baseLat + ELEMENT_SIZE, baseLong + ELEMENT_SIZE); |
221 | 221 | coords.add(co); |
222 | 222 | mapper.addToBounds(co); |
223 | 223 | |
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); | |
225 | 225 | coords.add(co); |
226 | 226 | mapper.addToBounds(co); |
227 | 227 | |
247 | 247 | |
248 | 248 | MapShape shape = new MapShape(); |
249 | 249 | shape.setMinResolution(10); |
250 | if (hasBackground) | |
251 | shape.setName(GType.formatType(type)); | |
250 | if (!hasBackground) | |
251 | shape.setName(makeName(type, false)); | |
252 | 252 | |
253 | 253 | double baseLat = lat + y * ELEMENT_SPACING; |
254 | 254 | double baseLong = lon + x * ELEMENT_SPACING; |
260 | 260 | coords.add(co); |
261 | 261 | mapper.addToBounds(co); |
262 | 262 | 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()); | |
264 | 264 | |
265 | 265 | co = new Coord(baseLat + ELEMENT_SIZE, baseLong); |
266 | 266 | coords.add(co); |
207 | 207 | continue; |
208 | 208 | } |
209 | 209 | w.deleteTag(MultiPolygonRelation.STYLE_FILTER_TAG); |
210 | w.deleteTag(MultiPolygonRelation.MP_CREATED_TAG); | |
210 | w.deleteTag(MultiPolygonRelation.MP_CREATED_TAG_KEY); | |
211 | 211 | ways.add(w); |
212 | 212 | } |
213 | 213 | } |
328 | 328 | CodePosition cp = new CodePosition(); |
329 | 329 | int b = r.getBval(); |
330 | 330 | int primary = sort.getPrimary(b); |
331 | cp.setPrimary((char) primary); | |
331 | cp.setPrimary(primary); | |
332 | 332 | |
333 | 333 | // We do not want the character to sort fully equal to the expanded characters (or any other |
334 | 334 | // character so adjust the ordering at other strengths. May need further tweaks. |
345 | 345 | } else { |
346 | 346 | secondary = 1; |
347 | 347 | } |
348 | cp.setSecondary((byte) (secondary)); | |
349 | cp.setTertiary((byte) (tertiary)); | |
348 | cp.setSecondary(secondary); | |
349 | cp.setTertiary(tertiary); | |
350 | 350 | } else { |
351 | 351 | 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); | |
357 | 357 | } |
358 | 358 | expansions.add(cp); |
359 | 359 | } |
72 | 72 | public boolean getProperty(String key, boolean def) { |
73 | 73 | String s = getProperty(key); |
74 | 74 | if (s != null) { |
75 | if (s.length() == 0) | |
75 | if (s.isEmpty()) | |
76 | 76 | return true; |
77 | 77 | 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'); | |
82 | 79 | } |
83 | 80 | return def; |
84 | 81 | } |
119 | 119 | set = null; |
120 | 120 | nextPoint = null; |
121 | 121 | |
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; | |
124 | 124 | } |
125 | 125 | |
126 | 126 | /** |
135 | 135 | this.maxDist = Math.pow(maxDist * 360 / Coord.U, 2); // convert maxDist in meter to distanceInDegreesSquared |
136 | 136 | nextPoint = null; |
137 | 137 | set = new LinkedHashSet<>(); |
138 | // false => first node is a latitude level | |
139 | 138 | findNextPoint(p.getLocation(), root, ROOT_NODE_USES_LONGITUDE); |
140 | 139 | return set; |
141 | 140 | } |
142 | 141 | |
143 | 142 | /** |
144 | 143 | * 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. | |
146 | 146 | * |
147 | 147 | * @param p the location of the given point |
148 | 148 | * @param tree the sub tree |
149 | 149 | * @param useLongitude gives the dimension to search in |
150 | * @return the closest point | |
151 | 150 | */ |
152 | private T findNextPoint(Coord p, KdNode tree, boolean useLongitude) { | |
151 | private void findNextPoint(Coord p, KdNode tree, boolean useLongitude) { | |
153 | 152 | if (tree == null) |
154 | return nextPoint; | |
155 | ||
156 | boolean smaller; | |
153 | return; | |
157 | 154 | |
158 | 155 | if (tree.left == null && tree.right == null) { |
159 | 156 | processNode(tree, p); |
160 | return nextPoint; | |
157 | return; | |
161 | 158 | } |
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); | |
164 | 161 | |
165 | 162 | processNode(tree, p); |
166 | 163 | // do we have to search the other part of the tree? |
168 | 165 | int testLon = useLongitude ? tree.point.getLocation().getHighPrecLon() : p.getHighPrecLon(); |
169 | 166 | Coord test = Coord.makeHighPrecCoord(testLat, testLon); |
170 | 167 | if (test.distanceInDegreesSquared(p) < minDist) { |
171 | nextPoint = findNextPoint(p, smaller ? tree.right : tree.left, !useLongitude); | |
168 | findNextPoint(p, smaller ? tree.right : tree.left, !useLongitude); | |
172 | 169 | } |
173 | return nextPoint; | |
174 | 170 | } |
175 | 171 | |
176 | 172 | private void processNode(KdNode node, Coord p) { |
94 | 94 | case "RGN": |
95 | 95 | count++; |
96 | 96 | System.out.println("RGN size " + size); |
97 | assertThat("RGN size", size, new RangeMatcher(2704)); | |
97 | assertThat("RGN size", size, new RangeMatcher(2710)); | |
98 | 98 | break; |
99 | 99 | case "TRE": |
100 | 100 | count++; |
51 | 51 | case "RGN": |
52 | 52 | count++; |
53 | 53 | System.out.println("RGN size " + size); |
54 | assertThat("RGN size", size, new RangeMatcher(127586)); | |
54 | assertThat("RGN size", size, new RangeMatcher(127579)); | |
55 | 55 | break; |
56 | 56 | case "TRE": |
57 | 57 | count++; |
69 | 69 | break; |
70 | 70 | case "NOD": |
71 | 71 | count++; |
72 | assertEquals("NOD size", 170201, size); | |
72 | System.out.println("NOD size " + size); | |
73 | assertEquals("NOD size", 146631, size); | |
73 | 74 | break; |
74 | 75 | } |
75 | 76 | } |
106 | 107 | break; |
107 | 108 | case "NOD": |
108 | 109 | count++; |
110 | System.out.println("NOD size " + size); | |
109 | 111 | assertEquals("NOD size", 3584, size); |
110 | 112 | break; |
111 | 113 | } |
71 | 71 | List<Numbers> numbers = new ArrayList<Numbers>(); |
72 | 72 | for (String s : strings) { |
73 | 73 | Numbers n = new Numbers(s); |
74 | n.setIndex(n.getNodeNumber()); | |
74 | n.setIndex(n.getPolishIndex()); | |
75 | 75 | numbers.add(n); |
76 | 76 | } |
77 | 77 | |
88 | 88 | |
89 | 89 | // Have to fix up the node numbers |
90 | 90 | for (Numbers n : list) { |
91 | n.setNodeNumber(n.getIndex()); | |
91 | n.setPolishIndex(n.getIndex()); | |
92 | 92 | } |
93 | 93 | |
94 | 94 | // Test that they are the same. |
104 | 104 | "0,O,1,9,E,2,12", |
105 | 105 | "1,O,11,17,E,14,20", |
106 | 106 | "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", | |
107 | 118 | }); |
108 | 119 | List<Numbers> output = writeAndRead(numbers); |
109 | 120 | assertEquals(numbers, output); |
267 | 278 | nr.setNumberOfNodes(numbers.get(numbers.size()-1).getIndex() + 1); |
268 | 279 | List<Numbers> list = nr.readNumbers(swapped); |
269 | 280 | for (Numbers n : list) |
270 | n.setNodeNumber(n.getIndex()); | |
281 | n.setPolishIndex(n.getIndex()); | |
271 | 282 | |
272 | 283 | return list; |
273 | 284 | } |
274 | 285 | |
275 | 286 | private List<Numbers> createList(String[] specs) { |
276 | List<Numbers> numbers = new ArrayList<Numbers>(); | |
287 | List<Numbers> numbers = new ArrayList<>(); | |
277 | 288 | for (String s : specs) { |
278 | 289 | Numbers n = new Numbers(s); |
279 | n.setIndex(n.getNodeNumber()); | |
290 | n.setIndex(n.getPolishIndex()); | |
280 | 291 | numbers.add(n); |
281 | 292 | } |
282 | 293 | return numbers; |
82 | 82 | assertFalse(numbers.isPlausible()); |
83 | 83 | } |
84 | 84 | @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 | |
85 | 91 | public void testSingleNumBothSides() { |
86 | 92 | String spec = "0,O,15,15,O,15,15"; |
87 | 93 | Numbers numbers = new Numbers(spec); |
28 | 28 | "pool", "ocean" |
29 | 29 | }; |
30 | 30 | |
31 | String PATH_SEP = System.getProperty("file.separator"); | |
31 | static final String PATH_SEP = System.getProperty("file.separator"); | |
32 | 32 | |
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<>(); | |
36 | 36 | |
37 | 37 | /** |
38 | 38 | * You can have options with values separated by either a ':' or an |
118 | 118 | public void testRelativeFilenamesInFile() { |
119 | 119 | String s = "input-file: foo\n"; |
120 | 120 | |
121 | OptionProcessor proc = new MyOptionProcessor(); | |
122 | Options opts = new Options(proc); | |
121 | Options opts = new Options(myOptionProcessor); | |
123 | 122 | Reader r = new StringReader(s); |
124 | 123 | |
125 | 124 | opts.readOptionFile(r, "/bar/string.args"); |
146 | 145 | exp_dir = "/home"; |
147 | 146 | } |
148 | 147 | |
149 | OptionProcessor proc = new MyOptionProcessor(); | |
150 | Options opts = new Options(proc); | |
148 | Options opts = new Options(myOptionProcessor); | |
151 | 149 | Reader r = new StringReader(s); |
152 | 150 | |
153 | 151 | opts.readOptionFile(r, "/bar/string.args"); |
166 | 164 | } |
167 | 165 | |
168 | 166 | private void readOptionsFromString(String s) { |
169 | OptionProcessor proc = new MyOptionProcessor(); | |
170 | Options opts = new Options(proc); | |
167 | Options opts = new Options(myOptionProcessor); | |
171 | 168 | Reader r = new StringReader(s); |
172 | 169 | |
173 | 170 | opts.readOptionFile(r, "from-string"); |
174 | 171 | } |
175 | 172 | |
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 | }; | |
183 | 178 | } |
1219 | 1219 | */ |
1220 | 1220 | private GType getFirstType(Rule rs, Element el) { |
1221 | 1221 | 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)); | |
1227 | 1223 | if (types.isEmpty()) |
1228 | 1224 | return null; |
1229 | 1225 | else |