This is an automated email from the git hooks/post-receive script. sebastic pushed a commit to branch master in repository mkgmap.
commit 503a1714505d40cd33bede2ae02dac75525ce4f6 Author: Bas Couwenberg <sebas...@xs4all.nl> Date: Sun Nov 1 10:57:41 2015 +0100 Imported Upstream version 0.0.0+svn3649 --- build.xml | 2 +- doc/options.txt | 10 +- resources/LocatorConfig.xml | 3 +- resources/help/en/options | 9 + resources/mkgmap-version.properties | 4 +- resources/styles/default/inc/access | 4 + resources/styles/default/lines | 11 +- src/uk/me/parabola/imgfmt/app/Coord.java | 21 +- .../me/parabola/imgfmt/app/net/AngleChecker.java | 424 +++++++++++++++++++++ src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java | 50 +-- src/uk/me/parabola/imgfmt/app/net/RouteArc.java | 18 +- src/uk/me/parabola/imgfmt/app/net/RouteNode.java | 323 +--------------- .../mkgmap/osmstyle/housenumber/ExtNumbers.java | 1 + .../osmstyle/housenumber/HousenumberGenerator.java | 7 +- .../osmstyle/housenumber/HousenumberGroup.java | 3 + .../osmstyle/housenumber/HousenumberIvl.java | 8 +- src/uk/me/parabola/mkgmap/reader/osm/Element.java | 33 +- .../mkgmap/reader/osm/bin/OsmBinHandler.java | 16 +- .../mkgmap/reader/osm/o5m/O5mBinHandler.java | 4 +- .../mkgmap/reader/osm/xml/Osm5XmlHandler.java | 12 +- .../me/parabola/mkgmap/reader/osm/ElementTest.java | 23 ++ 21 files changed, 602 insertions(+), 384 deletions(-) diff --git a/build.xml b/build.xml index 5ad2fc9..eca68f0 100644 --- a/build.xml +++ b/build.xml @@ -1,6 +1,6 @@ <?xml version="1.0"?> <!-- - File: build.xml + File: build.xml Copyright (C) 2006, 2012 mkgmap contributors diff --git a/doc/options.txt b/doc/options.txt index adc280d..ae3795f 100644 --- a/doc/options.txt +++ b/doc/options.txt @@ -434,7 +434,8 @@ specified, only zero-length arcs will be removed. ;--adjust-turn-headings[=BITMASK] -: Where possible, ensure that turns off to side roads change +: now ignored, former explanation: +Where possible, ensure that turns off to side roads change heading sufficiently so that the GPS believes that a turn is required rather than a fork. This also avoids spurious instructions to "keep right/left" when the road doesn't @@ -445,6 +446,13 @@ adjustments are to be made (where necessary): :* 1 = increase angle between side road and outgoing main road :* 2 = increase angle between side road and incoming main road +;--cycle-map +: Tells mkgmap that the map is for cyclists. This assumes that +different vehicles are different kinds of bicycles, e.g. a way +with mkgmap:car=yes and mkgmap:bicycle=no may be a road that is +good for racing bikes, but not for other cyclists. +This allows to optimise sharp angles at junctions of those roads. +Don't use with the default style as that is a general style! ;--report-similar-arcs : Issue a warning when more than one arc connects two nodes and diff --git a/resources/LocatorConfig.xml b/resources/LocatorConfig.xml index d2e74ba..b7a4872 100644 --- a/resources/LocatorConfig.xml +++ b/resources/LocatorConfig.xml @@ -147,9 +147,10 @@ <variant>BV</variant> <variant>BVT</variant> </country> - <country name="Brazil" abr="BRA" streetBeforeHousenumber="true"> + <country name="Brasil" abr="BRA" streetBeforeHousenumber="true"> <variant>BR</variant> <variant>BRA</variant> + <variant>Brazil</variant> </country> <country name="British Indian Ocean Territory" abr="IOT"> <variant>IO</variant> diff --git a/resources/help/en/options b/resources/help/en/options index 349317b..9f16789 100644 --- a/resources/help/en/options +++ b/resources/help/en/options @@ -437,6 +437,7 @@ Miscellaneous options: specified, only zero-length arcs will be removed. --adjust-turn-headings[=BITMASK] + Now ignored, former usage: Where possible, ensure that turns off to side roads change heading sufficiently so that the GPS believes that a turn is required rather than a fork. This also avoids spurious @@ -449,6 +450,14 @@ Miscellaneous options: 1 = increase angle between side road and outgoing main road 2 = increase angle between side road and incoming main road +--cycle-map + Tells mkgmap that the map is for cyclists. This assumes that + different vehicles are different kinds of bicycles, e.g. a way + with mkgmap:car=yes and mkgmap:bicycle=no may be a road that is + good for racing bikes, but not for other cyclists. + This allows to optimise sharp angles at junctions of those roads. + Don't use with the default style as that is a general style! + --report-similar-arcs Issue a warning when more than one arc connects two nodes and the ways that the arcs are derived from contain identical diff --git a/resources/mkgmap-version.properties b/resources/mkgmap-version.properties index 20db163..b3ec584 100644 --- a/resources/mkgmap-version.properties +++ b/resources/mkgmap-version.properties @@ -1,2 +1,2 @@ -svn.version: 3643 -build.timestamp: 2015-09-19T08:13:00+0100 +svn.version: 3649 +build.timestamp: 2015-10-27T06:51:19+0000 diff --git a/resources/styles/default/inc/access b/resources/styles/default/inc/access index 7023296..d0fd1fa 100644 --- a/resources/styles/default/inc/access +++ b/resources/styles/default/inc/access @@ -72,3 +72,7 @@ access=* { addaccess '${access}' } # Check for carpool lane (they are not really supported yet so these lines are commented) # hov=* { add carpool='${hov}' } # (carpool=yes | carpool=designated | carpool=permissive | carpool=official) { set mkgmap:carpool=yes } + +# Don't route through highway=construction, they are considered unusable +highway=construction {setaccess no} + diff --git a/resources/styles/default/lines b/resources/styles/default/lines index ab89b5a..2b3dedd 100644 --- a/resources/styles/default/lines +++ b/resources/styles/default/lines @@ -18,10 +18,13 @@ highway=* & name=* { set mkgmap:street='${name}' } # Mark highways with the toll flag highway=* & (toll=yes|toll=true) { set mkgmap:toll=yes } -# Hide proposed ways -highway=proposed {delete highway;delete junction} +# Hide proposed ways +(highway=proposed | highway=proposal | highway=planned | highway ~ '.*proposed.*') {delete highway;delete junction} # Hide removed ways -highway=razed {deletealltags} +(highway=razed | highway=dismantled) {deletealltags} +# Hide other non-existent ways +(highway=unbuilt | highway=neverbuilt | highway=rejected | highway ~ 'x-.*') {delete highway;delete junction} + # Hide unaccessible tunnels highway=* & tunnel=yes & (access=private|access=no) & foot!=* & bicycle!=* {delete highway;delete junction} @@ -106,7 +109,7 @@ junction=roundabout [0x0c road_class=0 road_speed=1 resolution 22] # Ways that may or may not be useable -# Treat ways under construction almost as highway=path +# Treat ways under construction almost as highway=path, see also extra rule in inc/access highway=construction { add mkgmap:dead-end-check = false; } [0x16 road_class=0 road_speed=0 resolution 23] diff --git a/src/uk/me/parabola/imgfmt/app/Coord.java b/src/uk/me/parabola/imgfmt/app/Coord.java index 89dc0f7..b2be354 100644 --- a/src/uk/me/parabola/imgfmt/app/Coord.java +++ b/src/uk/me/parabola/imgfmt/app/Coord.java @@ -49,6 +49,7 @@ public class Coord implements Comparable<Coord> { private final static short PART_OF_SHAPE2 = 0x0100; // use only in ShapeMerger private final static short END_OF_WAY = 0x0200; // use only in WrongAngleFixer private final static short HOUSENUMBER_NODE = 0x0400; // start/end of house number interval + private final static short ADDED_HOUSENUMBER_NODE = 0x0800; // node was added for house numbers public final static int HIGH_PREC_BITS = 30; public final static int DELTA_SHIFT = 6; @@ -88,7 +89,7 @@ public class Coord implements Comparable<Coord> { int lon30 = toBit30(longitude); this.latDelta = (byte) ((this.latitude << 6) - lat30); this.lonDelta = (byte) ((this.longitude << 6) - lon30); - + // verify math assert (this.latitude << 6) - latDelta == lat30; assert (this.longitude << 6) - lonDelta == lon30; @@ -340,7 +341,6 @@ public class Coord implements Comparable<Coord> { } /** - * Set or unset flag for {@link WrongAngleFixer} * @param b true or false */ public void setNumberNode(boolean b) { @@ -350,6 +350,23 @@ public class Coord implements Comparable<Coord> { this.flags &= ~HOUSENUMBER_NODE; } + /** + * @return if this is the beginning/end of a house number interval + */ + public boolean isAddedNumberNode(){ + return (flags & ADDED_HOUSENUMBER_NODE) != 0; + } + + /** + * @param b true or false + */ + public void setAddedNumberNode(boolean b) { + if (b) + this.flags |= ADDED_HOUSENUMBER_NODE; + else + this.flags &= ~ADDED_HOUSENUMBER_NODE; + } + public int hashCode() { // Use a factor for latitude to span over the whole integer range: // max lat: 4194304 diff --git a/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java b/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java new file mode 100644 index 0000000..b938058 --- /dev/null +++ b/src/uk/me/parabola/imgfmt/app/net/AngleChecker.java @@ -0,0 +1,424 @@ +/* + * Copyright (C) 2015 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 or + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +package uk.me.parabola.imgfmt.app.net; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import uk.me.parabola.log.Logger; +import uk.me.parabola.util.EnhancedProperties; + +/** + * Find sharp angles at junctions. The Garmin routing algorithm doesn't + * like to route on roads building a sharp angle. It adds a time penalty + * from 30 to 150 seconds and often prefers small detours instead. + * The penalty depends on the road speed and the vehicle, for pedestrian + * mode it is zero, for bicycles it is rather small, for cars it is high. + * The sharp angles typically don't exist in the real world, they are + * caused by the simplifications done by mappers. + * + * Maps created for cyclists typically "abuse" the car routing for racing + * bikes, but in this scenario the time penalties are much too high, + * and detours are likely. + * + * This method tries to modify the initial heading values of the arcs + * which are used to calculate the angles. Where possible, the values are + * changed so that angles appear larger. + * + * @author Gerd Petermann + * + */ +public class AngleChecker { + private static final Logger log = Logger.getLogger(AngleChecker.class); + + private boolean ignoreSharpAngles; + private boolean cycleMap; +// private final Coord test = new Coord(48.074815,16.272771); + + private final int MIN_ANGLE = 0x10; + private final int MIN_LOW_SPEED_ANGLE = 0x20; + + private int mask; + + // helper class to collect multiple arcs with (nearly) the same initial headings + private class ArcGroup { + float initialHeading; + byte imgHeading; + int isOneWayTrueCount; + int isForwardTrueCount; + int maxRoadSpeed; + byte orAccessMask; + HashSet<RoadDef> roadDefs = new HashSet<>(); + + List<RouteArc> arcs = new ArrayList<>(); + public void addArc(RouteArc arc) { + arcs.add(arc); + if (arc.getRoadDef().isOneway()) + isOneWayTrueCount++; + if (arc.isForward()) + isForwardTrueCount++; + if (arc.getRoadDef().getRoadSpeed() > maxRoadSpeed) + maxRoadSpeed = arc.getRoadDef().getRoadSpeed(); + orAccessMask |= arc.getRoadDef().getAccess(); + roadDefs.add(arc.getRoadDef()); + } + public float getInitialHeading() { + return initialHeading; + } + public boolean isOneway() { + return isOneWayTrueCount == arcs.size(); + } + public boolean isForward() { + return isForwardTrueCount == arcs.size(); + } + /** + * @return + */ + public void setInitialHeading(float modIH) { + while (modIH > 180) + modIH -= 360; + while (modIH < -180) + modIH += 360; + initialHeading = modIH; + imgHeading = (byte) (RouteArc.directionFromDegrees(initialHeading) & mask); + + for (RouteArc arc : arcs){ + arc.setInitialHeading(modIH); + } + } + + public String toString(){ + return arcs.get(0).toString(); + } + } + + public void config(EnhancedProperties props) { + // undocumented option - usually used for debugging only + ignoreSharpAngles = props.getProperty("ignore-sharp-angles", false); + cycleMap = props.getProperty("cycle-map", false); +// float a = 0; +// for (int i = 0; i <= 1440; i++){ +// int ar = (int) Math.round(a * 256.0 / 360); +// int am = ar & 0xf0; +// log.error(a,ar,"0x" + Integer.toHexString(am)); +// a +=0.25; +// if (a >= 180) +// a -= 360; +// } + return; + } + + public void check(Map<Integer, RouteNode> nodes) { + if (!ignoreSharpAngles){ + byte sharpAnglesCheckMask = cycleMap ? (byte) (0xff & ~AccessTagsAndBits.FOOT) : AccessTagsAndBits.BIKE; + + for (RouteNode node : nodes.values()){ + mask = 0xf0; // we assume compacted format + fixSharpAngles(node, sharpAnglesCheckMask); + } + } + } + + public void fixSharpAngles(RouteNode node, byte sharpAnglesCheckMask) { + + // get direct arcs leaving the node + List<ArcGroup> arcGroups = buildArcGroups(node); + + int n = arcGroups.size(); + if (n <= 1) + return; + // sort the arcs by initial heading + Collections.sort(arcGroups, new Comparator<ArcGroup>() { + public int compare(ArcGroup ag1, ArcGroup ag2) { + if (ag1.initialHeading < ag2.initialHeading) + return -1; + if (ag1.initialHeading > ag2.initialHeading) + return 1; + return 0; + } + }); + + class AngleAttr { + int angle; + int maskedAngle; + int maskedMinAngle = MIN_ANGLE; + boolean noAccess; + + int maskedDeltaToMin(){ + return maskedAngle - maskedMinAngle; + } + void setMaskedMinAngle(int maskedMinAngle){ + this.maskedMinAngle = maskedMinAngle; + } + + public String toString(){ + return angle + "° " + maskedAngle + " " + maskedMinAngle + " " + noAccess; + } + } + + // step one: calculate the existing angles + AngleAttr[] angles = new AngleAttr[n]; + for (int i = 0; i < n; i++){ + ArcGroup ag1 = arcGroups.get(i); + ArcGroup ag2 = arcGroups.get(i+1 < n ? i+1 : 0); + AngleAttr angleAttr = new AngleAttr(); + angles[i] = angleAttr; + angleAttr.angle = Math.round(ag2.getInitialHeading() - ag1.getInitialHeading()); + angleAttr.maskedAngle = ag2.imgHeading - ag1.imgHeading; + if (i + 1 >= n){ + angleAttr.angle += 360; + } + if (angleAttr.maskedAngle < 0) + angleAttr.maskedAngle += 256; + + if (ag1.isOneway() && ag1.isForward()){ + // the "incoming" arc is a wrong direction oneway + angleAttr.noAccess = true; + } else if (ag2.isOneway() && ag2.isForward() == false){ + // the "outgoing" arc is a wrong direction oneway + angleAttr.noAccess = true; + } + +// if (node.getCoord().distance(test) < 2){ +// if (angleAttr.angle == 20){ +// angleAttr.maskedMinAngle = 0x30; +// continue; +// } +// } + int sumSpeeds = ag1.maxRoadSpeed + ag2.maxRoadSpeed; + if (sumSpeeds <= 1) + continue; + byte pathAccessMask = (byte) (ag1.orAccessMask & ag2.orAccessMask); + if (pathAccessMask == 0){ + // no common vehicle allowed on both arcs + angleAttr.noAccess = true; + } + if (angleAttr.noAccess) + continue; + int maskedMinAngle = MIN_LOW_SPEED_ANGLE; + // the Garmin algorithm sees rounded values, so the thresholds are probably + // near 22.5 (0x10), 45(0x20), 67.5 (0x30), 90, 112.5 (0x40) + + // the following code doesn't seem to improve anything, I leave it as comment + // for further experiments. +// if (cycleMap){ +// if (sumSpeeds >= 14) +// maskedMinAngle = 0x80; +// if (sumSpeeds >= 12) +// maskedMinAngle = 0x70; +// if (sumSpeeds >= 10) +// maskedMinAngle = 0x60; +// if (sumSpeeds >= 8) +// maskedMinAngle = 0x50; +// else if (sumSpeeds >= 6) +// maskedMinAngle = 0x40; +// else if (sumSpeeds >= 4) +// maskedMinAngle = 0x30; +// } + angleAttr.setMaskedMinAngle(maskedMinAngle); + + if (angleAttr.maskedDeltaToMin() >= 0) + continue; + + String ignoredReason = null; + if (pathAccessMask == AccessTagsAndBits.FOOT) + ignoredReason = "because it can only be used by pedestrians"; + else if ((pathAccessMask & sharpAnglesCheckMask) == 0) + ignoredReason = "because it can not be used by bike"; + else if (ag1.isOneway() && ag2.isOneway()){ + // both arcs are one-ways, probably the road splits here + // to avoid the sharp angles we are looking for + ignoredReason = "because it seems to be a flare road"; + } + else if (ag1.roadDefs.size() == 1 && ag2.roadDefs.size() == 1 && ag1.roadDefs.containsAll(ag2.roadDefs)){ + ignoredReason = "because both arcs belong to the same road"; + } + if (ignoredReason != null){ + if (log.isInfoEnabled()){ + String sharpAngle = "sharp angle " + angleAttr.angle + "° at " + node.getCoord().toDegreeString(); + log.info(sharpAngle, "headings",getCompassBearing(ag1.getInitialHeading()) , getCompassBearing(ag2.getInitialHeading()),"speeds",ag1.maxRoadSpeed, ag2.maxRoadSpeed); + log.info("ignoring", sharpAngle, ignoredReason); + } + angleAttr.setMaskedMinAngle(MIN_ANGLE); + angleAttr.noAccess = true; + } + } + + for (int i = 0; i < n; i++){ + AngleAttr aa = angles[i]; + if (aa.maskedAngle >= aa.maskedMinAngle || aa.noAccess) + continue; + int oldAngle = aa.angle; + ArcGroup ag1 = arcGroups.get(i); + ArcGroup ag2 = arcGroups.get(i+1 < n ? i+1 : 0); + String sharpAngle = ""; + if (log.isInfoEnabled()){ + sharpAngle = "sharp angle " + aa.angle + "° at " + node.getCoord().toDegreeString(); + log.info(sharpAngle, "headings",getCompassBearing(ag1.getInitialHeading()) , getCompassBearing(ag2.getInitialHeading()),"speeds",ag1.maxRoadSpeed, ag2.maxRoadSpeed); + } + + // XXX restrictions ? + boolean fixed = false; + int wantedIncrement = Math.abs(aa.maskedDeltaToMin()) ; + AngleAttr predAA = angles[i == 0 ? n - 1 : i - 1]; + AngleAttr nextAA = angles[i >= n - 1 ? 0 : i + 1]; + + // we can increase the angle by changing the heading values of one or both arcs + // find out which one to change first + byte origImgDir1 = ag1.imgHeading; + byte origImgDir2 = ag2.imgHeading; + int origImgAngle = getImgAngle(ag1.imgHeading, ag2.imgHeading); + + int deltaPred = predAA.maskedDeltaToMin(); + int deltaNext = nextAA.maskedDeltaToMin(); + + if (deltaNext > 0 && (deltaNext > deltaPred || deltaPred < wantedIncrement)){ + int usedIncrement = Math.min(wantedIncrement, deltaNext); + float oldIH = ag2.getInitialHeading(); + int modIH = ag2.imgHeading + usedIncrement; + if (modIH > 128) + modIH -= 256; + ag2.setInitialHeading(modIH * 360/256); + int modAngle = Math.round(ag2.getInitialHeading() - ag1.getInitialHeading()); + if (modAngle < 0) + modAngle += 360; + int modImgAngle = getImgAngle(ag1.imgHeading, ag2.imgHeading); + if (modImgAngle >= aa.maskedMinAngle) + fixed = true; + log.info(sharpAngle, "changing arc with heading", getCompassBearing(oldIH), "->",getCompassBearing(ag2.getInitialHeading()), + "angle is now",modAngle+"°, in img format:",origImgDir2,"->",ag2.imgHeading, "img angle (0-255)",origImgAngle, "->", modImgAngle); + aa.angle = modAngle; + nextAA.angle -= usedIncrement; + } + if (!fixed && deltaPred > 0){ + wantedIncrement = Math.abs(aa.maskedDeltaToMin()); + int usedIncrement = Math.min(wantedIncrement, deltaPred); + float oldIH = ag1.getInitialHeading(); + int modIH = ag1.imgHeading - usedIncrement; + if (modIH < -128) + modIH += 256; + ag1.setInitialHeading(modIH * 360/256); + int modAngle = Math.round(ag2.getInitialHeading() - ag1.getInitialHeading()); + if (modAngle < 0) + modAngle += 360; + int modImgAngle = getImgAngle(ag1.imgHeading, ag2.imgHeading); + if (modImgAngle >= aa.maskedMinAngle) + fixed = true; + + log.info(sharpAngle, "changing arc with heading", getCompassBearing(oldIH), "->", getCompassBearing(ag1.getInitialHeading()), + "angle is now",modAngle+"°, in img format:",origImgDir1,"->",ag1.imgHeading, "img angle (0-255)",origImgAngle, "->", modImgAngle); + aa.angle = modAngle; + predAA.angle -= usedIncrement; + } + if (!fixed){ + if (aa.angle == oldAngle) + log.info(sharpAngle, "don't know how to fix it"); + else + log.info(sharpAngle, "don't know how to enlarge it further"); + } + } + return; + } + + + /** + * Combine arcs with nearly the same initial heading. + * @param node + * @return + */ + private List<ArcGroup> buildArcGroups(RouteNode node) { + List<ArcGroup> arcGroups = new ArrayList<>(); + List<RouteArc> directArcs = new ArrayList<>(); + for (RouteArc arc : node.getArcs()){ + if (arc.isDirect()){ + directArcs.add(arc); + } + } + if (directArcs.size() < 2) + return arcGroups; // should not happen + + // sort the arcs by initial heading + Collections.sort(directArcs, new Comparator<RouteArc>() { + public int compare(RouteArc ra1, RouteArc ra2) { + if (ra1.getInitialHeading() < ra2.getInitialHeading()) + return -1; + if (ra1.getInitialHeading() > ra2.getInitialHeading()) + return 1; + int d = Integer.compare(ra1.getPointsHash(), ra2.getPointsHash()); + if (d != 0) + return d; + d = Long.compare(ra1.getRoadDef().getId() , ra2.getRoadDef().getId()); + if (d != 0) + return d; + return d; + } + }); + + Iterator<RouteArc> iter = directArcs.listIterator(); + RouteArc arc1 = iter.next(); + boolean addArc1 = false; + while (iter.hasNext() || addArc1){ + ArcGroup ag = new ArcGroup(); + ag.initialHeading = arc1.getInitialHeading(); + ag.addArc(arc1); + arcGroups.add(ag); + addArc1 = false; + while (iter.hasNext()){ + RouteArc arc2 = iter.next(); + if (Math.abs(arc1.getInitialHeading()- arc2.getInitialHeading()) < 1){ + if (arc1.getDest() != arc2.getDest() && arc1.getRoadDef().getId() != arc2.getRoadDef().getId()) + log.warn("sharp angle < 1° at",node.getCoord().toDegreeString(),",maybe duplicated OSM way with bearing",getCompassBearing(arc1.getInitialHeading())); + ag.addArc(arc2); + } else{ + arc1 = arc2; + if (iter.hasNext() == false) + addArc1 = true; + break; + } + } + } + for (ArcGroup ag : arcGroups){ + ag.imgHeading = (byte) (RouteArc.directionFromDegrees(ag.initialHeading) & mask); + } + return arcGroups; + } + + /** + * for log messages + */ + private String getCompassBearing (float bearing){ + float cb = (bearing + 360) % 360; + return Math.round(cb) + "°"; + } + + /** + * Debugging aid: guess what angle the Garmin algorithm is using. + * @param heading1 + * @param heading2 + * @return + */ + private int getImgAngle(byte heading1, byte heading2){ + int angle = heading2 - heading1; + if (angle < 0) + angle += 256; + if (angle > 255) + angle -= 256; + return angle; + } + +} diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java b/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java index 77fe5cc..bc52d4f 100644 --- a/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java +++ b/src/uk/me/parabola/imgfmt/app/net/RoadNetwork.java @@ -49,25 +49,19 @@ public class RoadNetwork { private final List<RouteNode> boundary = new ArrayList<>(); private final List<RoadDef> roadDefs = new ArrayList<>(); private List<RouteCenter> centers = new ArrayList<>(); - private int adjustTurnHeadings ; + private AngleChecker angleChecker = new AngleChecker(); + private boolean checkRoundabouts; private boolean checkRoundaboutFlares; private int maxFlareLengthRatio ; private boolean reportSimilarArcs; public void config(EnhancedProperties props) { - String ath = props.getProperty("adjust-turn-headings"); - if(ath != null) { - if(ath.length() > 0) - adjustTurnHeadings = Integer.decode(ath); - else - adjustTurnHeadings = RouteNode.ATH_DEFAULT_MASK; - } checkRoundabouts = props.getProperty("check-roundabouts", false); checkRoundaboutFlares = props.getProperty("check-roundabout-flares", false); maxFlareLengthRatio = props.getProperty("max-flare-length-ratio", 0); - reportSimilarArcs = props.getProperty("report-similar-arcs", false); + angleChecker.config(props); } public void addRoad(RoadDef roadDef, List<Coord> coordList) { @@ -128,32 +122,33 @@ public class RoadNetwork { RouteNode node1 = getOrAddNode(lastId, lastCoord); RouteNode node2 = getOrAddNode(id, co); - if(node1 == node2) log.error("Road " + roadDef + " contains consecutive identical nodes at " + co.toOSMURL() + " - routing will be broken"); else if(arcLength == 0) log.warn("Road " + roadDef + " contains zero length arc at " + co.toOSMURL()); Coord forwardBearingPoint = coordList.get(lastIndex + 1); - if(lastCoord.equals(forwardBearingPoint)) { + if(lastCoord.equals(forwardBearingPoint) || forwardBearingPoint.isAddedNumberNode()) { // bearing point is too close to last node to be // useful - try some more points for(int bi = lastIndex + 2; bi <= index; ++bi) { - if(!lastCoord.equals(coordList.get(bi))) { - forwardBearingPoint = coordList.get(bi); - break; - } + Coord coTest = coordList.get(bi); + if (coTest.isAddedNumberNode() || lastCoord.equals(coTest)) + continue; + forwardBearingPoint = coTest; + break; } } Coord reverseBearingPoint = coordList.get(index - 1); - if(co.equals(reverseBearingPoint)) { + if(co.equals(reverseBearingPoint) || reverseBearingPoint.isAddedNumberNode()) { // bearing point is too close to this node to be // useful - try some more points for(int bi = index - 2; bi >= lastIndex; --bi) { - if(!co.equals(coordList.get(bi))) { - reverseBearingPoint = coordList.get(bi); - break; - } + Coord coTest = coordList.get(bi); + if (coTest.isAddedNumberNode() || co.equals(coTest)) + continue; + reverseBearingPoint = coTest; + break; } } @@ -162,24 +157,16 @@ public class RoadNetwork { double reverseInitialBearing = co.bearingTo(reverseBearingPoint); double directLength = (lastIndex + 1 == index) ? arcLength : lastCoord.distance(co); - double reverseFinalBearing, forwardFinalBearing, reverseDirectBearing; + double reverseDirectBearing = 0; if (directLength > 0){ // bearing on rhumb line is a constant, so we can simply revert reverseDirectBearing = (forwardDirectBearing <= 0) ? 180 + forwardDirectBearing: -(180 - forwardDirectBearing) % 180.0; - forwardFinalBearing = (reverseInitialBearing <= 0) ? 180 + reverseInitialBearing : -(180 - reverseInitialBearing) % 180.0; - reverseFinalBearing = (forwardInitialBearing <= 0) ? 180 + forwardInitialBearing : -(180 - forwardInitialBearing) % 180.0; - } - else { - reverseDirectBearing = 0; - forwardFinalBearing = 0; - reverseFinalBearing = 0; } // Create forward arc from node1 to node2 RouteArc arc = new RouteArc(roadDef, node1, node2, forwardInitialBearing, - forwardFinalBearing, forwardDirectBearing, arcLength, arcLength, @@ -192,7 +179,6 @@ public class RoadNetwork { RouteArc reverseArc = new RouteArc(roadDef, node2, node1, reverseInitialBearing, - reverseFinalBearing, reverseDirectBearing, arcLength, arcLength, @@ -269,8 +255,7 @@ public class RoadNetwork { if(reportSimilarArcs) node.reportSimilarArcs(); } - if(adjustTurnHeadings != 0) - node.tweezeArcs(adjustTurnHeadings); + nod1.addNode(node); n++; } @@ -281,6 +266,7 @@ public class RoadNetwork { public List<RouteCenter> getCenters() { if (centers.isEmpty()){ + angleChecker.check(nodes); addArcsToMajorRoads(); splitCenters(); } diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java index 5f0f320..7fa9302 100644 --- a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java +++ b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java @@ -43,7 +43,6 @@ public class RouteArc { // heading / bearing: private float initialHeading; // degrees (A-> B in an arc ABCD) - private final float finalHeading; // degrees (C-> D in an arc ABCD) private final float directHeading; // degrees (A-> D in an arc ABCD) private final RoadDef roadDef; @@ -81,7 +80,6 @@ public class RouteArc { * @param source The source node. (A) * @param dest The destination node (E). * @param initialBearing The initial heading (signed degrees) (A->B) - * @param finalBearing The final heading (signed degrees) (D->E) * @param directBearing The direct heading (signed degrees) (A->E) * @param arcLength the length of the arc in meter (A->B->C->D->E) * @param pathLength the length of the arc in meter (summed length for additional arcs) @@ -90,7 +88,7 @@ public class RouteArc { */ public RouteArc(RoadDef roadDef, RouteNode source, RouteNode dest, - double initialBearing, double finalBearing, double directBearing, + double initialBearing, double directBearing, double arcLength, double pathLength, double directLength, @@ -100,7 +98,6 @@ public class RouteArc { this.source = source; this.dest = dest; this.initialHeading = (float) initialBearing; - this.finalHeading = (float) finalBearing; this.directHeading = (directBearing < 180) ? (float) directBearing : -180.0f; int len = NODHeader.metersToRaw(arcLength); if (len >= (1 << 22)) { @@ -129,12 +126,21 @@ public class RouteArc { return initialHeading; } + public float getDirectHeading() { + return directHeading; + } + public void setInitialHeading(float ih) { initialHeading = ih; } public float getFinalHeading() { - return finalHeading; + float fh = 0; + if (lengthInMeter != 0){ + fh = getReverseArc().getInitialHeading(); + fh = (fh <= 0) ? 180.0f + fh : -(180.0f - fh) % 180.0f; + } + return fh; } public RouteNode getSource() { @@ -224,7 +230,7 @@ public class RouteArc { return lengthInMeter; } - public static byte directionFromDegrees(double dir) { + public static byte directionFromDegrees(float dir) { return (byte) Math.round(dir * 256.0 / 360) ; } diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java index 026d7e2..13c0711 100644 --- a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java +++ b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java @@ -17,14 +17,12 @@ package uk.me.parabola.imgfmt.app.net; import it.unimi.dsi.fastutil.ints.IntArrayList; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.imgfmt.app.CoordNode; import uk.me.parabola.imgfmt.app.ImgFileWriter; -import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.log.Logger; /** @@ -53,10 +51,6 @@ public class RouteNode implements Comparable<RouteNode> { // only used internally in mkgmap private static final int F_DISCARDED = 0x100; // node has been discarded - private static final int MAX_MAIN_ROAD_HEADING_CHANGE = 120; - private static final int MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS = 45; - private static final int MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS = 50; - private int offsetNod1 = -1; // arcs from this node @@ -352,310 +346,6 @@ public class RouteNode implements Comparable<RouteNode> { return coord.compareTo(otherNode.getCoord()); } - private static boolean possiblySameRoad(RouteArc raa, RouteArc rab) { - - RoadDef rda = raa.getRoadDef(); - RoadDef rdb = rab.getRoadDef(); - - if(rda.getId() == rdb.getId()) { - // roads have the same (OSM) id - return true; - } - - boolean bothArcsNamed = false; - for(Label laba : rda.getLabels()) { - if(laba != null && laba.getOffset() != 0) { - for(Label labb : rdb.getLabels()) { - if(labb != null && labb.getOffset() != 0) { - bothArcsNamed = true; - if(laba.equals(labb)) { - // the roads have the same label - if(rda.isLinkRoad() == rdb.isLinkRoad()) { - // if both are a link road or both are - // not a link road, consider them the - // same road - return true; - } - // One is a link road and the other isn't - // so consider them different roads - this - // is because people often give a link - // road that's leaving some road the same - // ref as that road but it suits us better - // to consider them as different roads - return false; - } - } - } - } - } - - if(bothArcsNamed) { - // both roads have names and they don't match - return false; - } - - // at least one road is unnamed - if(rda.isRoundabout() && rdb.isRoundabout()) { - // hopefully, segments of the same (unnamed) roundabout - return true; - } - - return false; - } - - private static boolean rightTurnRequired(float inHeading, float outHeading, float otherHeading) { - // given the headings of the incoming, outgoing and side - // roads, decide whether a side road is to the left or the - // right of the main road - - outHeading -= inHeading; - while(outHeading < -180) - outHeading += 360; - while(outHeading > 180) - outHeading -= 360; - - otherHeading -= inHeading; - while(otherHeading < -180) - otherHeading += 360; - while(otherHeading > 180) - otherHeading -= 360; - - return otherHeading > outHeading; - } - - private static final int ATH_OUTGOING = 1; - private static final int ATH_INCOMING = 2; - - public static final int ATH_DEFAULT_MASK = ATH_OUTGOING | ATH_INCOMING; - - public void tweezeArcs(int mask) { - if(arcs.size() >= 3) { - - // detect the "shallow turn" scenario where at a junction - // on some "main" road, the side road leaves the main - // road at a very shallow angle and the GPS says "keep - // right/left" when it would be better if it said "turn - // right/left" - - // also helps to produce a turn instruction when the main - // road bends sharply but the side road keeps close to the - // original heading - - // the code tries to detect a pair of arcs (the "incoming" - // arc and the "outgoing" arc) that are the "main road" - // and the remaining arc (called the "other" arc) which is - // the "side road" - - // having worked out the roles for the arcs, the heuristic - // applied is that if the main road doesn't change its - // heading by more than maxMainRoadHeadingChange, ensure - // that the side road heading differs from the outgoing - // heading by at least - // minDiffBetweenOutgoingAndOtherArcs and the side road - // heading differs from the incoming heading by at least - // minDiffBetweenIncomingAndOtherArcs - - // list of outgoing arcs discovered at this node - List<RouteArc> outgoingArcs = new ArrayList<RouteArc>(); - - // sort incoming arcs by decreasing class/speed - List<RouteArc> inArcs = new ArrayList<RouteArc>(); - for (RouteArc arc : arcs){ - if (arc.isDirect()) - inArcs.add(arc.getReverseArc()); - } - - Collections.sort(inArcs, new Comparator<RouteArc>() { - public int compare(RouteArc ra1, RouteArc ra2) { - int c1 = ra1.getRoadDef().getRoadClass(); - int c2 = ra2.getRoadDef().getRoadClass(); - if(c1 == c2) - return (ra2.getRoadDef().getRoadSpeed() - - ra1.getRoadDef().getRoadSpeed()); - return c2 - c1; - } - }); - - // look at incoming arcs in order of decreasing class/speed - for(RouteArc inArc : inArcs) { - - RoadDef inRoadDef = inArc.getRoadDef(); - - if(!inArc.isForward() && inRoadDef.isOneway()) { - // ignore reverse arc if road is oneway - continue; - } - - float inHeading = inArc.getFinalHeading(); - // determine the outgoing arc that is likely to be the - // same road as the incoming arc - RouteArc outArc = null; - - if(throughRoutes != null) { - // through_route relations have the highest precedence - for(RouteArc[] pair : throughRoutes) { - if(pair[0] == inArc) { - outArc = pair[1]; - log.info("Found through route from " + inRoadDef + " to " + outArc.getRoadDef()); - break; - } - } - } - - if(outArc == null) { - // next, if oa has the same RoadDef as inArc, it's - // definitely the same road - for(RouteArc oa : arcs) { - if (oa.isDirect() == false) - continue; - if(oa.getDest() != inArc.getSource()) { - // this arc is not going to the same node as - // inArc came from - if(oa.getRoadDef() == inRoadDef) { - outArc = oa; - break; - } - } - } - } - - if(outArc == null) { - // next, although the RoadDefs don't match, use - // possiblySameRoad() to see if the roads' id or - // labels (names/refs) match - for(RouteArc oa : arcs) { - if (oa.isDirect() == false) - continue; - if(oa.getDest() != inArc.getSource()) { - // this arc is not going to the same node as - // inArc came from - if((oa.isForward() || !oa.getRoadDef().isOneway()) && - possiblySameRoad(inArc, oa)) { - outArc = oa; - break; - } - } - } - } - - // if we did not find the outgoing arc, give up with - // this incoming arc - if(outArc == null) { - //log.info("Can't continue road " + inRoadDef + " at " + coord.toOSMURL()); - continue; - } - - // remember that this arc is an outgoing arc - outgoingArcs.add(outArc); - - float outHeading = outArc.getInitialHeading(); - float mainHeadingDelta = outHeading - inHeading; - while(mainHeadingDelta > 180) - mainHeadingDelta -= 360; - while(mainHeadingDelta < -180) - mainHeadingDelta += 360; - //log.info(inRoadDef + " continues to " + outArc.getRoadDef() + " with a heading change of " + mainHeadingDelta + " at " + coord.toOSMURL()); - - if(Math.abs(mainHeadingDelta) > MAX_MAIN_ROAD_HEADING_CHANGE) { - // if the continuation road heading change is - // greater than maxMainRoadHeadingChange don't - // adjust anything - continue; - } - - for(RouteArc otherArc : arcs) { - if (otherArc.isDirect() == false) - continue; - - // for each other arc leaving this node, tweeze - // its heading if its heading change from the - // outgoing heading is less than - // minDiffBetweenOutgoingAndOtherArcs or its - // heading change from the incoming heading is - // less than minDiffBetweenIncomingAndOtherArcs - - if(otherArc.getDest() == inArc.getSource() || - otherArc == outArc) { - // we're looking at the incoming or outgoing - // arc, ignore it - continue; - } - - if(!otherArc.isForward() && - otherArc.getRoadDef().isOneway()) { - // ignore reverse arc if road is oneway - continue; - } - - if(inRoadDef.isLinkRoad() && - otherArc.getRoadDef().isLinkRoad()) { - // it's a link road leaving a link road so - // leave the angle unchanged to avoid - // introducing a time penalty by increasing - // the angle (this stops the router using link - // roads that "cut the corner" at roundabouts) - continue; - } - - if(outgoingArcs.contains(otherArc)) { - // this arc was previously matched as an - // outgoing arc so we don't want to change its - // heading now - continue; - } - - float otherHeading = otherArc.getInitialHeading(); - float outToOtherDelta = otherHeading - outHeading; - while(outToOtherDelta > 180) - outToOtherDelta -= 360; - while(outToOtherDelta < -180) - outToOtherDelta += 360; - float inToOtherDelta = otherHeading - inHeading; - while(inToOtherDelta > 180) - inToOtherDelta -= 360; - while(inToOtherDelta < -180) - inToOtherDelta += 360; - - float newHeading = otherHeading; - if(rightTurnRequired(inHeading, outHeading, otherHeading)) { - // side road to the right - if((mask & ATH_OUTGOING) != 0 && - Math.abs(outToOtherDelta) < MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS) - newHeading = outHeading + MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS; - if((mask & ATH_INCOMING) != 0 && - Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) { - float nh = inHeading + MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS; - if(nh > newHeading) - newHeading = nh; - } - - if(newHeading > 180) - newHeading -= 360; - } - else { - // side road to the left - if((mask & ATH_OUTGOING) != 0 && - Math.abs(outToOtherDelta) < MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS) - newHeading = outHeading - MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS; - if((mask & ATH_INCOMING) != 0 && - Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) { - float nh = inHeading - MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS; - if(nh < newHeading) - newHeading = nh; - } - - if(newHeading < -180) - newHeading += 360; - } - if(Math.abs(newHeading - otherHeading) > 0.0000001) { - otherArc.setInitialHeading(newHeading); - log.info("Adjusting turn heading from " + otherHeading + " to " + newHeading + " at junction of " + inRoadDef + " and " + otherArc.getRoadDef() + " at " + coord.toOSMURL()); - } - } - } - } - } - public void checkRoundabouts() { List<RouteArc> roundaboutArcs = new ArrayList<RouteArc>(); @@ -1010,7 +700,6 @@ public class RouteNode implements Comparable<RouteNode> { sourceNode, destNode, roadArcs.get(i).getInitialHeading(), // not used - arcToDest.getFinalHeading(), // not used c1.bearingTo(c2), partialArcLength, // from stepNode to destNode on road pathLength, // from sourceNode to destNode on road @@ -1111,4 +800,16 @@ public class RouteNode implements Comparable<RouteNode> { public int hashCode(){ return getCoord().getId(); } + + public List<RouteArc> getDirectArcsBetween(RouteNode otherNode) { + List<RouteArc> result = new ArrayList<>(); + for(RouteArc a : arcs){ + if(a.isDirect() && a.getDest() == otherNode){ + result.add(a); + } + } + return result; + } + + } diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java index 36e7e19..733d104 100644 --- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java +++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/ExtNumbers.java @@ -1070,6 +1070,7 @@ public class ExtNumbers { */ private int addAsNumberNode(int pos, Coord toAdd){ toAdd.setNumberNode(true); + toAdd.setAddedNumberNode(true); getRoad().getPoints().add(pos, toAdd); ExtNumbers work = next; diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java index e7e389e..753d5f2 100644 --- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java +++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGenerator.java @@ -908,10 +908,7 @@ public class HousenumberGenerator { MapRoad uncheckedRoads[] = new MapRoad[houses.length]; for (int i = 0 ; i < houses.length; i++) uncheckedRoads[i] = houses[i].getRoad(); - isOK = info.checkRoads(); - if (!isOK) - continue; // check if houses are assigned to different roads now houses = info.getHouseNodes(); for (int i = 0 ; i < houses.length; i++){ @@ -919,6 +916,10 @@ public class HousenumberGenerator { initialHousesForRoads.removeMapping(uncheckedRoads[i], houses[i]); if (houses[i].isIgnored() == false) initialHousesForRoads.add(houses[i].getRoad(), houses[i]); + else { + if (!isOK) + log.info("housenumber is assigned to different road after checking addr:interpolation way which turned out to be invalid",houses[i],info ); + } } } } diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java index f09df40..1b4fa58 100644 --- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java +++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberGroup.java @@ -164,9 +164,11 @@ public class HousenumberGroup { if (timesToAdd == 2){ // add two new points between c1 and c2 points.add(seg + 1, pointToUse); + pointToUse.setAddedNumberNode(true); pointToUse = new Coord (pointToUse); pointToUse.setNumberNode(true); points.add(seg + 1, pointToUse); + pointToUse.setAddedNumberNode(true); linkNode = pointToUse; } else { // copy it @@ -174,6 +176,7 @@ public class HousenumberGroup { pointToUse.setNumberNode(true); // add copy before c2 points.add(seg + 1, pointToUse); + pointToUse.setAddedNumberNode(true); if (pointToUse.highPrecEquals(c1)){ linkNode = c1; } else { diff --git a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java index b6c25d8..20008fa 100644 --- a/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java +++ b/src/uk/me/parabola/mkgmap/osmstyle/housenumber/HousenumberIvl.java @@ -171,9 +171,11 @@ public class HousenumberIvl { while (streetName.equals(knownHouses[i].getRoad().getStreet()) == false && knownHouses[i].hasAlternativeRoad()){ HousenumberMatch testx = new HousenumberMatch(knownHouses[i]); MapRoad r = knownHouses[i].getAlternativeRoads().remove(0); - HousenumberGenerator.findClosestRoadSegment(testx, r); - if (testx.getDistance() < MAX_INTERPOLATION_DISTANCE_TO_ROAD){ - copyRoadData(testx, knownHouses[i]); + if (streetName.equals(r.getStreet())){ + HousenumberGenerator.findClosestRoadSegment(testx, r); + if (testx.getDistance() < MAX_INTERPOLATION_DISTANCE_TO_ROAD){ + copyRoadData(testx, knownHouses[i]); + } } } } diff --git a/src/uk/me/parabola/mkgmap/reader/osm/Element.java b/src/uk/me/parabola/mkgmap/reader/osm/Element.java index 5421f3a..eb5dafd 100644 --- a/src/uk/me/parabola/mkgmap/reader/osm/Element.java +++ b/src/uk/me/parabola/mkgmap/reader/osm/Element.java @@ -19,10 +19,16 @@ import java.util.Collections; import java.util.Iterator; import java.util.Map; +import uk.me.parabola.imgfmt.app.Label; +import uk.me.parabola.log.Logger; + + /** * Superclass of the node, segment and way OSM elements. */ public abstract class Element { + private static final Logger log = Logger.getLogger(Element.class); + private Tags tags; private long id; private long originalId; @@ -32,7 +38,30 @@ public abstract class Element { } /** - * Add a tag to the way. Some tags are recognised separately and saved in + * Add a tag to the element. This method should be called by OSM readers + * because it trims obsolete spaces from the value. + * + * @param key The tag name. + * @param val Its value. + */ + public void addTagFromRawOSM(String key, String val) { + if (val != null){ + val = val.trim(); + if (val.isEmpty() == false){ + // remove duplicated spaces within value + String squashed = Label.squashSpaces(val); + if (val.equals(squashed) == false) { + if (log.isInfoEnabled()) + log.info(this.toBrowseURL(),"obsolete blanks removed from tag", key, " '" + val + "' -> '" + squashed + "'"); + val = squashed; + } + } + } + addTag(key, val.intern()); + } + + /** + * Add a tag to the element. Some tags are recognised separately and saved in * separate fields. * * @param key The tag name. @@ -45,7 +74,7 @@ public abstract class Element { } /** - * Add a tag to the way. Some tags are recognised separately and saved in + * Add a tag to the element. Some tags are recognised separately and saved in * separate fields. * * @param tagKey The tag id created by TagDict diff --git a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java index efba2ac..2811e58 100644 --- a/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java +++ b/src/uk/me/parabola/mkgmap/reader/osm/bin/OsmBinHandler.java @@ -70,10 +70,10 @@ public class OsmBinHandler extends OsmHandler { Node node = new Node(id, co); for (int tid = 0; tid < tagCount; tid++) { String key = getStringById(binNode.getKeys(tid)); - String val = getStringById(binNode.getVals(tid)).trim(); + String val = getStringById(binNode.getVals(tid)); key = keepTag(key, val); if (key != null) - node.addTag(key, val.intern()); + node.addTagFromRawOSM(key, val); } saver.addNode(node); @@ -105,12 +105,12 @@ public class OsmBinHandler extends OsmHandler { int keyid = nodes.getKeysVals(kvid++); int valid = nodes.getKeysVals(kvid++); String key = getStringById(keyid); - String val = getStringById(valid).trim(); + String val = getStringById(valid); key = keepTag(key, val); if (key != null) { if (node == null) node = new Node(id, co); - node.addTag(key, val.intern()); + node.addTagFromRawOSM(key, val); ntags++; } } @@ -132,10 +132,10 @@ public class OsmBinHandler extends OsmHandler { for (int j = 0; j < binWay.getKeysCount(); j++) { String key = getStringById(binWay.getKeys(j)); - String val = getStringById(binWay.getVals(j)).trim(); + String val = getStringById(binWay.getVals(j)); key = keepTag(key, val); if (key != null) - way.addTag(key, val.intern()); + way.addTagFromRawOSM(key, val); } long nid = 0; @@ -157,7 +157,7 @@ public class OsmBinHandler extends OsmHandler { boolean tagsIncomplete = false; for (int j = 0; j < binRel.getKeysCount(); j++) { String key = getStringById(binRel.getKeys(j)); - String val = getStringById(binRel.getVals(j)).trim(); + String val = getStringById(binRel.getVals(j)); // type is required for relations - all other tags are filtered if ("type".equals(key)) // intern the string @@ -167,7 +167,7 @@ public class OsmBinHandler extends OsmHandler { if (key == null) tagsIncomplete = true; else - rel.addTag(key, val.intern()); + rel.addTagFromRawOSM(key, val); } if (tagsIncomplete) { diff --git a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java index 1992725..b1b4863 100644 --- a/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java +++ b/src/uk/me/parabola/mkgmap/reader/osm/o5m/O5mBinHandler.java @@ -314,7 +314,7 @@ public class O5mBinHandler extends OsmHandler{ while (bytesToRead > 0){ readStringPair(); String key = stringPair[0]; - String val = stringPair[1].trim(); + String val = stringPair[1]; // the type tag is required for relations - all other tags are filtered if (elem instanceof Relation && "type".equals(key)) // intern the string @@ -322,7 +322,7 @@ public class O5mBinHandler extends OsmHandler{ else key = keepTag(key, val); if (key != null) - elem.addTag(key, val.intern()); + elem.addTagFromRawOSM(key, val); else tagsIncomplete = true; } diff --git a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java index 8af21ec..54b1c69 100644 --- a/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java +++ b/src/uk/me/parabola/mkgmap/reader/osm/xml/Osm5XmlHandler.java @@ -207,7 +207,7 @@ public class Osm5XmlHandler extends OsmHandler { private void startInNode(String qName, Attributes attributes) { if (qName.equals("tag")) { String key = attributes.getValue("k"); - String val = attributes.getValue("v").trim(); + String val = attributes.getValue("v"); // We only want to create a full node for nodes that are POI's // and not just one point of a way. Only create if it has tags that @@ -219,7 +219,7 @@ public class Osm5XmlHandler extends OsmHandler { currentNode = new Node(currentElementId, co); } - currentNode.addTag(key, val.intern()); + currentNode.addTagFromRawOSM(key, val); } } } @@ -235,10 +235,10 @@ public class Osm5XmlHandler extends OsmHandler { addCoordToWay(currentWay, id); } else if (qName.equals("tag")) { String key = attributes.getValue("k"); - String val = attributes.getValue("v").trim(); + String val = attributes.getValue("v"); key = keepTag(key, val); if (key != null) - currentWay.addTag(key, val.intern()); + currentWay.addTagFromRawOSM(key, val); } } @@ -276,7 +276,7 @@ public class Osm5XmlHandler extends OsmHandler { currentRelation.addElement(attributes.getValue("role"), el); } else if (qName.equals("tag")) { String key = attributes.getValue("k"); - String val = attributes.getValue("v").trim(); + String val = attributes.getValue("v"); // the type tag is required for relations - all other tags are filtered if ("type".equals(key)) // intern the key @@ -286,7 +286,7 @@ public class Osm5XmlHandler extends OsmHandler { if (key == null) { currentRelation.addTag(TAGS_INCOMPLETE_TAG, "true"); } else { - currentRelation.addTag(key, val.intern()); + currentRelation.addTagFromRawOSM(key, val); } } } diff --git a/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java b/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java index b5ce727..0c1bd7d 100644 --- a/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java +++ b/test/uk/me/parabola/mkgmap/reader/osm/ElementTest.java @@ -54,4 +54,27 @@ public class ElementTest { new String[] {"1", "2", "3"}, values.toArray()); } + + @Test + public void testaddTagFromRawOSM() { + Element el = new Way(1); + + el.addTagFromRawOSM("a", "1"); + el.addTagFromRawOSM("b", "1 "); + el.addTagFromRawOSM("c", " 1"); + el.addTagFromRawOSM("d", "1 2"); + el.addTagFromRawOSM("e", "1 2 3"); + el.addTagFromRawOSM("f", " 1 2 3 4 "); + el.addTagFromRawOSM("g", " "); + el.addTagFromRawOSM("h", " "); + + assertEquals("1", el.getTag("a")); + assertEquals("1", el.getTag("b")); + assertEquals("1", el.getTag("c")); + assertEquals("1 2", el.getTag("d")); + assertEquals("1 2 3", el.getTag("e")); + assertEquals("1 2 3 4", el.getTag("f")); + assertEquals("", el.getTag("g")); + assertEquals("", el.getTag("h")); + } } -- Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/mkgmap.git _______________________________________________ Pkg-grass-devel mailing list Pkg-grass-devel@lists.alioth.debian.org http://lists.alioth.debian.org/cgi-bin/mailman/listinfo/pkg-grass-devel