New upstream version 0.0.0+svn4223
Bas Couwenberg
5 years ago
533 | 533 | that support routing. If you specify this option, you do not need |
534 | 534 | to specify --net. |
535 | 535 | <p> |
536 | ;--add-boundary-nodes-at-admin-boundaries=NUM | |
537 | : This option controls how mkgmap calculates special routing nodes which | |
538 | are needed by Garmin software to allow routing between different map tiles. | |
539 | These nodes are written to section 3 and 4 in the NOD file. | |
540 | When a road crosses the tile boundary (bbox), the road is split at this | |
541 | point and such a special node is written. This allows routing between | |
542 | one set of tiles produced by splitter.jar. However, if you create a map | |
543 | from different sets of tiles, those tiles are likely to overlap. | |
544 | For the overlapping tiles, none of the entries in NOD3 match and thus | |
545 | routing across tile border doesn't work when the route is not fully | |
546 | covered by one of the tiles. | |
547 | The option tells mkgmap to add special nodes whereever a road touches or | |
548 | crosses an administratve boundary. The NUM parameter specifies a filter | |
549 | for the admin_level. Boundaries with a higher admin_level value are ignored. | |
550 | The default value is 2 (country borders). Another reasonable value might | |
551 | be 4. A value less or equal to 0 tells mkgmap to ignore intersections at | |
552 | administrative boundaries. | |
553 | <p> | |
536 | 554 | ;--drive-on=left|right|detect|detect,left|detect,right |
537 | 555 | : Explicitly specify which side of the road vehicles are |
538 | 556 | expected to drive on. |
538 | 538 | of drive-on-left roads with the rest. |
539 | 539 | Use the --bounds option to make sure that the detection |
540 | 540 | finds the correct country. |
541 | ||
542 | --add-boundary-nodes-at-admin-boundaries=NUM | |
543 | This option controls how mkgmap calculates special routing nodes which | |
544 | are needed by Garmin software to allow routing between different map tiles. | |
545 | These nodes are written to section 3 and 4 in the NOD file. | |
546 | When a road crosses the tile boundary (bbox), the road is split at this | |
547 | point and such a special node is written. This allows routing between | |
548 | one set of tiles produced by splitter.jar. However, if you create a map | |
549 | from different sets of tiles, those tiles are likely to overlap. | |
550 | For the overlapping tiles, none of the entries in NOD3 match and thus | |
551 | routing across tile border doesn't work when the route is not fully | |
552 | covered by one of the tiles. | |
553 | The option tells mkgmap to add special nodes whereever a road touches or | |
554 | crosses an administratve boundary. The NUM parameter specifies a filter | |
555 | for the admin_level. Boundaries with a higher admin_level value are ignored. | |
556 | The default value is 2 (country borders). Another reasonable value might | |
557 | be 4. A value less or equal to 0 tells mkgmap to ignore intersections at | |
558 | administrative boundaries. | |
541 | 559 | |
542 | 560 | --check-roundabouts |
543 | 561 | Check that roundabouts have the expected direction (clockwise |
0 | svn.version: 4214 | |
1 | build.timestamp: 2018-07-28T09:29:55+0100 | |
0 | svn.version: 4223 | |
1 | build.timestamp: 2018-08-13T07:21:37+0100 |
18 | 18 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level4=Hamburg {set mkgmap:city='${mkgmap:admin_level4}' } |
19 | 19 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level4=Berlin {set mkgmap:city='${mkgmap:admin_level4}' } |
20 | 20 | mkgmap:country=DEU & mkgmap:region!=* & mkgmap:admin_level4=* { set mkgmap:region='${mkgmap:admin_level4}' } |
21 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8|subst:Gemeinde |subst:Stadt}' } | |
22 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7|subst:Gemeinde |subst:Stadt}' } | |
23 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level6=* { set mkgmap:city='${mkgmap:admin_level6|subst:Gemeinde |subst:Stadt}' } | |
24 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9|subst:Gemeinde |subst:Stadt}' } | |
25 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10|subst:Gemeinde |subst:Stadt}' } | |
21 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level8=* { set mkgmap:city='${mkgmap:admin_level8|subst:Gemeinde |subst:Stadt }' } | |
22 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level7=* { set mkgmap:city='${mkgmap:admin_level7|subst:Gemeinde |subst:Stadt }' } | |
23 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level6=* { set mkgmap:city='${mkgmap:admin_level6|subst:Gemeinde |subst:Stadt }' } | |
24 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level9=* { set mkgmap:city='${mkgmap:admin_level9|subst:Gemeinde |subst:Stadt }' } | |
25 | mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level10=* { set mkgmap:city='${mkgmap:admin_level10|subst:Gemeinde |subst:Stadt }' } | |
26 | 26 | |
27 | 27 | |
28 | 28 | # Austria = AUT |
15 | 15 | */ |
16 | 16 | package uk.me.parabola.imgfmt; |
17 | 17 | |
18 | import java.awt.geom.Line2D; | |
18 | 19 | import java.io.Closeable; |
19 | 20 | import java.io.File; |
20 | 21 | import java.io.FileInputStream; |
27 | 28 | import java.util.Date; |
28 | 29 | import java.util.zip.GZIPInputStream; |
29 | 30 | |
31 | import uk.me.parabola.imgfmt.app.Coord; | |
30 | 32 | import uk.me.parabola.imgfmt.app.ImgFileWriter; |
31 | import uk.me.parabola.imgfmt.app.Coord; | |
32 | 33 | /** |
33 | 34 | * Some miscellaneous functions that are used within the .img code. |
34 | 35 | * |
388 | 389 | return (long)(latHp & 0xffffffffL) << 32 | (lonHp & 0xffffffffL); |
389 | 390 | } |
390 | 391 | |
391 | /** | |
392 | * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa. | |
393 | * This is a form of intersection check where it is allowed that one line ends on the | |
394 | * other line or that the two lines overlap. | |
395 | * @param p1_1 first point of line 1 | |
396 | * @param p1_2 second point of line 1 | |
397 | * @param p2_1 first point of line 2 | |
398 | * @param p2_2 second point of line 2 | |
399 | * @return true if both lines intersect somewhere in the middle of each other | |
400 | */ | |
401 | public static boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) { | |
402 | int width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon(); | |
403 | int width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon(); | |
404 | ||
405 | int height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat(); | |
406 | int height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat(); | |
407 | ||
408 | int denominator = ((height2 * width1) - (width2 * height1)); | |
409 | if (denominator == 0) { | |
410 | // the lines are parallel | |
411 | // they might overlap but this is ok for this test | |
412 | return false; | |
413 | } | |
414 | ||
415 | int x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon(); | |
416 | int y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat(); | |
417 | ||
418 | double isx = (double)((width2 * y1My3) - (height2 * x1Mx3)) | |
419 | / denominator; | |
420 | if (isx <= 0 || isx >= 1) { | |
421 | return false; | |
422 | } | |
423 | ||
424 | double isy = (double)((width1 * y1My3) - (height1 * x1Mx3)) | |
425 | / denominator; | |
426 | ||
427 | if (isy <= 0 || isy >= 1) { | |
428 | return false; | |
429 | } | |
430 | ||
431 | return true; | |
432 | } | |
433 | ||
434 | 392 | public static int numberToPointerSize(int n) { |
435 | 393 | // moved from imgfmt/app/mdr/MdrSection.java and app/typ/TYPFile.java |
436 | 394 | if (n <= 0xff) |
451 | 409 | writer.put3s(longitude); |
452 | 410 | } |
453 | 411 | |
412 | /** | |
413 | * Finds the intersection of two line segments, also if one ends on the other | |
414 | * See https://de.wikipedia.org/wiki/Geod%C3%A4tisches_Rechnen#Geradenschnitt | |
415 | * @param p1_1 the coordinates of the start point of the first specified line segment | |
416 | * @param p1_2 the coordinates of the end point of the first specified line segment | |
417 | * @param p2_1 the coordinates of the start point of the second specified line segment | |
418 | * @param p2_2 the coordinates of the end point of the second specified line segment | |
419 | * @return null if no intersection was found, else a new Coord instance with the coordinates of the intersection | |
420 | */ | |
421 | public static Coord getSegmentSegmentIntersection (Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) { | |
422 | long x1 = p1_1.getHighPrecLon(); | |
423 | long y1 = p1_1.getHighPrecLat(); | |
424 | long x2 = p1_2.getHighPrecLon(); | |
425 | long y2 = p1_2.getHighPrecLat(); | |
426 | long x3 = p2_1.getHighPrecLon(); | |
427 | long y3 = p2_1.getHighPrecLat(); | |
428 | long x4 = p2_2.getHighPrecLon(); | |
429 | long y4 = p2_2.getHighPrecLat(); | |
430 | ||
431 | if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) | |
432 | return null; | |
433 | ||
434 | long divider = (y2 - y1) * (x4 - x3) - (y4 - y3) * (x2 - x1); | |
435 | if (divider == 0) | |
436 | return null; // parallel | |
437 | double h = ((y4 - y3) * (x1 - x4) - (y1 - y4) * (x4 - x3)) / (double) divider; | |
438 | ||
439 | if (h < 0 || h > 1) { | |
440 | return null; // intersection of lines is not on given segment | |
441 | } | |
442 | double xs = x1 + h * (x2 - x1); | |
443 | double ys = y1 + h * (y2 - y1); | |
444 | return Coord.makeHighPrecCoord((int)Math.round(ys), (int) Math.round(xs)); | |
445 | } | |
446 | ||
454 | 447 | } |
448 |
68 | 68 | , Utils.toMapUnit(maxLat), Utils.toMapUnit(maxLong)); |
69 | 69 | } |
70 | 70 | |
71 | public static Area getBBox(List<Coord> points) { | |
72 | int tmpMinLat = Integer.MAX_VALUE; | |
73 | int tmpMaxLat = Integer.MIN_VALUE; | |
74 | int tmpMinLong = Integer.MAX_VALUE; | |
75 | int tmpMaxLong = Integer.MIN_VALUE; | |
76 | ||
77 | for (Coord co : points) { | |
78 | int lat = co.getLatitude(); | |
79 | if (lat < tmpMinLat) | |
80 | tmpMinLat = lat; | |
81 | if (lat > tmpMaxLat) | |
82 | tmpMaxLat = lat; | |
83 | ||
84 | int lon = co.getLongitude(); | |
85 | if (lon < tmpMinLong) | |
86 | tmpMinLong = lon; | |
87 | if (lon > tmpMaxLong) | |
88 | tmpMaxLong = lon; | |
89 | } | |
90 | return new Area(tmpMinLat, tmpMinLong, tmpMaxLat, tmpMaxLong); | |
91 | } | |
92 | ||
71 | 93 | public int getMinLat() { |
72 | 94 | return minLat; |
73 | 95 | } |
49 | 49 | private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer |
50 | 50 | private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval |
51 | 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 | |
52 | 53 | |
53 | 54 | private final static int HIGH_PREC_BITS = 30; |
54 | 55 | public final static int DELTA_SHIFT = HIGH_PREC_BITS - 24; |
339 | 340 | } |
340 | 341 | |
341 | 342 | /** |
342 | * @return if this is the beginning/end of a house number interval | |
343 | * @return true if this is the beginning/end of a house number interval | |
343 | 344 | */ |
344 | 345 | public boolean isNumberNode(){ |
345 | 346 | return (flags & HOUSENUMBER_NODE) != 0; |
356 | 357 | } |
357 | 358 | |
358 | 359 | /** |
359 | * @return if this is the beginning/end of a house number interval | |
360 | * @return true if this was added by the housenumber processing | |
360 | 361 | */ |
361 | 362 | public boolean isAddedNumberNode(){ |
362 | 363 | return (flags & ADDED_HOUSENUMBER_NODE) != 0; |
370 | 371 | this.flags |= ADDED_HOUSENUMBER_NODE; |
371 | 372 | else |
372 | 373 | this.flags &= ~ADDED_HOUSENUMBER_NODE; |
374 | } | |
375 | ||
376 | /** | |
377 | * @return true if this was marked as place on country border. | |
378 | */ | |
379 | public boolean getOnCountryBorder() { | |
380 | return (flags & ON_COUNTRY_BORDER) != 0; | |
381 | } | |
382 | ||
383 | /** | |
384 | * Mark as place on a country border | |
385 | * @param onCountryBorder | |
386 | */ | |
387 | public void setOnCountryBorder(boolean onCountryBorder) { | |
388 | if (onCountryBorder) | |
389 | this.flags |= ON_COUNTRY_BORDER; | |
390 | else | |
391 | this.flags &= ~ON_COUNTRY_BORDER; | |
373 | 392 | } |
374 | 393 | |
375 | 394 | public int hashCode() { |
681 | 700 | * @return true if rounding error is large. |
682 | 701 | */ |
683 | 702 | public boolean hasAlternativePos(){ |
684 | if (getOnBoundary()) | |
703 | if (getOnBoundary() || getOnCountryBorder()) | |
685 | 704 | return false; |
686 | 705 | return (Math.abs(latDelta) > MAX_DELTA || Math.abs(lonDelta) > MAX_DELTA); |
687 | 706 | } |
693 | 712 | */ |
694 | 713 | public List<Coord> getAlternativePositions(){ |
695 | 714 | ArrayList<Coord> list = new ArrayList<>(); |
696 | if (getOnBoundary()) | |
715 | if (getOnBoundary() || getOnCountryBorder()) | |
697 | 716 | return list; |
698 | 717 | int modLatDelta = 0; |
699 | 718 | int modLonDelta = 0; |
31 | 31 | * @param longitude The longitude in map units. |
32 | 32 | * @param id The ID of this routing node. |
33 | 33 | * @param boundary This is a routing node on the boundary. |
34 | * @param onCountryBorder This is a routing node on a country boundary. | |
34 | 35 | */ |
35 | public CoordNode(int latitude, int longitude, int id, boolean boundary) { | |
36 | public CoordNode(int latitude, int longitude, int id, boolean boundary, boolean onCountryBorder) { | |
36 | 37 | super(latitude, longitude); |
37 | 38 | this.id = id; |
38 | 39 | setOnBoundary(boundary); |
40 | setOnCountryBorder(onCountryBorder); | |
39 | 41 | setNumberNode(true); |
40 | 42 | preserved(true); |
41 | 43 | } |
42 | 44 | |
43 | public CoordNode(Coord other, int id, boolean boundary){ | |
45 | public CoordNode(Coord other, int id, boolean boundary, boolean onCountryBorder) { | |
44 | 46 | super(other); |
45 | 47 | this.id = id; |
46 | 48 | setOnBoundary(boundary); |
49 | setOnCountryBorder(onCountryBorder); | |
47 | 50 | setNumberNode(true); |
48 | 51 | preserved(true); |
49 | 52 |
75 | 75 | |
76 | 76 | public RouteNode(Coord coord) { |
77 | 77 | this.coord = (CoordNode) coord; |
78 | setBoundary(this.coord.getOnBoundary()); | |
78 | setBoundary(this.coord.getOnBoundary() || this.coord.getOnCountryBorder()); | |
79 | 79 | } |
80 | 80 | |
81 | 81 | private boolean haveLargeOffsets() { |
402 | 402 | Utils.toDegrees(currLon))); |
403 | 403 | |
404 | 404 | if (extra) |
405 | line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false)); | |
405 | line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false, false)); | |
406 | 406 | else |
407 | 407 | line.addCoord(new Coord(currLat, currLon)); |
408 | 408 | |
477 | 477 | currLon += dx << (24 - div.getResolution()); |
478 | 478 | Coord coord; |
479 | 479 | if (isnode) |
480 | coord = new CoordNode(currLat, currLon, 0/* XXX */, false); | |
480 | coord = new CoordNode(currLat, currLon, 0/* XXX */, false, false); | |
481 | 481 | else |
482 | 482 | coord = new Coord(currLat, currLon); |
483 | 483 |
67 | 67 | int endIndex = coords.size()-1; |
68 | 68 | for(int i = endIndex-1; i > 0; i--) { |
69 | 69 | Coord p = coords.get(i); |
70 | //int highwayCount = p.getHighwayCount(); | |
71 | ||
72 | // If a node in the line use the douglas peucker algorithm for upper segment | |
73 | // TODO: Should consider only nodes connected to roads visible at current resolution. | |
74 | 70 | if (p.preserved()) { |
75 | 71 | // point is "preserved", don't remove it |
76 | 72 | douglasPeucker(coords, i, endIndex, maxErrorDistance); |
99 | 95 | */ |
100 | 96 | public static void douglasPeucker(List<Coord> points, int startIndex, int endIndex, double allowedError) |
101 | 97 | { |
102 | if (startIndex >= endIndex) | |
98 | if (endIndex - startIndex <= 1) { | |
103 | 99 | return; |
104 | ||
100 | } | |
101 | ||
105 | 102 | double maxDistance = 0; //Highest distance |
106 | 103 | int maxIndex = endIndex; //Index of highest distance |
107 | 104 | |
108 | 105 | Coord a = points.get(startIndex); |
109 | 106 | Coord b = points.get(endIndex); |
110 | ||
111 | 107 | |
112 | 108 | // Find point with highest distance to line between start- and end-point. |
113 | // handle also closed or nearly closed lines and spikes on straight lines | |
114 | 109 | for(int i = endIndex-1; i > startIndex; i--) { |
115 | 110 | Coord p = points.get(i); |
116 | 111 | double distance = p.shortestDistToLineSegment(a, b); |
126 | 121 | } |
127 | 122 | else { |
128 | 123 | // All points in tolerance, delete all of them. |
129 | // Remove the end-point if it is the same as the start point | |
130 | if (a.highPrecEquals(b) && points.get(endIndex).preserved() == false) | |
131 | endIndex++; | |
132 | ||
133 | if (endIndex - startIndex > 4){ | |
124 | if (endIndex - startIndex > 4) { | |
134 | 125 | // faster than many repeated remove actions |
135 | points.subList(startIndex+1, endIndex).clear(); | |
136 | return; | |
137 | } | |
138 | ||
139 | // Remove the points in between | |
140 | for (int i = endIndex - 1; i > startIndex; i--) { | |
141 | points.remove(i); | |
126 | points.subList(startIndex + 1, endIndex).clear(); | |
127 | } else { | |
128 | for (int i = endIndex - 1; i > startIndex; i--) { | |
129 | points.remove(i); | |
130 | } | |
142 | 131 | } |
143 | 132 | } |
144 | 133 | } |
54 | 54 | Coord newP; |
55 | 55 | |
56 | 56 | if(p instanceof CoordNode && checkRouting) |
57 | newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary()); | |
57 | newP = new CoordNode(lat, lon, p.getId(), p.getOnBoundary(), p.getOnCountryBorder()); | |
58 | 58 | else |
59 | 59 | newP = new Coord(lat, lon); |
60 | 60 | newP.preserved(p.preserved()); |
24 | 24 | import java.util.IdentityHashMap; |
25 | 25 | import java.util.Iterator; |
26 | 26 | import java.util.LinkedHashMap; |
27 | import java.util.LinkedHashSet; | |
27 | 28 | import java.util.List; |
28 | 29 | import java.util.Map; |
30 | import java.util.Set; | |
29 | 31 | import java.util.Map.Entry; |
30 | 32 | import java.util.logging.Level; |
31 | 33 | |
34 | import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; | |
32 | 35 | import uk.me.parabola.imgfmt.ExitException; |
36 | import uk.me.parabola.imgfmt.Utils; | |
33 | 37 | import uk.me.parabola.imgfmt.app.Area; |
34 | 38 | import uk.me.parabola.imgfmt.app.Coord; |
35 | 39 | import uk.me.parabola.imgfmt.app.CoordNode; |
59 | 63 | import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator; |
60 | 64 | import uk.me.parabola.mkgmap.reader.osm.FeatureKind; |
61 | 65 | import uk.me.parabola.mkgmap.reader.osm.GType; |
66 | import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation; | |
62 | 67 | import uk.me.parabola.mkgmap.reader.osm.Node; |
63 | 68 | import uk.me.parabola.mkgmap.reader.osm.OsmConverter; |
64 | 69 | import uk.me.parabola.mkgmap.reader.osm.Relation; |
69 | 74 | import uk.me.parabola.mkgmap.reader.osm.Tags; |
70 | 75 | import uk.me.parabola.mkgmap.reader.osm.TypeResult; |
71 | 76 | import uk.me.parabola.mkgmap.reader.osm.Way; |
77 | import uk.me.parabola.util.ElementQuadTree; | |
72 | 78 | import uk.me.parabola.util.EnhancedProperties; |
73 | 79 | import uk.me.parabola.util.MultiHashMap; |
74 | 80 | |
111 | 117 | |
112 | 118 | public final static String WAY_POI_NODE_IDS = "mkgmap:way-poi-node-ids"; |
113 | 119 | private final HashMap<Integer, Map<String,MapPoint>> pointMap; |
114 | ||
120 | ||
121 | /** boundary ways with admin_level=2 */ | |
122 | private final Set<Way> borders = new LinkedHashSet<>(); | |
123 | private boolean addBoundaryNodesAtAdminBoundaries; | |
124 | private int admLevelNod3; | |
125 | ||
126 | ||
115 | 127 | private List<ConvertedWay> roads = new ArrayList<>(); |
116 | 128 | private List<ConvertedWay> lines = new ArrayList<>(); |
117 | 129 | private HashMap<Long, ConvertedWay> modifiedRoads = new HashMap<>(); |
209 | 221 | String styleOption= props.getProperty("style-option",null); |
210 | 222 | styleOptionTags = parseStyleOption(styleOption); |
211 | 223 | prefixSuffixFilter = new PrefixSuffixFilter(props); |
224 | ||
225 | // control calculation of extra nodes in NOD3 / NOD4 | |
226 | admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2); | |
227 | addBoundaryNodesAtAdminBoundaries = routable && admLevelNod3 > 0; | |
212 | 228 | } |
213 | 229 | |
214 | 230 | /** |
307 | 323 | removeRestrictionsWithWay(Level.WARNING, way, "is ignored"); |
308 | 324 | return; |
309 | 325 | } |
326 | if (addBoundaryNodesAtAdminBoundaries) { | |
327 | // is this a country border ? | |
328 | if (!FakeIdGenerator.isFakeId(way.getId()) && isNod3Border(way)) { | |
329 | borders.add(way); | |
330 | } | |
331 | } | |
332 | ||
310 | 333 | preConvertRules(way); |
311 | 334 | |
312 | 335 | String styleFilterTag = way.getTag(styleFilterTagKey); |
371 | 394 | } |
372 | 395 | } |
373 | 396 | |
397 | private boolean isNod3Border(Element el) { | |
398 | if ("administrative".equals(el.getTag("boundary"))) { | |
399 | String admLevelString = el.getTag("admin_level"); | |
400 | if (admLevelString != null) { | |
401 | try { | |
402 | int al = Integer.valueOf(admLevelString); | |
403 | return al <= admLevelNod3; | |
404 | } catch (NumberFormatException e) { | |
405 | } | |
406 | } | |
407 | } | |
408 | return false; | |
409 | } | |
410 | ||
374 | 411 | private int lineIndex = 0; |
375 | 412 | private final static short onewayTagKey = TagDict.getInstance().xlate("oneway"); |
376 | 413 | private void addConvertedWay(Way way, GType foundType) { |
430 | 467 | |
431 | 468 | /** One type result for nodes to avoid recreating one for each node. */ |
432 | 469 | private NodeTypeResult nodeTypeResult = new NodeTypeResult(); |
470 | ||
433 | 471 | private class NodeTypeResult implements TypeResult { |
434 | 472 | private Node node; |
435 | 473 | /** flag if the rule was fired */ |
582 | 620 | } |
583 | 621 | |
584 | 622 | /** |
623 | * Find all intersections of roads with country borders. | |
624 | * If a node exists close to the intersection, use the existing node, else add one. The nodes will be | |
625 | * written as external nodes to NOD file (NOD3 + NOD4) | |
626 | */ | |
627 | private void checkRoutingNodesAtAdminBoundaries() { | |
628 | if (!addBoundaryNodesAtAdminBoundaries || borders.isEmpty()) | |
629 | return; | |
630 | long t1 = System.currentTimeMillis(); | |
631 | ||
632 | // prepare boundary ways so that we can search them | |
633 | List<Element> clippedBorders = new ArrayList<>(); | |
634 | for (Way b : borders) { | |
635 | List<List<Coord>> clipped = LineClipper.clip(bbox, b.getPoints()); | |
636 | if (clipped == null) { | |
637 | splitBoundary(clippedBorders, b, b.getPoints()); | |
638 | } else { | |
639 | for (List<Coord> lco : clipped) { | |
640 | splitBoundary(clippedBorders, b, lco); | |
641 | } | |
642 | } | |
643 | } | |
644 | ElementQuadTree qt = new ElementQuadTree(bbox, clippedBorders); | |
645 | ||
646 | long countChg = 0; | |
647 | Long2ObjectOpenHashMap<Coord> commonCoordMap = new Long2ObjectOpenHashMap<>(); | |
648 | for (ConvertedWay r : roads) { | |
649 | if (!r.isValid()) | |
650 | continue; | |
651 | Way way = r.getWay(); | |
652 | Area searchRect = Area.getBBox(way.getPoints()); | |
653 | Set<Element> boundaries = qt.get(searchRect); | |
654 | if (boundaries.isEmpty()) | |
655 | continue; | |
656 | ||
657 | // the bounding box of the road intersects with one or more bounding boxes of borders | |
658 | Coord pw1 = way.getPoints().get(0); | |
659 | int pos = 1; | |
660 | while (pos < way.getPoints().size()) { | |
661 | boolean changed = false; | |
662 | Coord pw2 = way.getPoints().get(pos); | |
663 | for (Element el : boundaries) { | |
664 | List<Coord> b = ((Way) el).getPoints(); | |
665 | for (int i = 0; i < b.size() - 1; i++) { | |
666 | Coord pb1 = b.get(i); | |
667 | Coord pb2 = b.get(i + 1); | |
668 | Coord is = Utils.getSegmentSegmentIntersection(pw1, pw2, pb1, pb2); | |
669 | if (is != null) { | |
670 | // intersection can be equal to given nodes on road or close to it | |
671 | double dist1 = is.distance(pw1); | |
672 | double dist2 = is.distance(pw2); | |
673 | if (dist1 < dist2 && dist1 < 1) { | |
674 | if (!pw1.getOnCountryBorder()) { | |
675 | ++countChg; | |
676 | if (!pw1.getOnBoundary()) | |
677 | log.info("road intersects admin boundary, changing existing node to external routing node at",pw1.toDegreeString()); | |
678 | } | |
679 | pw1.setOnCountryBorder(true); | |
680 | } else if (dist2 < dist1 && dist2 < 1) { | |
681 | if (!pw2.getOnCountryBorder()) { | |
682 | ++countChg; | |
683 | if (!pw2.getOnBoundary()) | |
684 | log.info("road intersects admin boundary, changing existing node to external routing node at",pw2.toDegreeString()); | |
685 | } | |
686 | pw2.setOnCountryBorder(true); | |
687 | } else { | |
688 | long key = Utils.coord2Long(is); | |
689 | Coord replacement = commonCoordMap.get(key); | |
690 | if (replacement == null) { | |
691 | commonCoordMap.put(key, is); | |
692 | } else { | |
693 | assert is.highPrecEquals(replacement); | |
694 | is = replacement; | |
695 | } | |
696 | is.setOnCountryBorder(true); | |
697 | log.info("road intersects admin boundary, adding external routing node at",is.toDegreeString()); | |
698 | ||
699 | way.getPoints().add(pos, is); | |
700 | changed = true; | |
701 | pw2 = is; | |
702 | } | |
703 | } | |
704 | } | |
705 | } | |
706 | if (!changed) { | |
707 | ++pos; | |
708 | pw1 = pw2; | |
709 | } | |
710 | } | |
711 | } | |
712 | long t2 = System.currentTimeMillis() - t1; | |
713 | log.info("added",commonCoordMap.size(),"new nodes at country borders"); | |
714 | log.info("marked",countChg,"existing nodes at country borders"); | |
715 | log.info("adding country border routing nodes took " + t2 + " ms"); | |
716 | ||
717 | } | |
718 | ||
719 | /** | |
720 | * Split complex border ways into smaller portions. | |
721 | * @param clippedBorders | |
722 | * @param orig | |
723 | * @param points | |
724 | */ | |
725 | private void splitBoundary(List<Element> clippedBorders, Way orig, List<Coord> points) { | |
726 | int pos = 0; | |
727 | final int max = 20; // seems to be a good compromise | |
728 | while (pos < points.size()) { | |
729 | int right = Math.min(points.size(), pos + max); | |
730 | Way w = new Way(orig.getId(), points.subList(pos, right)); | |
731 | w.setFakeId(); | |
732 | clippedBorders.add(w); | |
733 | pos += max - 1; | |
734 | if (pos + 1 == points.size()) | |
735 | pos--; | |
736 | } | |
737 | } | |
738 | ||
739 | ||
740 | /** | |
585 | 741 | * Merges roads with identical attributes (GType, OSM tags) to reduce the size of the |
586 | 742 | * road network. |
587 | 743 | */ |
599 | 755 | pointMap.clear(); |
600 | 756 | style.reportStats(); |
601 | 757 | driveOnLeft = calcDrivingSide(); |
758 | ||
759 | checkRoutingNodesAtAdminBoundaries(); | |
760 | borders.clear(); | |
602 | 761 | |
603 | 762 | setHighwayCounts(); |
604 | 763 | findUnconnectedRoads(); |
635 | 794 | } |
636 | 795 | deletedRoads = null; |
637 | 796 | modifiedRoads = null; |
638 | ||
639 | 797 | mergeRoads(); |
640 | 798 | |
641 | 799 | resetHighwayCounts(); |
960 | 1118 | } |
961 | 1119 | else if("through_route".equals(relation.getTag("type"))) { |
962 | 1120 | throughRouteRelations.add(relation); |
1121 | } else if (addBoundaryNodesAtAdminBoundaries) { | |
1122 | if (relation instanceof MultiPolygonRelation || "boundary".equals(relation.getTag("type"))) { | |
1123 | if (isNod3Border(relation)) { | |
1124 | for (Entry<String, Element> e : relation.getElements()) { | |
1125 | if (FakeIdGenerator.isFakeId(e.getValue().getId())) | |
1126 | continue; | |
1127 | if (e.getValue() instanceof Way) | |
1128 | borders.add((Way) e.getValue()); | |
1129 | } | |
1130 | } | |
1131 | } | |
963 | 1132 | } |
964 | 1133 | } |
965 | 1134 | |
1360 | 1529 | nWay.copyTags(way); |
1361 | 1530 | for(Coord co : lco) { |
1362 | 1531 | nWay.addPoint(co); |
1363 | if(co.getOnBoundary()) { | |
1532 | if(co.getOnBoundary() || co.getOnCountryBorder()) { | |
1364 | 1533 | // this point lies on a boundary |
1365 | 1534 | // make sure it becomes a node |
1366 | 1535 | co.incHighwayCount(); |
1630 | 1799 | arcLength += d; |
1631 | 1800 | } |
1632 | 1801 | } |
1633 | if(p.getHighwayCount() > 1) { | |
1802 | if(p.getHighwayCount() > 1 || p.getOnCountryBorder()) { | |
1634 | 1803 | // this point is a node connecting highways |
1635 | 1804 | CoordNode coordNode = nodeIdMap.get(p); |
1636 | 1805 | if(coordNode == null) { |
1637 | 1806 | // assign a node id |
1638 | coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary()); | |
1807 | coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary(), p.getOnCountryBorder()); | |
1639 | 1808 | nodeIdMap.put(p, coordNode); |
1640 | 1809 | } |
1641 | 1810 | |
1750 | 1919 | Coord coord = points.get(n); |
1751 | 1920 | CoordNode thisCoordNode = nodeIdMap.get(coord); |
1752 | 1921 | assert thisCoordNode != null : "Way " + debugWayName + " node " + i + " (point index " + n + ") at " + coord.toOSMURL() + " yields a null coord node"; |
1753 | boolean boundary = coord.getOnBoundary(); | |
1922 | boolean boundary = coord.getOnBoundary() || coord.getOnCountryBorder(); | |
1754 | 1923 | if(boundary && log.isInfoEnabled()) { |
1755 | 1924 | log.info("Way", debugWayName + "'s point #" + n, "at", coord.toOSMURL(), "is a boundary node"); |
1756 | 1925 | } |
148 | 148 | } |
149 | 149 | replacement.setOnBoundary(true); |
150 | 150 | } |
151 | if (toRepl.getOnCountryBorder()){ | |
152 | if (replacement.equals(toRepl) == false){ | |
153 | log.error("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 | } | |
151 | 158 | toRepl.setReplaced(true); |
152 | 159 | if (toRepl instanceof CoordPOI) { |
153 | 160 | CoordPOI cp = (CoordPOI) toRepl; |
833 | 840 | * @return true if remove is okay |
834 | 841 | */ |
835 | 842 | private boolean allowedToRemove(Coord p){ |
836 | if (p.getOnBoundary()) | |
843 | if (p.getOnBoundary() || p.getOnCountryBorder()) | |
837 | 844 | return false; |
838 | 845 | if (mode == MODE_LINES && p.isEndOfWay()) |
839 | 846 | return false; |
1099 | 1106 | } |
1100 | 1107 | Coord c = getCurrentLocation(replacements); |
1101 | 1108 | Coord n = neighbour.getCurrentLocation(replacements); |
1102 | ||
1103 | 1109 | // check special cases: don't merge if |
1104 | 1110 | // 1) both points are via nodes |
1105 | 1111 | // 2) both nodes are boundary nodes with non-equal coords |
1106 | 1112 | // 3) one point is via node and the other is a boundary node, the result could be that the restriction is ignored. |
1107 | if (c.getOnBoundary()){ | |
1108 | if (n.isViaNodeOfRestriction() || n.getOnBoundary() && c.equals(n) == false) | |
1113 | boolean cOnBoundary = c.getOnBoundary() || c.getOnCountryBorder(); | |
1114 | boolean nOnBoundary = n.getOnBoundary() || n.getOnCountryBorder(); | |
1115 | if (cOnBoundary){ | |
1116 | if (n.isViaNodeOfRestriction() || nOnBoundary && c.equals(n) == false) | |
1109 | 1117 | return false; |
1110 | 1118 | } |
1111 | if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || n.getOnBoundary())) | |
1119 | if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || nOnBoundary)) | |
1112 | 1120 | return false; |
1113 | if (c instanceof CoordPOI && (n instanceof CoordPOI || n.getOnBoundary())) | |
1121 | if (c instanceof CoordPOI && (n instanceof CoordPOI || nOnBoundary)) | |
1114 | 1122 | return false; |
1115 | if (n instanceof CoordPOI && (c instanceof CoordPOI || c.getOnBoundary())) | |
1123 | if (n instanceof CoordPOI && (c instanceof CoordPOI || cOnBoundary)) | |
1116 | 1124 | return false; |
1117 | ||
1125 | //TODO: nodes on country borders? | |
1118 | 1126 | Coord mergePoint; |
1119 | if (c.getOnBoundary() || c instanceof CoordPOI) | |
1127 | if (cOnBoundary || c instanceof CoordPOI) | |
1120 | 1128 | mergePoint = c; |
1121 | else if (n.getOnBoundary() || n instanceof CoordPOI) | |
1129 | else if (nOnBoundary || n instanceof CoordPOI) | |
1122 | 1130 | mergePoint = n; |
1123 | 1131 | else if (c.equals(n)) |
1124 | 1132 | mergePoint = c; |
943 | 943 | Coord closePoint = getRoad().getPoints().get(index); |
944 | 944 | Coord toAdd = new Coord(closePoint); |
945 | 945 | toAdd.setOnBoundary(closePoint.getOnBoundary()); |
946 | toAdd.setOnCountryBorder(closePoint.getOnCountryBorder()); | |
946 | 947 | toAdd.incHighwayCount(); |
947 | 948 | // we have to make sure that the road starts and ends with a CoordNode! |
948 | 949 | this.endInRoad = addAsNumberNode(splitSegment, toAdd); |
151 | 151 | if (log.isDebugEnabled()) |
152 | 152 | log.debug("Create coastline way", id, "with", points.size(), |
153 | 153 | "points"); |
154 | Coord firstPoint = getPoints().get(0); | |
155 | ||
156 | int minLat = firstPoint.getLatitude(); | |
157 | int maxLat = firstPoint.getLatitude(); | |
158 | int minLong = firstPoint.getLongitude(); | |
159 | int maxLong = firstPoint.getLongitude(); | |
160 | ||
161 | for (Coord c : getPoints()) { | |
162 | if (c.getLatitude() < minLat) { | |
163 | minLat = c.getLatitude(); | |
164 | } else if (c.getLatitude() > maxLat) { | |
165 | maxLat = c.getLatitude(); | |
166 | } | |
167 | if (c.getLongitude() < minLong) { | |
168 | minLong = c.getLongitude(); | |
169 | } else if (c.getLongitude() > maxLong) { | |
170 | maxLong = c.getLongitude(); | |
171 | } | |
172 | } | |
173 | bbox = new Area(minLat, minLong, maxLat, maxLong); | |
154 | bbox = Area.getBBox(points); | |
174 | 155 | } |
175 | 156 | |
176 | 157 | @Override |
65 | 65 | // Options |
66 | 66 | private final boolean ignoreTurnRestrictions; |
67 | 67 | |
68 | /** name of the tag that contains a ;-separated list of tagnames that should be removed after all elements have been processed */ | |
69 | public static final String MKGMAP_REMOVE_TAG = "mkgmap:removetags"; | |
70 | /** tagvalue of the {@link ElementSaver#MKGMAP_REMOVE_TAG} if all tags should be removed */ | |
71 | public static final String MKGMAP_REMOVE_TAG_ALL_KEY = "mkgmap:ALL"; | |
68 | /** name of the tag that contains a ;-separated list of tag names that should be removed after all elements have been processed */ | |
69 | public static final short MKGMAP_REMOVE_TAG_KEY = TagDict.getInstance().xlate("mkgmap:removetags"); | |
72 | 70 | |
73 | 71 | public ElementSaver(EnhancedProperties args) { |
74 | 72 | if (args.getProperty("preserve-element-order", false)) { |
33 | 33 | long t1 = System.currentTimeMillis(); |
34 | 34 | log.info("Finishing multipolygons"); |
35 | 35 | for (Way way : saver.getWays().values()) { |
36 | String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG); | |
36 | String removeTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY); | |
37 | 37 | if (removeTag == null) { |
38 | 38 | continue; |
39 | 39 | } |
40 | if (ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY.equals(removeTag)) { | |
41 | if (log.isDebugEnabled()) | |
42 | log.debug("Remove all tags from way",way.getId(),way.toTagString()); | |
43 | way.removeAllTags(); | |
44 | } else { | |
45 | String[] tagsToRemove = removeTag.split(";"); | |
46 | if (log.isDebugEnabled()) | |
47 | log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString()); | |
48 | for (String rTag : tagsToRemove) { | |
49 | way.deleteTag(rTag); | |
50 | } | |
51 | way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG); | |
40 | String[] tagsToRemove = removeTag.split(";"); | |
41 | if (log.isDebugEnabled()) { | |
42 | log.debug("Remove tags",Arrays.toString(tagsToRemove),"from way",way.getId(),way.toTagString()); | |
43 | } | |
44 | way.deleteTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY); | |
45 | for (String rTag : tagsToRemove) { | |
46 | way.deleteTag(rTag); | |
52 | 47 | } |
53 | 48 | } |
54 | 49 | log.info("Multipolygon hook finished in "+(System.currentTimeMillis()-t1)+" ms"); |
17 | 17 | import java.awt.geom.Area; |
18 | 18 | import java.awt.geom.Line2D; |
19 | 19 | import java.util.ArrayList; |
20 | import java.util.Arrays; | |
20 | 21 | import java.util.BitSet; |
21 | 22 | import java.util.Collection; |
22 | 23 | import java.util.Collections; |
36 | 37 | import java.util.concurrent.LinkedBlockingQueue; |
37 | 38 | import java.util.logging.Level; |
38 | 39 | |
39 | import uk.me.parabola.imgfmt.Utils; | |
40 | 40 | import uk.me.parabola.imgfmt.app.Coord; |
41 | 41 | import uk.me.parabola.log.Logger; |
42 | 42 | import uk.me.parabola.util.Java2DConverter; |
1631 | 1631 | || (prevLatField == 0 && prevLonField == 0); |
1632 | 1632 | |
1633 | 1633 | boolean intersects = intersectionPossible |
1634 | && Utils.linesCutEachOther(p1_1, p1_2, p2_1, p2_2); | |
1634 | && linesCutEachOther(p1_1, p1_2, p2_1, p2_2); | |
1635 | 1635 | |
1636 | 1636 | if (intersects) { |
1637 | 1637 | if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext()) |
1724 | 1724 | Coord sw = new Coord(tileBounds.getMinLat(), tileBounds.getMinLong()); |
1725 | 1725 | Coord se = new Coord(tileBounds.getMinLat(), tileBounds.getMaxLong()); |
1726 | 1726 | Coord ne = new Coord(tileBounds.getMaxLat(), tileBounds.getMaxLong()); |
1727 | return Utils.linesCutEachOther(nw, sw, p1_1, p1_2) | |
1728 | || Utils.linesCutEachOther(sw, se, p1_1, p1_2) | |
1729 | || Utils.linesCutEachOther(se, ne, p1_1, p1_2) | |
1730 | || Utils.linesCutEachOther(ne, nw, p1_1, p1_2); | |
1731 | } | |
1732 | ||
1727 | return linesCutEachOther(nw, sw, p1_1, p1_2) | |
1728 | || linesCutEachOther(sw, se, p1_1, p1_2) | |
1729 | || linesCutEachOther(se, ne, p1_1, p1_2) | |
1730 | || linesCutEachOther(ne, nw, p1_1, p1_2); | |
1731 | } | |
1732 | ||
1733 | /** | |
1734 | * XXX: This code presumes that certain tests were already done! | |
1735 | * Check if the line p1_1 to p1_2 cuts line p2_1 to p2_2 in two pieces and vice versa. | |
1736 | * This is a form of intersection check where it is allowed that one line ends on the | |
1737 | * other line or that the two lines overlap. | |
1738 | * @param p1_1 first point of line 1 | |
1739 | * @param p1_2 second point of line 1 | |
1740 | * @param p2_1 first point of line 2 | |
1741 | * @param p2_2 second point of line 2 | |
1742 | * @return true if both lines intersect somewhere in the middle of each other | |
1743 | */ | |
1744 | private static boolean linesCutEachOther(Coord p1_1, Coord p1_2, Coord p2_1, Coord p2_2) { | |
1745 | long width1 = p1_2.getHighPrecLon() - p1_1.getHighPrecLon(); | |
1746 | long width2 = p2_2.getHighPrecLon() - p2_1.getHighPrecLon(); | |
1747 | ||
1748 | long height1 = p1_2.getHighPrecLat() - p1_1.getHighPrecLat(); | |
1749 | long height2 = p2_2.getHighPrecLat() - p2_1.getHighPrecLat(); | |
1750 | ||
1751 | long denominator = ((height2 * width1) - (width2 * height1)); | |
1752 | if (denominator == 0) { | |
1753 | // the lines are parallel | |
1754 | // they might overlap but this is ok for this test | |
1755 | return false; | |
1756 | } | |
1757 | ||
1758 | long x1Mx3 = p1_1.getHighPrecLon() - p2_1.getHighPrecLon(); | |
1759 | long y1My3 = p1_1.getHighPrecLat() - p2_1.getHighPrecLat(); | |
1760 | ||
1761 | double isx = (double)((width2 * y1My3) - (height2 * x1Mx3)) | |
1762 | / denominator; | |
1763 | if (isx <= 0 || isx >= 1) { | |
1764 | return false; | |
1765 | } | |
1766 | ||
1767 | double isy = (double)((width1 * y1My3) - (height1 * x1Mx3)) | |
1768 | / denominator; | |
1769 | ||
1770 | if (isy <= 0 || isy >= 1) { | |
1771 | return false; | |
1772 | } | |
1773 | return true; | |
1774 | } | |
1733 | 1775 | |
1734 | 1776 | private List<JoinedWay> getWaysFromPolygonList(BitSet selection) { |
1735 | 1777 | if (selection.isEmpty()) { |
1872 | 1914 | * @param way |
1873 | 1915 | * a joined way |
1874 | 1916 | * @param tagname |
1875 | * the tag to be removed (<code>null</code> means remove all | |
1876 | * tags) | |
1917 | * the tag to be removed | |
1877 | 1918 | * @param tagvalue |
1878 | * the value of the tag to be removed (<code>null</code> means | |
1879 | * don't check the value) | |
1919 | * the value of the tag to be removed | |
1880 | 1920 | */ |
1881 | 1921 | private void removeTagInOrgWays(JoinedWay way, String tagname, |
1882 | 1922 | String tagvalue) { |
1887 | 1927 | continue; |
1888 | 1928 | } |
1889 | 1929 | |
1890 | boolean remove = false; | |
1891 | if (tagname == null) { | |
1892 | // remove all tags | |
1893 | remove = true; | |
1894 | } else if (tagvalue == null) { | |
1895 | // remove the tag without comparing the value | |
1896 | remove = w.getTag(tagname) != null; | |
1897 | } else if (tagvalue.equals(w.getTag(tagname))) { | |
1898 | remove = true; | |
1899 | } | |
1900 | ||
1901 | if (remove) { | |
1902 | if (tagname == null) { | |
1903 | // remove all tags | |
1904 | if (log.isDebugEnabled()) | |
1905 | log.debug("Will remove all tags from", w.getId(), w | |
1906 | .toTagString()); | |
1907 | removeTagsInOrgWays(w, tagname); | |
1908 | } else { | |
1909 | if (log.isDebugEnabled()) | |
1910 | log.debug("Will remove", tagname + "=" | |
1911 | + w.getTag(tagname), "from way", w.getId(), w | |
1912 | .toTagString()); | |
1913 | removeTagsInOrgWays(w, tagname); | |
1914 | } | |
1930 | if (tagvalue.equals(w.getTag(tagname))) { | |
1931 | if (log.isDebugEnabled()) { | |
1932 | log.debug("Will remove", tagname + "=" + w.getTag(tagname), "from way", w.getId(), w.toTagString()); | |
1933 | } | |
1934 | removeTagsInOrgWays(w, tagname); | |
1915 | 1935 | } |
1916 | 1936 | } |
1917 | 1937 | } |
1918 | 1938 | |
1919 | 1939 | protected void removeTagsInOrgWays(Way way, String tag) { |
1920 | if (tag == null) { | |
1921 | way.addTag(ElementSaver.MKGMAP_REMOVE_TAG, ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY); | |
1940 | if (tag == null || tag.isEmpty()) { | |
1922 | 1941 | return; |
1923 | 1942 | } |
1924 | if (tag.isEmpty()) { | |
1943 | String tagsToRemove = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY); | |
1944 | ||
1945 | if (tagsToRemove == null) { | |
1946 | tagsToRemove = tag; | |
1947 | } else if (tag.equals(tagsToRemove)) { | |
1925 | 1948 | return; |
1926 | } | |
1927 | String removedTagsTag = way.getTag(ElementSaver.MKGMAP_REMOVE_TAG); | |
1928 | if (ElementSaver.MKGMAP_REMOVE_TAG_ALL_KEY.equals(removedTagsTag)) { | |
1929 | // cannot add more tags to remove | |
1930 | return; | |
1931 | } | |
1932 | ||
1933 | if (removedTagsTag == null) { | |
1934 | way.addTag(ElementSaver.MKGMAP_REMOVE_TAG, tag); | |
1935 | } else if (removedTagsTag.equals(tag) == false) { | |
1936 | way.addTag(ElementSaver.MKGMAP_REMOVE_TAG, removedTagsTag+";"+tag); | |
1937 | } | |
1949 | } else { | |
1950 | String[] keys = tagsToRemove.split(";"); | |
1951 | if (Arrays.asList(keys).contains(tag)) { | |
1952 | return; | |
1953 | } | |
1954 | tagsToRemove += ";" + tag; | |
1955 | } | |
1956 | way.addTag(ElementSaver.MKGMAP_REMOVE_TAG_KEY, tagsToRemove); | |
1938 | 1957 | } |
1939 | 1958 | |
1940 | 1959 | /** |
62 | 62 | usedTags.add("route"); |
63 | 63 | usedTags.add("taxi"); |
64 | 64 | } |
65 | int admLevelNod3 = props.getProperty("add-boundary-nodes-at-admin-boundaries", 2); | |
66 | if (admLevelNod3 > 0) { | |
67 | usedTags.add("boundary"); | |
68 | usedTags.add("admin_level"); | |
69 | } | |
70 | ||
65 | 71 | |
66 | 72 | // only enabled if the route option is set |
67 | 73 | return props.containsKey("route"); |
99 | 99 | Rectangle bboxRect = new Rectangle(bbox.getMinLong(), bbox.getMinLat(), bbox.getWidth(), bbox.getHeight()); |
100 | 100 | long ways = saver.getWays().size(); |
101 | 101 | for (Way way : new ArrayList<Way>(saver.getWays().values())) { |
102 | if (way.isViaWay()) | |
103 | continue; | |
102 | 104 | if (way.getPoints().isEmpty()) { |
103 | 105 | // empty way will not appear in the map => remove it |
104 | 106 | saver.getWays().remove(way.getId()); |
119 | 121 | // It is possible that the way is larger than the bounding box and therefore |
120 | 122 | // contains the bbox completely. Especially this is true for the sea polygon |
121 | 123 | // when using --generate-sea=polygon |
122 | // So need the calc the bbox of the way | |
123 | Coord firstC = way.getPoints().get(0); | |
124 | int minLat = firstC.getLatitude(); | |
125 | int maxLat = firstC.getLatitude(); | |
126 | int minLong = firstC.getLongitude(); | |
127 | int maxLong = firstC.getLongitude(); | |
128 | ||
129 | 124 | for (Coord c : way.getPoints()) { |
130 | 125 | if (bbox.contains(c)) { |
131 | 126 | coordInBbox = true; |
143 | 138 | } |
144 | 139 | } |
145 | 140 | |
146 | if (minLat > c.getLatitude()) { | |
147 | minLat = c.getLatitude(); | |
148 | } else if (maxLat < c.getLatitude()) { | |
149 | maxLat = c.getLatitude(); | |
150 | } | |
151 | if (minLong > c.getLongitude()) { | |
152 | minLong = c.getLongitude(); | |
153 | } else if (maxLong < c.getLongitude()) { | |
154 | maxLong = c.getLongitude(); | |
155 | } | |
156 | ||
157 | 141 | prevC = c; |
158 | 142 | } |
159 | 143 | if (coordInBbox==false) { |
160 | 144 | // no coord of the way is within the bounding box |
161 | 145 | // check if the way possibly covers the bounding box completely |
162 | Area wayBbox = new Area(minLat, minLong, maxLat, maxLong); | |
146 | Area wayBbox = Area.getBBox(way.getPoints()); | |
163 | 147 | if (wayBbox.contains(saver.getBoundingBox())) { |
164 | 148 | log.debug(way, "possibly covers the bbox completely. Keep it."); |
165 | 149 | } else { |
149 | 149 | if (id == 0) { |
150 | 150 | CoordNode node = nodeCoords.get((long) ni.nodeId); |
151 | 151 | if (node == null) { |
152 | node = new CoordNode(coord, ni.nodeId, ni.boundary); | |
152 | node = new CoordNode(coord, ni.nodeId, ni.boundary, false); | |
153 | 153 | nodeCoords.put((long) ni.nodeId, node); |
154 | 154 | } |
155 | 155 | points.set(n, node); |
1 | 1 | |
2 | 2 | import java.util.Collection; |
3 | 3 | import java.util.HashSet; |
4 | import java.util.List; | |
4 | import java.util.LinkedHashSet; | |
5 | 5 | import java.util.Set; |
6 | 6 | |
7 | 7 | import uk.me.parabola.imgfmt.app.Area; |
8 | import uk.me.parabola.imgfmt.app.Coord; | |
9 | 8 | import uk.me.parabola.mkgmap.reader.osm.Element; |
10 | import uk.me.parabola.util.ElementQuadTreeNode.ElementQuadTreePolygon; | |
11 | 9 | |
12 | 10 | public class ElementQuadTree { |
13 | 11 | |
17 | 15 | this.root = new ElementQuadTreeNode(bbox, elements); |
18 | 16 | } |
19 | 17 | |
20 | public void remove(Element element) { | |
21 | root.remove(element); | |
22 | } | |
23 | ||
24 | 18 | public Set<Element> get(Area bbox) { |
25 | return root.get(bbox, new HashSet<Element>()); | |
26 | } | |
27 | ||
28 | public Set<Element> get(java.awt.geom.Area polygon) { | |
29 | return root.get(new ElementQuadTreePolygon(polygon), new HashSet<Element>()); | |
30 | } | |
31 | ||
32 | public Set<Element> get(Collection<List<Coord>> polygons) { | |
33 | return root.get(new ElementQuadTreePolygon(polygons), | |
34 | new HashSet<Element>()); | |
19 | HashSet<Element> res = new LinkedHashSet<>(); | |
20 | root.get(bbox, res); | |
21 | return res; | |
35 | 22 | } |
36 | 23 | |
37 | 24 | public int getDepth() { |
38 | 25 | return root.getDepth(); |
39 | } | |
40 | ||
41 | public Set<Element> get(List<Coord> polygon) { | |
42 | if (polygon.size() < 3) { | |
43 | return new HashSet<Element>(); | |
44 | } | |
45 | if (polygon.get(0).equals(polygon.get(polygon.size() - 1)) == false) { | |
46 | return new HashSet<Element>(); | |
47 | } | |
48 | return root.get(new ElementQuadTreePolygon(polygon), | |
49 | new HashSet<Element>()); | |
50 | } | |
51 | ||
52 | public long getCoordSize() { | |
53 | return root.getSize(); | |
54 | 26 | } |
55 | 27 | |
56 | 28 | public boolean isEmpty() { |
13 | 13 | |
14 | 14 | import java.awt.Rectangle; |
15 | 15 | import java.util.ArrayList; |
16 | import java.util.Arrays; | |
16 | 17 | import java.util.Collection; |
17 | import java.util.Collections; | |
18 | import java.util.HashMap; | |
18 | import java.util.Iterator; | |
19 | 19 | import java.util.List; |
20 | import java.util.Map; | |
21 | import java.util.Map.Entry; | |
22 | 20 | import java.util.Set; |
23 | 21 | |
24 | 22 | import uk.me.parabola.imgfmt.app.Area; |
25 | import uk.me.parabola.imgfmt.app.Coord; | |
26 | 23 | import uk.me.parabola.log.Logger; |
27 | 24 | import uk.me.parabola.mkgmap.reader.osm.Element; |
28 | 25 | import uk.me.parabola.mkgmap.reader.osm.Node; |
29 | 26 | import uk.me.parabola.mkgmap.reader.osm.Way; |
30 | 27 | |
28 | /** | |
29 | * A quad tree node. All nodes in the tree can contain elements. The element is stored in the node when its bbox is too large to fit into a single child. | |
30 | * @author Gerd Petermann | |
31 | * | |
32 | */ | |
31 | 33 | public final class ElementQuadTreeNode { |
32 | 34 | |
33 | 35 | private static final Logger log = Logger.getLogger(ElementQuadTreeNode.class); |
34 | 36 | |
35 | /** | |
36 | * A static empty list used for node objects. They have one coord only and | |
37 | * it is too costly to create a list for each node | |
38 | */ | |
39 | private static final List<Coord> EMPTY_LIST = Collections.emptyList(); | |
40 | ||
41 | 37 | /** The maximum number of coords in the quadtree node. */ |
42 | 38 | private static final int MAX_POINTS = 1000; |
43 | 39 | |
44 | /** Maps elements to its coords located in this quadtree node. */ | |
45 | private Map<Element, List<Coord>> elementMap; | |
40 | private List<BoxedElement> elementList; // can be null | |
46 | 41 | |
47 | 42 | /** The bounds of this quadtree node */ |
48 | 43 | private final Area bounds; |
49 | 44 | private final Rectangle boundsRect; |
50 | 45 | |
51 | /** Flag if this node and all subnodes are empty */ | |
46 | /** Flag if this node and all sub-nodes are empty */ | |
52 | 47 | private Boolean empty; |
53 | 48 | |
54 | /** The subnodes in case this node is not a leaf */ | |
49 | /** The sub-nodes in case this node is not a leaf */ | |
55 | 50 | private ElementQuadTreeNode[] children; |
56 | 51 | |
57 | public static final class ElementQuadTreePolygon { | |
58 | private final java.awt.geom.Area javaArea; | |
59 | private final Area bbox; | |
60 | ||
61 | public ElementQuadTreePolygon(java.awt.geom.Area javaArea) { | |
62 | this.javaArea = javaArea; | |
63 | Rectangle bboxRect = javaArea.getBounds(); | |
64 | bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y | |
65 | + bboxRect.height, bboxRect.x + bboxRect.width); | |
66 | } | |
67 | ||
68 | public ElementQuadTreePolygon(List<Coord> points) { | |
69 | this(Java2DConverter.createArea(points)); | |
70 | } | |
71 | ||
72 | public ElementQuadTreePolygon(Collection<List<Coord>> polygonList) { | |
73 | this.javaArea = new java.awt.geom.Area(); | |
74 | for (List<Coord> polygon : polygonList) { | |
75 | javaArea.add(Java2DConverter.createArea(polygon)); | |
76 | } | |
77 | Rectangle bboxRect = javaArea.getBounds(); | |
78 | bbox = new Area(bboxRect.y, bboxRect.x, bboxRect.y | |
79 | + bboxRect.height, bboxRect.x + bboxRect.width); | |
80 | } | |
81 | ||
82 | public Area getBbox() { | |
83 | return bbox; | |
84 | } | |
85 | ||
86 | public java.awt.geom.Area getArea() { | |
87 | return javaArea; | |
88 | } | |
89 | } | |
90 | ||
91 | /** | |
92 | * Retrieves if this quadtree node (and all subnodes) contains any elements. | |
52 | /** | |
53 | * Retrieves if this quadtree node (and all sub-nodes) contains any elements. | |
93 | 54 | * @return <code>true</code> this quadtree node does not contain any elements; <code>false</code> else |
94 | 55 | */ |
95 | 56 | public boolean isEmpty() { |
96 | 57 | if (empty == null) { |
97 | if (isLeaf()) { | |
98 | empty = elementMap.isEmpty(); | |
58 | if (elementList != null && !elementList.isEmpty()) { | |
59 | empty = false; | |
99 | 60 | } else { |
100 | 61 | empty = true; |
101 | 62 | for (ElementQuadTreeNode child : children) { |
102 | if (child.isEmpty()==false) { | |
103 | empty = false; | |
104 | break; | |
105 | } | |
63 | if (!child.isEmpty()) { | |
64 | empty = false; | |
65 | break; | |
106 | 66 | } |
67 | } | |
107 | 68 | } |
108 | 69 | } |
109 | 70 | return empty; |
111 | 72 | |
112 | 73 | |
113 | 74 | /** |
114 | * Retrieves the number of coords hold by this quadtree node and all subnodes. | |
75 | * Retrieves the number of coords hold by this quadtree node and all sub-nodes. | |
115 | 76 | * @return the number of coords |
116 | 77 | */ |
117 | public long getSize() { | |
118 | if (isLeaf()) { | |
119 | int items = 0; | |
120 | for (List<Coord> points : elementMap.values()) { | |
121 | if (points == EMPTY_LIST) { | |
122 | items++; | |
123 | } else { | |
124 | items += points.size(); | |
125 | } | |
126 | } | |
127 | return items; | |
128 | } else { | |
129 | int items = 0; | |
78 | private long getSize() { | |
79 | int items = 0; | |
80 | for (BoxedElement bel : elementList) { | |
81 | items += bel.getSize(); | |
82 | } | |
83 | if (children != null) { | |
130 | 84 | for (ElementQuadTreeNode child : children) { |
131 | items += child.getSize(); | |
132 | } | |
133 | return items; | |
134 | } | |
85 | items += child.getSize(); | |
86 | } | |
87 | } | |
88 | return items; | |
135 | 89 | } |
136 | 90 | |
137 | 91 | /** |
139 | 93 | * @return the depth of this quadtree node |
140 | 94 | */ |
141 | 95 | public int getDepth() { |
142 | if (isLeaf()) { | |
96 | if (children == null) | |
143 | 97 | return 1; |
144 | } else { | |
145 | int maxDepth = 0; | |
146 | for (ElementQuadTreeNode node : children) { | |
147 | maxDepth = Math.max(node.getDepth(), maxDepth); | |
148 | } | |
149 | return maxDepth + 1; | |
150 | } | |
151 | } | |
152 | ||
153 | private ElementQuadTreeNode(Area bounds, Map<Element, List<Coord>> elements) { | |
98 | int maxDepth = 0; | |
99 | for (ElementQuadTreeNode node : children) { | |
100 | maxDepth = Math.max(node.getDepth(), maxDepth); | |
101 | } | |
102 | return maxDepth + 1; | |
103 | } | |
104 | ||
105 | private ElementQuadTreeNode(Area bounds, List<BoxedElement> elements) { | |
154 | 106 | this.bounds = bounds; |
155 | 107 | boundsRect = new Rectangle(bounds.getMinLong(), bounds.getMinLat(), |
156 | 108 | bounds.getWidth(), bounds.getHeight()); |
157 | 109 | this.children = null; |
158 | elementMap =elements; | |
159 | empty = elementMap.isEmpty(); | |
110 | elementList = elements; | |
111 | empty = elementList.isEmpty(); | |
160 | 112 | |
161 | 113 | checkSplit(); |
162 | 114 | } |
168 | 120 | bounds.getWidth(), bounds.getHeight()); |
169 | 121 | this.children = null; |
170 | 122 | |
171 | this.elementMap = new HashMap<Element, List<Coord>>(elements.size()*4/3+10); | |
123 | this.elementList = new ArrayList<>(); | |
172 | 124 | |
173 | 125 | for (Element el : elements) { |
174 | if (el instanceof Way) { | |
175 | List<Coord> points = ((Way) el).getPoints(); | |
176 | // no need to create a copy of the points because the list is never changed | |
177 | elementMap.put(el, points); | |
178 | } else if (el instanceof Node) { | |
179 | elementMap.put(el, EMPTY_LIST); | |
180 | } | |
181 | } | |
182 | empty = elementMap.isEmpty(); | |
126 | BoxedElement bel = new BoxedElement(el); | |
127 | elementList.add(bel); | |
128 | } | |
129 | empty = elementList.isEmpty(); | |
183 | 130 | checkSplit(); |
184 | 131 | } |
185 | 132 | |
195 | 142 | * Checks if this quadtree node exceeds the maximum size and splits it in such a case. |
196 | 143 | */ |
197 | 144 | private void checkSplit() { |
198 | if (getSize() > MAX_POINTS) { | |
145 | if (elementList != null && elementList.size() > 1 && getSize() > MAX_POINTS) { | |
199 | 146 | split(); |
200 | 147 | } |
201 | } | |
202 | ||
203 | /** | |
204 | * Removes the element with the given bounding box from this quadtree node and all subnodes. | |
205 | * @param elem the element to be removed | |
206 | * @param bbox the bounding box of the element | |
207 | */ | |
208 | private void remove(Element elem, Area bbox) { | |
209 | if (bbox == null || isEmpty()) { | |
210 | return; | |
211 | } | |
212 | if (isLeaf()) { | |
213 | elementMap.remove(elem); | |
214 | empty = elementMap.isEmpty(); | |
215 | } else { | |
216 | for (ElementQuadTreeNode child : children) { | |
217 | if (child.getBounds().intersects(bbox)) { | |
218 | child.remove(elem, bbox); | |
219 | if (child.isEmpty()) { | |
220 | // update the empty flag | |
221 | empty = null; | |
222 | } | |
223 | } | |
224 | } | |
225 | } | |
226 | } | |
227 | ||
228 | /** | |
229 | * Calculates the bounding box of the given element. | |
230 | * @param elem an element | |
231 | * @return the bounding box of the element | |
232 | */ | |
233 | private Area getBbox(Element elem) { | |
234 | if (elem instanceof Node) { | |
235 | Coord c = ((Node) elem).getLocation(); | |
236 | return new Area(c.getLatitude(), c.getLongitude(), c.getLatitude(),c.getLongitude()); | |
237 | } else if (elem instanceof Way) { | |
238 | List<Coord> points = ((Way) elem).getPoints(); | |
239 | if (points.isEmpty()) { | |
240 | return null; | |
241 | } | |
242 | Coord c = points.get(0); | |
243 | int minLat = c.getLatitude(); | |
244 | int maxLat = c.getLatitude(); | |
245 | int minLong = c.getLongitude(); | |
246 | int maxLong = c.getLongitude(); | |
247 | for (Coord co : points) { | |
248 | if (co.getLatitude() < minLat) { | |
249 | minLat = co.getLatitude(); | |
250 | } else if (co.getLatitude() > maxLat) { | |
251 | maxLat = co.getLatitude(); | |
252 | } | |
253 | if (co.getLongitude() < minLong) { | |
254 | minLong = co.getLongitude(); | |
255 | } else if (co.getLongitude() > maxLong) { | |
256 | maxLong = co.getLongitude(); | |
257 | } | |
258 | } | |
259 | return new Area(minLat,minLong, maxLat, maxLong); | |
260 | } | |
261 | return null; | |
262 | } | |
263 | ||
264 | /** | |
265 | * Removes the element from this quadtree node and all subnodes. | |
266 | * @param elem the element to be removed | |
267 | */ | |
268 | public void remove(Element elem) { | |
269 | remove(elem, getBbox(elem)); | |
270 | 148 | } |
271 | 149 | |
272 | 150 | /** |
273 | 151 | * Retrieves all elements that intersects the given bounding box. |
274 | 152 | * @param bbox the bounding box |
275 | * @param resultList results are stored in this collection | |
153 | * @param resultSet results are stored in this collection | |
276 | 154 | * @return the resultList |
277 | 155 | */ |
278 | public Set<Element> get(Area bbox, Set<Element> resultList) { | |
279 | if (isEmpty()) { | |
280 | return resultList; | |
281 | } | |
282 | if (isLeaf()) { | |
283 | if (bbox.getMinLat() <= bounds.getMinLat() | |
284 | && bbox.getMaxLat() >= bounds.getMaxLat() | |
285 | && bbox.getMinLong() <= bounds.getMinLong() | |
286 | && bbox.getMaxLong() >= bounds.getMaxLong()) { | |
287 | ||
288 | // the bounding box is contained completely in the bbox | |
289 | // => add all points without further check | |
290 | resultList.addAll(elementMap.keySet()); | |
291 | } else { | |
292 | // check each point | |
293 | for (Entry<Element, List<Coord>> elem : elementMap.entrySet()) { | |
294 | if (elem.getKey() instanceof Node) { | |
295 | Node n = (Node) elem.getKey(); | |
296 | if (bbox.contains(n.getLocation())) { | |
297 | resultList.add(n); | |
298 | } | |
299 | } else if (elem.getKey() instanceof Way) { | |
300 | // no need to check - the element is already in the result list | |
301 | if (resultList.contains(elem.getKey())) { | |
302 | continue; | |
303 | } | |
304 | for (Coord c : elem.getValue()) { | |
305 | if (bbox.contains(c)) { | |
306 | resultList.add(elem.getKey()); | |
307 | break; | |
308 | } | |
309 | } | |
310 | } | |
311 | } | |
312 | } | |
313 | } else { | |
156 | public void get(Area bbox, Set<Element> resultSet) { | |
157 | if (isEmpty() || bbox.intersects(bounds) == false) { | |
158 | return; | |
159 | } | |
160 | if (elementList != null) { | |
161 | for (BoxedElement bel : elementList) { | |
162 | if (bbox.intersects(bel.getBBox())) | |
163 | resultSet.add(bel.el); | |
164 | } | |
165 | } | |
166 | if (children != null) { | |
314 | 167 | for (ElementQuadTreeNode child : children) { |
315 | if (child.isEmpty() == false | |
316 | && bbox.intersects(child.getBounds())) { | |
317 | resultList = child.get(bbox, resultList); | |
318 | } | |
319 | } | |
320 | } | |
321 | return resultList; | |
322 | } | |
323 | ||
324 | /** | |
325 | * Retrieves all elements that intersects the given polygon. | |
326 | * @param polygon the polygon | |
327 | * @param resultList results are stored in this collection | |
328 | * @return the resultList | |
329 | */ | |
330 | public Set<Element> get(ElementQuadTreePolygon polygon, | |
331 | Set<Element> resultList) { | |
332 | if (isEmpty()) { | |
333 | return resultList; | |
334 | } | |
335 | if (polygon.getBbox().intersects(getBounds())) { | |
336 | if (isLeaf()) { | |
337 | for (Entry<Element, List<Coord>> elem : elementMap.entrySet()) { | |
338 | if (resultList.contains(elem.getKey())) { | |
339 | continue; | |
340 | } | |
341 | if (elem.getKey() instanceof Node) { | |
342 | Node n = (Node)elem.getKey(); | |
343 | Coord c = n.getLocation(); | |
344 | if (polygon.getArea().contains(c.getLongitude(), | |
345 | c.getLatitude())) { | |
346 | resultList.add(n); | |
347 | } | |
348 | } else if (elem.getKey() instanceof Way) { | |
349 | for (Coord c : elem.getValue()) { | |
350 | if (polygon.getArea().contains(c.getLongitude(), | |
351 | c.getLatitude())) { | |
352 | resultList.add(elem.getKey()); | |
353 | break; | |
354 | } | |
355 | } | |
356 | } | |
357 | } | |
358 | } else { | |
359 | for (ElementQuadTreeNode child : children) { | |
360 | if (child.isEmpty()==false | |
361 | && polygon.getArea().intersects( | |
362 | child.getBoundsAsRectangle())) { | |
363 | java.awt.geom.Area subArea = (java.awt.geom.Area) polygon | |
364 | .getArea().clone(); | |
365 | ||
366 | subArea.intersect(Java2DConverter.createBoundsArea(new Area(child.getBounds() | |
367 | .getMinLat() - 1, child.getBounds() | |
368 | .getMinLong() - 1, child.getBounds() | |
369 | .getMaxLat() + 1, child.getBounds() | |
370 | .getMaxLong() + 1)) | |
371 | ); | |
372 | child.get(new ElementQuadTreePolygon(subArea), | |
373 | resultList); | |
374 | } | |
375 | } | |
376 | } | |
377 | } | |
378 | return resultList; | |
379 | ||
380 | } | |
381 | ||
382 | /** | |
383 | * Retrieves if this quadtree node is a leaf. | |
384 | * @return <code>true</code> this node is a leaf | |
385 | */ | |
386 | public boolean isLeaf() { | |
387 | return elementMap != null; | |
168 | child.get(bbox, resultSet); | |
169 | } | |
170 | } | |
388 | 171 | } |
389 | 172 | |
390 | 173 | /** |
398 | 181 | |
399 | 182 | int halfLat = (bounds.getMinLat() + bounds.getMaxLat()) / 2; |
400 | 183 | int halfLong = (bounds.getMinLong() + bounds.getMaxLong()) / 2; |
401 | children = new ElementQuadTreeNode[4]; | |
402 | 184 | Area[] childBounds = new Area[4]; |
403 | 185 | |
404 | 186 | childBounds[0] = new Area(bounds.getMinLat(), bounds.getMinLong(), |
410 | 192 | childBounds[3] = new Area(halfLat, halfLong, bounds.getMaxLat(), |
411 | 193 | bounds.getMaxLong()); |
412 | 194 | |
413 | List<Map<Element, List<Coord>>> childElems = new ArrayList<Map<Element, List<Coord>>>(4); | |
195 | List<List<BoxedElement>> childElems = new ArrayList<>(); | |
414 | 196 | for (int i = 0; i < 4; i++) { |
415 | childElems.add(new HashMap<Element, List<Coord>>()); | |
416 | } | |
417 | for (Entry<Element,List<Coord>> elem : elementMap.entrySet()) { | |
418 | if (elem.getKey() instanceof Node) { | |
419 | Node node = (Node) elem.getKey(); | |
420 | for (int i = 0; i < childBounds.length; i++) { | |
421 | if (childBounds[i].contains(node.getLocation())) { | |
422 | childElems.get(i).put(node, EMPTY_LIST); | |
197 | childElems.add(new ArrayList<>()); | |
198 | } | |
199 | boolean modified = false; | |
200 | Iterator<BoxedElement> iter = elementList.iterator(); | |
201 | while (iter.hasNext()) { | |
202 | BoxedElement bel = iter.next(); | |
203 | int count = 0; | |
204 | int lastIndex = -1; | |
205 | for (int i = 0; i < 4; i++) { | |
206 | if (childBounds[i].intersects(bel.getBBox())) { | |
207 | count++; | |
208 | lastIndex = i; | |
209 | if (childBounds[i].insideBoundary(bel.getBBox())) { | |
423 | 210 | break; |
424 | 211 | } |
425 | 212 | } |
426 | } else if (elem.getKey() instanceof Way) { | |
427 | List<List<Coord>> points = new ArrayList<List<Coord>>(4); | |
428 | for (int i = 0; i < 4; i++) { | |
429 | // usually ways are quite local | |
430 | // therefore there is a high probability that only one child is covered | |
431 | // dim the new list as the old list | |
432 | points.add(new ArrayList<Coord>(elem.getValue().size())); | |
433 | } | |
434 | for (Coord c : elem.getValue()) { | |
435 | for (int i = 0; i < childBounds.length; i++) { | |
436 | if (childBounds[i].contains(c)) { | |
437 | points.get(i).add(c); | |
438 | break; | |
439 | } | |
440 | } | |
441 | } | |
442 | for (int i = 0; i< 4; i++) { | |
443 | if (points.get(i).isEmpty()==false) { | |
444 | childElems.get(i).put(elem.getKey(), points.get(i)); | |
445 | } | |
446 | } | |
447 | } | |
448 | } | |
449 | ||
450 | for (int i = 0; i < 4; i++) { | |
451 | children[i] = new ElementQuadTreeNode(childBounds[i], childElems.get(i)); | |
452 | } | |
453 | ||
454 | elementMap = null; | |
213 | } | |
214 | if (count == 1) { | |
215 | childElems.get(lastIndex).add(bel); | |
216 | iter.remove(); | |
217 | modified = true; | |
218 | } | |
219 | } | |
220 | if (modified) { | |
221 | children = new ElementQuadTreeNode[4]; | |
222 | for (int i = 0; i < 4; i++) { | |
223 | children[i] = new ElementQuadTreeNode(childBounds[i], childElems.get(i)); | |
224 | } | |
225 | } | |
226 | if (elementList.isEmpty()) | |
227 | elementList = null; | |
455 | 228 | } |
456 | 229 | |
457 | 230 | public void clear() { |
458 | 231 | this.children = null; |
459 | elementMap = new HashMap<Element, List<Coord>>(); | |
232 | elementList = null; | |
233 | } | |
234 | ||
235 | private class BoxedElement{ | |
236 | private Area bbox; | |
237 | private final Element el; | |
238 | ||
239 | Area getBBox() { | |
240 | if (el instanceof Way) { | |
241 | if (bbox == null) | |
242 | bbox = Area.getBBox(((Way) el).getPoints()); | |
243 | return bbox; | |
244 | } | |
245 | return Area.getBBox(Arrays.asList(((Node) el).getLocation())); | |
246 | } | |
247 | ||
248 | BoxedElement(Element el) { | |
249 | this.el = el; | |
250 | } | |
251 | ||
252 | int getSize() { | |
253 | if (el instanceof Way) | |
254 | return ((Way) el).getPoints().size(); | |
255 | return 1; | |
256 | } | |
460 | 257 | } |
461 | 258 | } |