v3 - now based on r1265. Added --adjust-turn-headings option to enable
this functionality. Reports now downgraded to info (from warn).
------------------------
v2 - various changes - should now work with junctions that have more
than 1 side road - should be able to treat exits from roundabouts in
the same way as side roads.
-------------------------
The attached patch (which also includes the drive-on-left patch because
it's based on that) attempts to improve the quality of routing
instructions so that when a turn off a road that doesn't involve a big
heading change is required, the GPS should say "turn left/right" rather
than "keep left/right". It can also reduce the number of "keep
left/right" instructions you get when you are not leaving the main road
but passing a junction that has a shallow turn (e.g. sometimes happens
on motorways when it tells you to keep left/right even though you are
not leaving the motorway).
I have tested it a little and it appears to work. I expect there are
situations where it does the wrong thing so it would be great if people
could give it a go and see if it behaves OK.
There isn't any option to enable the feature at the moment. If you use
the patch, it will be enabled.
It's currently issuing a warning for each change it makes but later that
can be downgraded to info.
Cheers,
Mark
diff --git a/resources/help/en/options b/resources/help/en/options
index bf7a587..e31a747 100644
--- a/resources/help/en/options
+++ b/resources/help/en/options
@@ -211,6 +211,13 @@ Miscellaneous options:
than that length will be removed. If a length is not
specified, only zero-length arcs will be removed.
+--adjust-turn-headings
+ 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
+ actually fork.
+
--road-name-pois[=GarminCode]
Generate a POI for each named road. By default, the POIs'
Garmin type code is 0x640a. If desired, a different type code
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
index c50d591..0b431cf 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java
@@ -69,7 +69,7 @@ public class RoadDef implements Comparable {
// for diagnostic purposes
public String toString() {
- return "RoadDef(" + name + ", " + id + ")";
+ return "(" + name + ", " + id + ")";
}
public String getName() {
@@ -105,6 +105,8 @@ public class RoadDef implements Comparable {
private City city;
private Zip zip;
+ private boolean roundabout;
+ private boolean linkRoad;
/**
* This is for writing to NET1.
@@ -537,6 +539,10 @@ public class RoadDef implements Comparable {
netFlags |= NET_FLAG_ONEWAY;
}
+ public boolean isOneway() {
+ return (netFlags & NET_FLAG_ONEWAY) != 0;
+ }
+
public void setCity(City city) {
this.city = city;
netFlags |= NET_FLAG_ADDRINFO;
@@ -561,4 +567,20 @@ public class RoadDef implements Comparable {
public City getCity() {
return city;
}
+
+ public void setRoundabout(boolean r) {
+ roundabout = r;
+ }
+
+ public boolean isRoundabout() {
+ return roundabout;
+ }
+
+ public void setLinkRoad(boolean lr) {
+ linkRoad = lr;
+ }
+
+ public boolean isLinkRoad() {
+ return linkRoad;
+ }
}
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
index 57f3f50..50ace24 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteArc.java
@@ -43,8 +43,8 @@ public class RouteArc {
private int offset;
- private final byte initialHeading;
- private byte endDirection;
+ private int initialHeading; // degrees
+ private int finalHeading; // degrees
private final RoadDef roadDef;
@@ -69,10 +69,12 @@ public class RouteArc {
* @param roadDef The road that this arc segment is part of.
* @param source The source node.
* @param dest The destination node.
- * @param nextCoord The heading coordinate.
+ * @param initialHeading The initial heading (signed degrees)
*/
- public RouteArc(RoadDef roadDef, RouteNode source, RouteNode dest,
- Coord nextCoord, double length) {
+ public RouteArc(RoadDef roadDef,
+ RouteNode source, RouteNode dest,
+ int initialHeading, int finalHeading,
+ double length) {
this.roadDef = roadDef;
this.source = source;
this.dest = dest;
@@ -80,25 +82,28 @@ public class RouteArc {
this.length = convertMeters(length);
if(log.isDebugEnabled())
log.debug("set length", this.length);
- // if nextCoord is so close to the source node that the
- // coordinates are equal, it won't be possible to determine
- // the heading angle so use the dest node instead
- if(source.getCoord().equals(nextCoord)) {
- if(source.getCoord().equals(dest.getCoord())) {
- log.warn("Can't determine arc heading (using 0) at " + source.getCoord().toOSMURL());
- this.initialHeading = (byte)0;
- }
- else {
- log.info("Using destination node to determine arc heading at " + source.getCoord().toOSMURL());
- this.initialHeading = calcAngle(dest.getCoord());
- }
- }
- else
- this.initialHeading = calcAngle(nextCoord);
+ this.initialHeading = initialHeading;
+ this.finalHeading = finalHeading;
// too early: dest.nodeClass may still increase
//setDestinationClass(dest.getNodeClass());
}
+ public int getInitialHeading() {
+ return initialHeading;
+ }
+
+ public void setInitialHeading(int ih) {
+ initialHeading = ih;
+ }
+
+ public int getFinalHeading() {
+ return finalHeading;
+ }
+
+ public void setFinalHeading(int fh) {
+ finalHeading = fh;
+ }
+
public RouteNode getSource() {
return source;
}
@@ -167,23 +172,6 @@ public class RouteArc {
return indexB;
}
- private byte calcAngle(Coord end) {
- Coord start = source.getCoord();
-
- if(log.isDebugEnabled())
- log.debug("start", start.toDegreeString(), ", end", end.toDegreeString());
-
- double angle = source.getCoord().bearingTo(end);
-
- if(log.isDebugEnabled())
- log.debug("angle is ", angle);
-
- byte b = (byte) (256 * angle / 360);
- if(log.isDebugEnabled())
- log.debug("deg from ret val", (360 * b) / 256);
-
- return b;
- }
private static int convertMeters(double l) {
// XXX: really a constant factor?
@@ -222,7 +210,7 @@ public class RouteArc {
for (int aLendat : lendat)
writer.put((byte) aLendat);
- writer.put(initialHeading);
+ writer.put((byte)(256 * initialHeading / 360));
if (curve) {
int[] curvedat = encodeCurve();
@@ -314,13 +302,4 @@ public class RouteArc {
log.debug("setting destination class", destinationClass);
flagA |= (destinationClass & MASK_DESTCLASS);
}
-
- public void setEndDirection(double ang) {
- endDirection = angleToByte(ang);
- curve = true;
- }
-
- private static byte angleToByte(double ang) {
- return (byte) (255 * ang / 360);
- }
}
diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
index 90a46cd..8086fda 100644
--- a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
+++ b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java
@@ -21,6 +21,7 @@ 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;
/**
@@ -58,7 +59,9 @@ public class RouteNode implements Comparable<RouteNode> {
private final List<RouteArc> arcs = new ArrayList<RouteArc>();
// restrictions at (via) this node
private final List<RouteRestriction> restrictions = new ArrayList<RouteRestriction>();
-
+ // arcs to this node
+ private final List<RouteArc> incomingArcs = new ArrayList<RouteArc>();
+
private int flags = F_UNK_NEEDED;
private final CoordNode coord;
@@ -107,6 +110,10 @@ public class RouteNode implements Comparable<RouteNode> {
flags |= F_ARCS;
}
+ public void addIncomingArc(RouteArc arc) {
+ incomingArcs.add(arc);
+ }
+
public void addRestriction(RouteRestriction restr) {
restrictions.add(restr);
flags |= F_RESTRICTIONS;
@@ -285,4 +292,169 @@ public class RouteNode implements Comparable<RouteNode> {
public int compareTo(RouteNode otherNode) {
return coord.compareTo(otherNode.getCoord());
}
+
+ private static boolean possiblySameRoad(RouteArc raa, RouteArc rab) {
+
+ if(raa == rab) {
+ // the arcs are the same object
+ return true;
+ }
+
+ RoadDef rda = raa.getRoadDef();
+ RoadDef rdb = rab.getRoadDef();
+ if(rda == rdb) {
+ // the arcs share the same RoadDef
+ 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 name
+ 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's a link road and the other isn't
+ // so consider them 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;
+ }
+
+ public void tweezeArcs() {
+ if(arcs.size() >= 3) {
+ final int maxMainRoadHeadingChange = 120;
+ final int minSideRoadHeadingChange = 45; // min change to be a "turn"
+
+ // detect the "shallow turn" scenario where at a junction
+ // on some road, the side road leaves the original road at
+ // a very shallow angle and the GPS says "keep right/left"
+ // when it would be better if it said "turn right/left"
+
+ // the code tries to detect a pair of arcs (the "incoming"
+ // arc and the "continuing" 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 continuing
+ // heading by at least minSideRoadHeadingChange
+
+ for(RouteArc inArc : incomingArcs) {
+
+ if(!inArc.isForward() && inArc.getRoadDef().isOneway()) {
+ // don't bother with reverse arcs if the road is
+ // oneway
+ continue;
+ }
+
+ int inHeading = inArc.getFinalHeading();
+ // determine the outgoing (continuing) arc that is
+ // likely to be the same road as the incoming arc
+ RouteArc continuingArc = null;
+ for(RouteArc ca : arcs) {
+ if(ca.getDest() != inArc.getSource()) {
+ // this arc is not going to the same node as
+ // inArc came from
+ if(possiblySameRoad(inArc, ca)) {
+ continuingArc = ca;
+ break;
+ }
+ }
+ }
+ // if we did not find the continuing arc, give up with
+ // this incoming arc
+ if(continuingArc == null) {
+ //log.info("Can't continue road " + inArc.getRoadDef() + " at " + coord.toOSMURL());
+ continue;
+ }
+ int continuingHeading = continuingArc.getInitialHeading();
+ int mainHeadingDelta = continuingHeading - inHeading;
+ while(mainHeadingDelta > 180)
+ mainHeadingDelta -= 360;
+ while(mainHeadingDelta < -180)
+ mainHeadingDelta += 360;
+ //log.info(inArc.getRoadDef() + " continues to " + continuingArc.getRoadDef() + " with a heading change of " + mainHeadingDelta + " at " + coord.toOSMURL());
+
+ if(Math.abs(mainHeadingDelta) > maxMainRoadHeadingChange) {
+ // if the continuation road heading change is
+ // greater than maxMainRoadHeadingChange don't
+ // adjust anything
+ continue;
+ }
+
+ for(RouteArc otherArc : arcs) {
+
+ // for each other arc leaving this node, tweeze
+ // its heading if its heading change from the
+ // continuation heading is less than
+ // minSideRoadHeadingChange
+
+ if(otherArc.getDest() == inArc.getSource() ||
+ otherArc == continuingArc) {
+ // we're looking at the incoming or continuing
+ // arc, ignore it
+ continue;
+ }
+
+
+ if(possiblySameRoad(inArc, otherArc) ||
+ possiblySameRoad(continuingArc, otherArc)) {
+ // not obviously a different road so give up
+ continue;
+ }
+
+ int outHeading = otherArc.getInitialHeading();
+ int outHeadingDelta = outHeading - continuingHeading;
+ while(outHeadingDelta > 180)
+ outHeadingDelta -= 360;
+ while(outHeadingDelta < -180)
+ outHeadingDelta += 360;
+ // log.warn("Found turn ("+ outHeadingDelta + " deg) from " + inArc.getRoadDef() + " to " + otherArc.getRoadDef() + " at " + coord.toOSMURL());
+ if(outHeadingDelta > 0 &&
+ outHeadingDelta < minSideRoadHeadingChange) {
+ int newHeading = continuingHeading + minSideRoadHeadingChange;
+ if(newHeading > 180)
+ newHeading -= 360;
+ otherArc.setInitialHeading(newHeading);
+ log.info("Adjusting turn heading from " + outHeading + " to " + newHeading + " at junction of " + inArc.getRoadDef() + " and " + otherArc.getRoadDef() + " at " + coord.toOSMURL());
+ }
+ else if(outHeadingDelta < 0 &&
+ outHeadingDelta > -minSideRoadHeadingChange) {
+ int newHeading = continuingHeading - minSideRoadHeadingChange;
+
+ if(newHeading < -180)
+ newHeading += 360;
+ otherArc.setInitialHeading(newHeading);
+ log.info("Adjusting turn heading from " + outHeading + " to " + newHeading + " at junction of " + inArc.getRoadDef() + " and " + otherArc.getRoadDef() + " at " + coord.toOSMURL());
+ }
+ }
+ }
+ }
+ }
}
diff --git a/src/uk/me/parabola/mkgmap/general/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java
index 71a54fe..22b94d4 100644
--- a/src/uk/me/parabola/mkgmap/general/MapDetails.java
+++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java
@@ -28,6 +28,7 @@ import uk.me.parabola.imgfmt.app.trergn.Overview;
import uk.me.parabola.imgfmt.app.trergn.PointOverview;
import uk.me.parabola.imgfmt.app.trergn.PolygonOverview;
import uk.me.parabola.imgfmt.app.trergn.PolylineOverview;
+import uk.me.parabola.util.EnhancedProperties;
/**
* The map features that we are going to map are collected here.
@@ -51,6 +52,10 @@ public class MapDetails implements MapCollector, MapDataSource {
private final RoadNetwork roadNetwork = new RoadNetwork();
+ public void config(EnhancedProperties props) {
+ roadNetwork.config(props);
+ }
+
/**
* Add a point to the map.
*
diff --git a/src/uk/me/parabola/mkgmap/general/MapRoad.java b/src/uk/me/parabola/mkgmap/general/MapRoad.java
index 2b0a96a..720c2bf 100644
--- a/src/uk/me/parabola/mkgmap/general/MapRoad.java
+++ b/src/uk/me/parabola/mkgmap/general/MapRoad.java
@@ -108,4 +108,12 @@ public class MapRoad extends MapLine {
public void setRoadZip(Zip z) {
this.roadDef.setZip(z);
}
+
+ public void setRoundabout(boolean r) {
+ this.roadDef.setRoundabout(r);
+ }
+
+ public void setLinkRoad(boolean lr) {
+ this.roadDef.setLinkRoad(lr);
+ }
}
diff --git a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
index 8506381..a6bfcfe 100644
--- a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
+++ b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java
@@ -30,6 +30,7 @@ import uk.me.parabola.imgfmt.app.net.RouteCenter;
import uk.me.parabola.imgfmt.app.net.RouteNode;
import uk.me.parabola.imgfmt.app.net.RouteRestriction;
import uk.me.parabola.log.Logger;
+import uk.me.parabola.util.EnhancedProperties;
/**
* This holds the road network. That is all the roads and the nodes
@@ -60,6 +61,11 @@ public class RoadNetwork {
private final List<RoadDef> roadDefs = new ArrayList<RoadDef>();
private List<RouteCenter> centers = new ArrayList<RouteCenter>();
+ private boolean adjustTurnHeadings;
+
+ public void config(EnhancedProperties props) {
+ adjustTurnHeadings = props.getProperty("adjust-turn-headings", false);
+ }
public void addRoad(MapRoad road) {
//mapRoads.add(road);
@@ -110,18 +116,53 @@ public class RoadNetwork {
log.error(" " + co.toOSMURL());
}
+ Coord bearingPoint = coordList.get(lastIndex + 1);
+ if(lastCoord.equals(bearingPoint)) {
+ // 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))) {
+ bearingPoint = coordList.get(bi);
+ break;
+ }
+ }
+ }
+ int forwardBearing = (int)lastCoord.bearingTo(bearingPoint);
+ int inverseForwardBearing = (int)bearingPoint.bearingTo(lastCoord);
+
+ bearingPoint = coordList.get(index - 1);
+ if(co.equals(bearingPoint)) {
+ // 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))) {
+ bearingPoint = coordList.get(bi);
+ break;
+ }
+ }
+ }
+ int reverseBearing = (int)co.bearingTo(bearingPoint);
+ int inverseReverseBearing = (int)bearingPoint.bearingTo(co);
+
// Create forward arc from node1 to node2
- Coord bearing = coordList.get(lastIndex + 1);
- RouteArc arc = new RouteArc(road.getRoadDef(), node1, node2, bearing, arcLength);
+ RouteArc arc = new RouteArc(road.getRoadDef(),
+ node1,
+ node2,
+ forwardBearing,
+ inverseReverseBearing,
+ arcLength);
arc.setForward();
node1.addArc(arc);
+ node2.addIncomingArc(arc);
// Create the reverse arc
- bearing = coordList.get(index - 1);
- RouteArc arc2 = new RouteArc(road.getRoadDef(),
- node2, node1,
- bearing, arcLength);
- node2.addArc(arc2);
+ arc = new RouteArc(road.getRoadDef(),
+ node2, node1,
+ reverseBearing,
+ inverseForwardBearing,
+ arcLength);
+ node2.addArc(arc);
+ node1.addIncomingArc(arc);
} else {
// This is the first node in the road
road.getRoadDef().setNode(getNode(id, co));
@@ -162,8 +203,11 @@ public class RoadNetwork {
NOD1Part nod1 = new NOD1Part();
- for (RouteNode node : nodes.values())
+ for (RouteNode node : nodes.values()) {
+ if(adjustTurnHeadings)
+ node.tweezeArcs();
nod1.addNode(node);
+ }
centers = nod1.subdivide();
}
diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
index f05829d..0e0e4fe 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -975,6 +975,11 @@ public class StyledConverter implements OsmConverter {
MapRoad road = new MapRoad(way.getId(), line);
+ if("roundabout".equals(way.getTag("junction")))
+ road.setRoundabout(true);
+
+ road.setLinkRoad(gt.getType() == 0x08 || gt.getType() == 0x09);
+
// set road parameters.
road.setRoadClass(gt.getRoadClass());
if (way.isBoolTag("oneway")) {
diff --git a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
index a61b37b..9ae86d0 100644
--- a/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
+++ b/src/uk/me/parabola/mkgmap/reader/MapperBasedMapDataSource.java
@@ -114,6 +114,7 @@ public abstract class MapperBasedMapDataSource implements MapDataSource, Configu
public void config(EnhancedProperties props) {
configProps = props;
+ mapper.config(props);
}
protected EnhancedProperties getConfig() {
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev