Codebase list mkgmap / 5ad77db
New upstream version 0.0.0+svn4223 Bas Couwenberg 5 years ago
24 changed file(s) with 549 addition(s) and 556 deletion(s). Raw diff Collapse all Expand all
533533 that support routing. If you specify this option, you do not need
534534 to specify --net.
535535 <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>
536554 ;--drive-on=left|right|detect|detect,left|detect,right
537555 : Explicitly specify which side of the road vehicles are
538556 expected to drive on.
538538 of drive-on-left roads with the rest.
539539 Use the --bounds option to make sure that the detection
540540 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.
541559
542560 --check-roundabouts
543561 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
1818 mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level4=Hamburg {set mkgmap:city='${mkgmap:admin_level4}' }
1919 mkgmap:country=DEU & mkgmap:city!=* & mkgmap:admin_level4=Berlin {set mkgmap:city='${mkgmap:admin_level4}' }
2020 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 }' }
2626
2727
2828 # Austria = AUT
1515 */
1616 package uk.me.parabola.imgfmt;
1717
18 import java.awt.geom.Line2D;
1819 import java.io.Closeable;
1920 import java.io.File;
2021 import java.io.FileInputStream;
2728 import java.util.Date;
2829 import java.util.zip.GZIPInputStream;
2930
31 import uk.me.parabola.imgfmt.app.Coord;
3032 import uk.me.parabola.imgfmt.app.ImgFileWriter;
31 import uk.me.parabola.imgfmt.app.Coord;
3233 /**
3334 * Some miscellaneous functions that are used within the .img code.
3435 *
388389 return (long)(latHp & 0xffffffffL) << 32 | (lonHp & 0xffffffffL);
389390 }
390391
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
434392 public static int numberToPointerSize(int n) {
435393 // moved from imgfmt/app/mdr/MdrSection.java and app/typ/TYPFile.java
436394 if (n <= 0xff)
451409 writer.put3s(longitude);
452410 }
453411
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
454447 }
448
6868 , Utils.toMapUnit(maxLat), Utils.toMapUnit(maxLong));
6969 }
7070
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
7193 public int getMinLat() {
7294 return minLat;
7395 }
4949 private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer
5050 private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval
5151 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
5253
5354 private final static int HIGH_PREC_BITS = 30;
5455 public final static int DELTA_SHIFT = HIGH_PREC_BITS - 24;
339340 }
340341
341342 /**
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
343344 */
344345 public boolean isNumberNode(){
345346 return (flags & HOUSENUMBER_NODE) != 0;
356357 }
357358
358359 /**
359 * @return if this is the beginning/end of a house number interval
360 * @return true if this was added by the housenumber processing
360361 */
361362 public boolean isAddedNumberNode(){
362363 return (flags & ADDED_HOUSENUMBER_NODE) != 0;
370371 this.flags |= ADDED_HOUSENUMBER_NODE;
371372 else
372373 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;
373392 }
374393
375394 public int hashCode() {
681700 * @return true if rounding error is large.
682701 */
683702 public boolean hasAlternativePos(){
684 if (getOnBoundary())
703 if (getOnBoundary() || getOnCountryBorder())
685704 return false;
686705 return (Math.abs(latDelta) > MAX_DELTA || Math.abs(lonDelta) > MAX_DELTA);
687706 }
693712 */
694713 public List<Coord> getAlternativePositions(){
695714 ArrayList<Coord> list = new ArrayList<>();
696 if (getOnBoundary())
715 if (getOnBoundary() || getOnCountryBorder())
697716 return list;
698717 int modLatDelta = 0;
699718 int modLonDelta = 0;
3131 * @param longitude The longitude in map units.
3232 * @param id The ID of this routing node.
3333 * @param boundary This is a routing node on the boundary.
34 * @param onCountryBorder This is a routing node on a country boundary.
3435 */
35 public CoordNode(int latitude, int longitude, int id, boolean boundary) {
36 public CoordNode(int latitude, int longitude, int id, boolean boundary, boolean onCountryBorder) {
3637 super(latitude, longitude);
3738 this.id = id;
3839 setOnBoundary(boundary);
40 setOnCountryBorder(onCountryBorder);
3941 setNumberNode(true);
4042 preserved(true);
4143 }
4244
43 public CoordNode(Coord other, int id, boolean boundary){
45 public CoordNode(Coord other, int id, boolean boundary, boolean onCountryBorder) {
4446 super(other);
4547 this.id = id;
4648 setOnBoundary(boundary);
49 setOnCountryBorder(onCountryBorder);
4750 setNumberNode(true);
4851 preserved(true);
4952
7575
7676 public RouteNode(Coord coord) {
7777 this.coord = (CoordNode) coord;
78 setBoundary(this.coord.getOnBoundary());
78 setBoundary(this.coord.getOnBoundary() || this.coord.getOnCountryBorder());
7979 }
8080
8181 private boolean haveLargeOffsets() {
402402 Utils.toDegrees(currLon)));
403403
404404 if (extra)
405 line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false));
405 line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false, false));
406406 else
407407 line.addCoord(new Coord(currLat, currLon));
408408
477477 currLon += dx << (24 - div.getResolution());
478478 Coord coord;
479479 if (isnode)
480 coord = new CoordNode(currLat, currLon, 0/* XXX */, false);
480 coord = new CoordNode(currLat, currLon, 0/* XXX */, false, false);
481481 else
482482 coord = new Coord(currLat, currLon);
483483
6767 int endIndex = coords.size()-1;
6868 for(int i = endIndex-1; i > 0; i--) {
6969 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.
7470 if (p.preserved()) {
7571 // point is "preserved", don't remove it
7672 douglasPeucker(coords, i, endIndex, maxErrorDistance);
9995 */
10096 public static void douglasPeucker(List<Coord> points, int startIndex, int endIndex, double allowedError)
10197 {
102 if (startIndex >= endIndex)
98 if (endIndex - startIndex <= 1) {
10399 return;
104
100 }
101
105102 double maxDistance = 0; //Highest distance
106103 int maxIndex = endIndex; //Index of highest distance
107104
108105 Coord a = points.get(startIndex);
109106 Coord b = points.get(endIndex);
110
111107
112108 // 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
114109 for(int i = endIndex-1; i > startIndex; i--) {
115110 Coord p = points.get(i);
116111 double distance = p.shortestDistToLineSegment(a, b);
126121 }
127122 else {
128123 // 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) {
134125 // 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 }
142131 }
143132 }
144133 }
5454 Coord newP;
5555
5656 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());
5858 else
5959 newP = new Coord(lat, lon);
6060 newP.preserved(p.preserved());
2424 import java.util.IdentityHashMap;
2525 import java.util.Iterator;
2626 import java.util.LinkedHashMap;
27 import java.util.LinkedHashSet;
2728 import java.util.List;
2829 import java.util.Map;
30 import java.util.Set;
2931 import java.util.Map.Entry;
3032 import java.util.logging.Level;
3133
34 import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
3235 import uk.me.parabola.imgfmt.ExitException;
36 import uk.me.parabola.imgfmt.Utils;
3337 import uk.me.parabola.imgfmt.app.Area;
3438 import uk.me.parabola.imgfmt.app.Coord;
3539 import uk.me.parabola.imgfmt.app.CoordNode;
5963 import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
6064 import uk.me.parabola.mkgmap.reader.osm.FeatureKind;
6165 import uk.me.parabola.mkgmap.reader.osm.GType;
66 import uk.me.parabola.mkgmap.reader.osm.MultiPolygonRelation;
6267 import uk.me.parabola.mkgmap.reader.osm.Node;
6368 import uk.me.parabola.mkgmap.reader.osm.OsmConverter;
6469 import uk.me.parabola.mkgmap.reader.osm.Relation;
6974 import uk.me.parabola.mkgmap.reader.osm.Tags;
7075 import uk.me.parabola.mkgmap.reader.osm.TypeResult;
7176 import uk.me.parabola.mkgmap.reader.osm.Way;
77 import uk.me.parabola.util.ElementQuadTree;
7278 import uk.me.parabola.util.EnhancedProperties;
7379 import uk.me.parabola.util.MultiHashMap;
7480
111117
112118 public final static String WAY_POI_NODE_IDS = "mkgmap:way-poi-node-ids";
113119 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
115127 private List<ConvertedWay> roads = new ArrayList<>();
116128 private List<ConvertedWay> lines = new ArrayList<>();
117129 private HashMap<Long, ConvertedWay> modifiedRoads = new HashMap<>();
209221 String styleOption= props.getProperty("style-option",null);
210222 styleOptionTags = parseStyleOption(styleOption);
211223 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;
212228 }
213229
214230 /**
307323 removeRestrictionsWithWay(Level.WARNING, way, "is ignored");
308324 return;
309325 }
326 if (addBoundaryNodesAtAdminBoundaries) {
327 // is this a country border ?
328 if (!FakeIdGenerator.isFakeId(way.getId()) && isNod3Border(way)) {
329 borders.add(way);
330 }
331 }
332
310333 preConvertRules(way);
311334
312335 String styleFilterTag = way.getTag(styleFilterTagKey);
371394 }
372395 }
373396
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
374411 private int lineIndex = 0;
375412 private final static short onewayTagKey = TagDict.getInstance().xlate("oneway");
376413 private void addConvertedWay(Way way, GType foundType) {
430467
431468 /** One type result for nodes to avoid recreating one for each node. */
432469 private NodeTypeResult nodeTypeResult = new NodeTypeResult();
470
433471 private class NodeTypeResult implements TypeResult {
434472 private Node node;
435473 /** flag if the rule was fired */
582620 }
583621
584622 /**
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 /**
585741 * Merges roads with identical attributes (GType, OSM tags) to reduce the size of the
586742 * road network.
587743 */
599755 pointMap.clear();
600756 style.reportStats();
601757 driveOnLeft = calcDrivingSide();
758
759 checkRoutingNodesAtAdminBoundaries();
760 borders.clear();
602761
603762 setHighwayCounts();
604763 findUnconnectedRoads();
635794 }
636795 deletedRoads = null;
637796 modifiedRoads = null;
638
639797 mergeRoads();
640798
641799 resetHighwayCounts();
9601118 }
9611119 else if("through_route".equals(relation.getTag("type"))) {
9621120 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 }
9631132 }
9641133 }
9651134
13601529 nWay.copyTags(way);
13611530 for(Coord co : lco) {
13621531 nWay.addPoint(co);
1363 if(co.getOnBoundary()) {
1532 if(co.getOnBoundary() || co.getOnCountryBorder()) {
13641533 // this point lies on a boundary
13651534 // make sure it becomes a node
13661535 co.incHighwayCount();
16301799 arcLength += d;
16311800 }
16321801 }
1633 if(p.getHighwayCount() > 1) {
1802 if(p.getHighwayCount() > 1 || p.getOnCountryBorder()) {
16341803 // this point is a node connecting highways
16351804 CoordNode coordNode = nodeIdMap.get(p);
16361805 if(coordNode == null) {
16371806 // assign a node id
1638 coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary());
1807 coordNode = new CoordNode(p, nextNodeId++, p.getOnBoundary(), p.getOnCountryBorder());
16391808 nodeIdMap.put(p, coordNode);
16401809 }
16411810
17501919 Coord coord = points.get(n);
17511920 CoordNode thisCoordNode = nodeIdMap.get(coord);
17521921 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();
17541923 if(boundary && log.isInfoEnabled()) {
17551924 log.info("Way", debugWayName + "'s point #" + n, "at", coord.toOSMURL(), "is a boundary node");
17561925 }
148148 }
149149 replacement.setOnBoundary(true);
150150 }
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 }
151158 toRepl.setReplaced(true);
152159 if (toRepl instanceof CoordPOI) {
153160 CoordPOI cp = (CoordPOI) toRepl;
833840 * @return true if remove is okay
834841 */
835842 private boolean allowedToRemove(Coord p){
836 if (p.getOnBoundary())
843 if (p.getOnBoundary() || p.getOnCountryBorder())
837844 return false;
838845 if (mode == MODE_LINES && p.isEndOfWay())
839846 return false;
10991106 }
11001107 Coord c = getCurrentLocation(replacements);
11011108 Coord n = neighbour.getCurrentLocation(replacements);
1102
11031109 // check special cases: don't merge if
11041110 // 1) both points are via nodes
11051111 // 2) both nodes are boundary nodes with non-equal coords
11061112 // 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)
11091117 return false;
11101118 }
1111 if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || n.getOnBoundary()))
1119 if (c.isViaNodeOfRestriction() && (n.isViaNodeOfRestriction() || nOnBoundary))
11121120 return false;
1113 if (c instanceof CoordPOI && (n instanceof CoordPOI || n.getOnBoundary()))
1121 if (c instanceof CoordPOI && (n instanceof CoordPOI || nOnBoundary))
11141122 return false;
1115 if (n instanceof CoordPOI && (c instanceof CoordPOI || c.getOnBoundary()))
1123 if (n instanceof CoordPOI && (c instanceof CoordPOI || cOnBoundary))
11161124 return false;
1117
1125 //TODO: nodes on country borders?
11181126 Coord mergePoint;
1119 if (c.getOnBoundary() || c instanceof CoordPOI)
1127 if (cOnBoundary || c instanceof CoordPOI)
11201128 mergePoint = c;
1121 else if (n.getOnBoundary() || n instanceof CoordPOI)
1129 else if (nOnBoundary || n instanceof CoordPOI)
11221130 mergePoint = n;
11231131 else if (c.equals(n))
11241132 mergePoint = c;
943943 Coord closePoint = getRoad().getPoints().get(index);
944944 Coord toAdd = new Coord(closePoint);
945945 toAdd.setOnBoundary(closePoint.getOnBoundary());
946 toAdd.setOnCountryBorder(closePoint.getOnCountryBorder());
946947 toAdd.incHighwayCount();
947948 // we have to make sure that the road starts and ends with a CoordNode!
948949 this.endInRoad = addAsNumberNode(splitSegment, toAdd);
151151 if (log.isDebugEnabled())
152152 log.debug("Create coastline way", id, "with", points.size(),
153153 "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);
174155 }
175156
176157 @Override
6565 // Options
6666 private final boolean ignoreTurnRestrictions;
6767
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");
7270
7371 public ElementSaver(EnhancedProperties args) {
7472 if (args.getProperty("preserve-element-order", false)) {
3333 long t1 = System.currentTimeMillis();
3434 log.info("Finishing multipolygons");
3535 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);
3737 if (removeTag == null) {
3838 continue;
3939 }
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);
5247 }
5348 }
5449 log.info("Multipolygon hook finished in "+(System.currentTimeMillis()-t1)+" ms");
1717 import java.awt.geom.Area;
1818 import java.awt.geom.Line2D;
1919 import java.util.ArrayList;
20 import java.util.Arrays;
2021 import java.util.BitSet;
2122 import java.util.Collection;
2223 import java.util.Collections;
3637 import java.util.concurrent.LinkedBlockingQueue;
3738 import java.util.logging.Level;
3839
39 import uk.me.parabola.imgfmt.Utils;
4040 import uk.me.parabola.imgfmt.app.Coord;
4141 import uk.me.parabola.log.Logger;
4242 import uk.me.parabola.util.Java2DConverter;
16311631 || (prevLatField == 0 && prevLonField == 0);
16321632
16331633 boolean intersects = intersectionPossible
1634 && Utils.linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
1634 && linesCutEachOther(p1_1, p1_2, p2_1, p2_2);
16351635
16361636 if (intersects) {
16371637 if ((polygon1.getWay().isClosedArtificially() && !it1.hasNext())
17241724 Coord sw = new Coord(tileBounds.getMinLat(), tileBounds.getMinLong());
17251725 Coord se = new Coord(tileBounds.getMinLat(), tileBounds.getMaxLong());
17261726 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 }
17331775
17341776 private List<JoinedWay> getWaysFromPolygonList(BitSet selection) {
17351777 if (selection.isEmpty()) {
18721914 * @param way
18731915 * a joined way
18741916 * @param tagname
1875 * the tag to be removed (<code>null</code> means remove all
1876 * tags)
1917 * the tag to be removed
18771918 * @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
18801920 */
18811921 private void removeTagInOrgWays(JoinedWay way, String tagname,
18821922 String tagvalue) {
18871927 continue;
18881928 }
18891929
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);
19151935 }
19161936 }
19171937 }
19181938
19191939 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()) {
19221941 return;
19231942 }
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)) {
19251948 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);
19381957 }
19391958
19401959 /**
6262 usedTags.add("route");
6363 usedTags.add("taxi");
6464 }
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
6571
6672 // only enabled if the route option is set
6773 return props.containsKey("route");
9999 Rectangle bboxRect = new Rectangle(bbox.getMinLong(), bbox.getMinLat(), bbox.getWidth(), bbox.getHeight());
100100 long ways = saver.getWays().size();
101101 for (Way way : new ArrayList<Way>(saver.getWays().values())) {
102 if (way.isViaWay())
103 continue;
102104 if (way.getPoints().isEmpty()) {
103105 // empty way will not appear in the map => remove it
104106 saver.getWays().remove(way.getId());
119121 // It is possible that the way is larger than the bounding box and therefore
120122 // contains the bbox completely. Especially this is true for the sea polygon
121123 // 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
129124 for (Coord c : way.getPoints()) {
130125 if (bbox.contains(c)) {
131126 coordInBbox = true;
143138 }
144139 }
145140
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
157141 prevC = c;
158142 }
159143 if (coordInBbox==false) {
160144 // no coord of the way is within the bounding box
161145 // 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());
163147 if (wayBbox.contains(saver.getBoundingBox())) {
164148 log.debug(way, "possibly covers the bbox completely. Keep it.");
165149 } else {
149149 if (id == 0) {
150150 CoordNode node = nodeCoords.get((long) ni.nodeId);
151151 if (node == null) {
152 node = new CoordNode(coord, ni.nodeId, ni.boundary);
152 node = new CoordNode(coord, ni.nodeId, ni.boundary, false);
153153 nodeCoords.put((long) ni.nodeId, node);
154154 }
155155 points.set(n, node);
11
22 import java.util.Collection;
33 import java.util.HashSet;
4 import java.util.List;
4 import java.util.LinkedHashSet;
55 import java.util.Set;
66
77 import uk.me.parabola.imgfmt.app.Area;
8 import uk.me.parabola.imgfmt.app.Coord;
98 import uk.me.parabola.mkgmap.reader.osm.Element;
10 import uk.me.parabola.util.ElementQuadTreeNode.ElementQuadTreePolygon;
119
1210 public class ElementQuadTree {
1311
1715 this.root = new ElementQuadTreeNode(bbox, elements);
1816 }
1917
20 public void remove(Element element) {
21 root.remove(element);
22 }
23
2418 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;
3522 }
3623
3724 public int getDepth() {
3825 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();
5426 }
5527
5628 public boolean isEmpty() {
1313
1414 import java.awt.Rectangle;
1515 import java.util.ArrayList;
16 import java.util.Arrays;
1617 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashMap;
18 import java.util.Iterator;
1919 import java.util.List;
20 import java.util.Map;
21 import java.util.Map.Entry;
2220 import java.util.Set;
2321
2422 import uk.me.parabola.imgfmt.app.Area;
25 import uk.me.parabola.imgfmt.app.Coord;
2623 import uk.me.parabola.log.Logger;
2724 import uk.me.parabola.mkgmap.reader.osm.Element;
2825 import uk.me.parabola.mkgmap.reader.osm.Node;
2926 import uk.me.parabola.mkgmap.reader.osm.Way;
3027
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 */
3133 public final class ElementQuadTreeNode {
3234
3335 private static final Logger log = Logger.getLogger(ElementQuadTreeNode.class);
3436
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
4137 /** The maximum number of coords in the quadtree node. */
4238 private static final int MAX_POINTS = 1000;
4339
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
4641
4742 /** The bounds of this quadtree node */
4843 private final Area bounds;
4944 private final Rectangle boundsRect;
5045
51 /** Flag if this node and all subnodes are empty */
46 /** Flag if this node and all sub-nodes are empty */
5247 private Boolean empty;
5348
54 /** The subnodes in case this node is not a leaf */
49 /** The sub-nodes in case this node is not a leaf */
5550 private ElementQuadTreeNode[] children;
5651
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.
9354 * @return <code>true</code> this quadtree node does not contain any elements; <code>false</code> else
9455 */
9556 public boolean isEmpty() {
9657 if (empty == null) {
97 if (isLeaf()) {
98 empty = elementMap.isEmpty();
58 if (elementList != null && !elementList.isEmpty()) {
59 empty = false;
9960 } else {
10061 empty = true;
10162 for (ElementQuadTreeNode child : children) {
102 if (child.isEmpty()==false) {
103 empty = false;
104 break;
105 }
63 if (!child.isEmpty()) {
64 empty = false;
65 break;
10666 }
67 }
10768 }
10869 }
10970 return empty;
11172
11273
11374 /**
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.
11576 * @return the number of coords
11677 */
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) {
13084 for (ElementQuadTreeNode child : children) {
131 items += child.getSize();
132 }
133 return items;
134 }
85 items += child.getSize();
86 }
87 }
88 return items;
13589 }
13690
13791 /**
13993 * @return the depth of this quadtree node
14094 */
14195 public int getDepth() {
142 if (isLeaf()) {
96 if (children == null)
14397 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) {
154106 this.bounds = bounds;
155107 boundsRect = new Rectangle(bounds.getMinLong(), bounds.getMinLat(),
156108 bounds.getWidth(), bounds.getHeight());
157109 this.children = null;
158 elementMap =elements;
159 empty = elementMap.isEmpty();
110 elementList = elements;
111 empty = elementList.isEmpty();
160112
161113 checkSplit();
162114 }
168120 bounds.getWidth(), bounds.getHeight());
169121 this.children = null;
170122
171 this.elementMap = new HashMap<Element, List<Coord>>(elements.size()*4/3+10);
123 this.elementList = new ArrayList<>();
172124
173125 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();
183130 checkSplit();
184131 }
185132
195142 * Checks if this quadtree node exceeds the maximum size and splits it in such a case.
196143 */
197144 private void checkSplit() {
198 if (getSize() > MAX_POINTS) {
145 if (elementList != null && elementList.size() > 1 && getSize() > MAX_POINTS) {
199146 split();
200147 }
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));
270148 }
271149
272150 /**
273151 * Retrieves all elements that intersects the given bounding box.
274152 * @param bbox the bounding box
275 * @param resultList results are stored in this collection
153 * @param resultSet results are stored in this collection
276154 * @return the resultList
277155 */
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) {
314167 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 }
388171 }
389172
390173 /**
398181
399182 int halfLat = (bounds.getMinLat() + bounds.getMaxLat()) / 2;
400183 int halfLong = (bounds.getMinLong() + bounds.getMaxLong()) / 2;
401 children = new ElementQuadTreeNode[4];
402184 Area[] childBounds = new Area[4];
403185
404186 childBounds[0] = new Area(bounds.getMinLat(), bounds.getMinLong(),
410192 childBounds[3] = new Area(halfLat, halfLong, bounds.getMaxLat(),
411193 bounds.getMaxLong());
412194
413 List<Map<Element, List<Coord>>> childElems = new ArrayList<Map<Element, List<Coord>>>(4);
195 List<List<BoxedElement>> childElems = new ArrayList<>();
414196 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())) {
423210 break;
424211 }
425212 }
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;
455228 }
456229
457230 public void clear() {
458231 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 }
460257 }
461258 }