v4 - now writes arc curve data that encodes the final heading of an arc.
This should yield better quality routing instructions when an arc's
final heading differs greatly from its initial heading.

At this time, only single byte curve data is being written and the
format of that is not 100% confirmed but it's looking plausible
(possible doubt over ordering of lower 3 bits).

The --adjust-turn-headings option does not enable/disable the writing
of curve data. If you want to disable the writing of curve data use the
temporary option --no-arc-curves. This option will probably disappear
when the curve data has proven to be reliable.

I have tested this with the GB map and it didn't break the routing.
I was getting good quality routing instructions (with the
--adjust-turn-headings option).

All feedback appreciated.

--------------------------

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..126cb77 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;
 
@@ -60,8 +60,10 @@ public class RouteArc {
 	private byte flagA;
 	private byte flagB;
 
-	private boolean curve;
+	private boolean haveCurve;
 	private int length;
+	private final int pointsHash;
+	private final boolean curveEnabled;
 
 	/**
 	 * Create a new arc.
@@ -69,34 +71,38 @@ 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,
+					boolean curveEnabled,
+					int pointsHash) {
 		this.roadDef = roadDef;
 		this.source = source;
 		this.dest = dest;
-
+		this.initialHeading = initialHeading;
+		this.finalHeading = finalHeading;
 		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);
-		// too early: dest.nodeClass may still increase
-		//setDestinationClass(dest.getNodeClass());
+		this.curveEnabled = curveEnabled;
+		this.pointsHash = pointsHash;
+	}
+
+	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() {
@@ -107,15 +113,26 @@ public class RouteArc {
 		return dest;
 	}
 
+	public int getLength() {
+		return length;
+	}
+
+	public int getPointsHash() {
+		return pointsHash;
+	}
+
 	/**
 	 * Provide an upper bound for the written size in bytes.
 	 */
 	public int boundSize() {
-		// XXX: this could be reduced, and may increase
-		// currently: 1 (flagA) + 1-2 (offset) + 1 (indexA)
-		//          + 2 (length) + 1 (initialHeading)
-		// needs updating when curve data is written
-		return 7;
+
+		int[] lendat = encodeLength();
+
+		// 1 (flagA) + 1-2 (offset) + 1 (indexA) + 1 (initialHeading)
+		int size = 5 + lendat.length;
+		if(haveCurve)
+			size += encodeCurve().length;
+		return size;
 	}
 
 	/**
@@ -167,23 +184,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,9 +222,9 @@ public class RouteArc {
 		for (int aLendat : lendat)
 			writer.put((byte) aLendat);
 
-		writer.put(initialHeading);
+		writer.put((byte)(256 * initialHeading / 360));
 
-		if (curve) {
+		if (haveCurve) {
 			int[] curvedat = encodeCurve();
 			for (int aCurvedat : curvedat)
 				writer.put((byte) aCurvedat);
@@ -262,18 +262,41 @@ public class RouteArc {
 	 * There's even more different encodings supposedly.
 	 */
 	private int[] encodeLength() {
-		// we'll just use a special encoding with curve=false for
-		// now, 14 bits for length
-		assert !curve : "not writing curve data yet";
+
+		// update haveCurve
+		haveCurve = (curveEnabled && finalHeading != initialHeading);
+
 		if (length >= (1 << 14)) {
 			log.error("Way " + roadDef.getName() + " (id " + roadDef.getId() + ") contains an arc whose length is too big to be encoded so the road will not be routable");
 			length = (1 << 14) - 1;
 		}
 
-		flagA |= 0x38; // all three bits set
-		int[] lendat = new int[2]; // two bytes of data
-		lendat[0] = 0x80 | (length & 0x3f); // 0x40 not set, 6 low bits of length
-		lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
+		// clear existing bits in case length or final heading have
+		// been changed
+		flagA &= ~0x38;
+		int[] lendat;
+		if(length < 0x200) {
+			// 9 bit length optional curve
+			if(haveCurve)
+				flagA |= 0x20;
+			flagA |= (length >> 5) & 0x08; // top bit of length
+			lendat = new int[1];		   // one byte of data
+			lendat[0] = length;			   // bottom 8 bits of length
+		}
+		else if(haveCurve) {
+			// 15 bit length with curve
+			flagA |= 0x38;				 // all three bits set
+			lendat = new int[2];		 // two bytes of data
+			lendat[0] = (length & 0x7f); // 0x80 not set, 7 low bits of length
+			lendat[1] = (length >> 7) & 0xff; // 8 more bits of length
+		}
+		else {
+			// 14 bit length no curve
+			flagA |= 0x38;		 // all three bits set
+			lendat = new int[2]; // two bytes of data
+			lendat[0] = 0x80 | (length & 0x3f); // 0x80 set, 0x40 not set, 6 low bits of length
+			lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
+		}
 
 		return lendat;
 	}
@@ -285,8 +308,27 @@ public class RouteArc {
 	 * all well understood yet.
 	 */
 	private int[] encodeCurve() {
-		assert !curve : "not writing curve data yet";
-		return null;
+		// most examples of curve data are a single byte that encodes
+		// the final heading of the arc. The bits appear to be
+		// reorganised into the order 21076543 (i.e. the top 5 bits
+		// are shifted down to the bottom).  Unfortunately, it's not
+		// that simple because sometimes the curve is encoded using 2
+		// bytes. The presence of the 2nd byte is indicated by the top
+		// 3 bits of the first byte all being zero. As the encoding of
+		// the 2-byte variant is not yet understood, for the moment,
+		// if the resulting value would have the top 3 bits all zero,
+		// we set what we hope is the LSB so that it becomes valid
+		// 1-byte curve data
+		int heading = 256 * finalHeading / 360;
+		int encodedHeading = ((heading & 0xf8) >> 3) | ((heading & 0x07) << 5);
+		if((encodedHeading & 0xe0) == 0) {
+			// hack - set a bit (hopefully, the LSB) to force 1-byte
+			// encoding
+			encodedHeading |= 0x20;
+		}
+		int[] curveData = new int[1];
+		curveData[0] = encodedHeading;
+		return curveData;
 	}
 
 	public RoadDef getRoadDef() {
@@ -314,13 +356,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..3da20d3 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,183 @@ 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());
+					}
+				}
+			}
+		}
+	}
+
+	public void reportSimilarArcs() {
+		for(int i = 0; i < arcs.size(); ++i) {
+			RouteArc arci = arcs.get(i);
+			for(int j = i + 1; j < arcs.size(); ++j) {
+				RouteArc arcj = arcs.get(j);
+				if(arci.getDest() == arcj.getDest() &&
+				   arci.getLength() == arcj.getLength() &&
+				   arci.getPointsHash() == arcj.getPointsHash()) {
+					log.warn("Similar arcs (" + arci.getRoadDef() + " and " + arcj.getRoadDef() + ") from " + 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..c480b68 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,15 @@ public class RoadNetwork {
 
 	private final List<RoadDef> roadDefs = new ArrayList<RoadDef>();
 	private List<RouteCenter> centers = new ArrayList<RouteCenter>();
+	private boolean adjustTurnHeadings;
+	private boolean reportSimilarArcs;
+	private boolean outputCurveData;
+
+	public void config(EnhancedProperties props) {
+		adjustTurnHeadings = props.getProperty("adjust-turn-headings", false);
+		reportSimilarArcs = props.getProperty("report-similar-arcs", false);
+		outputCurveData = !props.getProperty("no-arc-curves", false);
+	}
 
 	public void addRoad(MapRoad road) {
 		//mapRoads.add(road);
@@ -69,6 +79,7 @@ public class RoadNetwork {
 		int lastIndex = 0;
 		double roadLength = 0;
 		double arcLength = 0;
+		int pointsHash = 0;
 
 		List<Coord> coordList = road.getPoints();
 		int npoints = coordList.size();
@@ -82,11 +93,14 @@ public class RoadNetwork {
 			}
 
 			long id = co.getId();
+
+			pointsHash += co.hashCode();
+
 			if (id == 0)
 				// not a routing node
 				continue;
 
-			// The next coord determins the heading
+			// The next coord determines the heading
 			// If this is the not the first node, then create an arc from
 			// the previous node to this one (and back again).
 			if (lastCoord != null) {
@@ -110,18 +124,57 @@ 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,
+											outputCurveData,
+											pointsHash);
 				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,
+								   outputCurveData,
+								   pointsHash);
+				node2.addArc(arc);
+				node1.addIncomingArc(arc);
 			} else {
 				// This is the first node in the road
 				road.getRoadDef().setNode(getNode(id, co));
@@ -130,6 +183,7 @@ public class RoadNetwork {
 			lastCoord = (CoordNode) co;
 			lastIndex = index;
 			arcLength = 0;
+			pointsHash = co.hashCode();
 		}
 		road.getRoadDef().setLength(roadLength);
 	}
@@ -162,8 +216,13 @@ public class RoadNetwork {
 
 		NOD1Part nod1 = new NOD1Part();
 
-		for (RouteNode node : nodes.values())
+		for (RouteNode node : nodes.values()) {
+			if(reportSimilarArcs)
+				node.reportSimilarArcs();
+			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 5e8e3f3..a47803f 100644
--- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
+++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java
@@ -991,6 +991,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

Reply via email to