Index: src/uk/me/parabola/imgfmt/app/net/RouteArc.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/net/RouteArc.java	(revision 3028)
+++ src/uk/me/parabola/imgfmt/app/net/RouteArc.java	(working copy)
@@ -41,8 +41,10 @@
 
 	private int offset;
 
-	private int initialHeading; // degrees
-	private final int finalHeading; // degrees
+	// heading / bearing: 
+	private float initialHeading; // degrees (A-> B in an arc ABCD) 
+	private final float finalHeading; // degrees (D-> D in an arc ABCD)
+	private final float directHeading; // degrees (A-> D in an arc ABCD)
 
 	private final RoadDef roadDef;
 
@@ -60,20 +62,26 @@
 
 	private boolean haveCurve;
 	private int length;
+	private byte lengthRatio;
 	private final int pointsHash;
 	private final boolean curveEnabled;
 
 	/**
-	 * Create a new arc.
-	 *
+	/**
+	 * Create a new arc. An arc can contain multiple points (eg. A->B->C->D->E)
 	 * @param roadDef The road that this arc segment is part of.
-	 * @param source The source node.
-	 * @param dest The destination node.
-	 * @param initialHeading The initial heading (signed degrees)
+	 * @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 length the length of the arc in meter 
+	 * @param curveEnabled
+	 * @param pointsHash
 	 */
 	public RouteArc(RoadDef roadDef,
 					RouteNode source, RouteNode dest,
-					int initialHeading, int finalHeading,
+					double initialBearing, double finalBearing, double directBearing,
 					double length,
 					boolean curveEnabled,
 					int pointsHash) {
@@ -80,22 +88,27 @@
 		this.roadDef = roadDef;
 		this.source = source;
 		this.dest = dest;
-		this.initialHeading = initialHeading;
-		this.finalHeading = finalHeading;
-		this.length = convertMeters(length);
+		this.initialHeading = (float) initialBearing;
+		this.finalHeading = (float) finalBearing;
+		this.directHeading = (float) directBearing;
+		this.length = convertMeters(length); 
 		this.curveEnabled = curveEnabled;
 		this.pointsHash = pointsHash;
+		int encodedDirectLength = convertMeters(source.getCoord().distance(dest.getCoord()));
+		
+		lengthRatio = (byte) ((int)Math.round(32.0 * encodedDirectLength / length) & 0x1f); 		
+		haveCurve = curveEnabled && lengthRatio > 0;
 	}
 
-	public int getInitialHeading() {
+	public float getInitialHeading() {
 		return initialHeading;
 	}
 
-	public void setInitialHeading(int ih) {
+	public void setInitialHeading(float ih) {
 		initialHeading = ih;
 	}
 
-	public int getFinalHeading() {
+	public float getFinalHeading() {
 		return finalHeading;
 	}
 
@@ -218,7 +231,7 @@
 		for (int aLendat : lendat)
 			writer.put((byte) aLendat);
 
-		writer.put((byte)(256 * initialHeading / 360));
+		writer.put((byte)(initialHeading * 256 / 360));
 
 		if (haveCurve) {
 			int[] curvedat = encodeCurve();
@@ -258,10 +271,6 @@
 	 * There's even more different encodings supposedly.
 	 */
 	private int[] encodeLength() {
-
-		// update haveCurve
-		haveCurve = (curveEnabled && finalHeading != initialHeading);
-
 		if (length >= (1 << 22)) {
 			log.error("Way " + roadDef.getName() + " (id " + roadDef.getId() + ") contains an arc whose length (" + length + " units) is too big to be encoded so the way might not be routable");
 			length = (1 << 22) - 1;
@@ -308,31 +317,28 @@
 
 	/**
 	 * Encode the curve data into a sequence of bytes.
-	 *
-	 * 1 or 2 bytes show up in practice, but they're not at
-	 * all well understood yet.
+	 * Curve data contains a ratio between arc length and direct distance
+	 * and the direct bearing. This is typically encode in one byte, 
+	 * for extreme ratios two bytes are used.
 	 */
 	private int[] encodeCurve() {
-		// 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;
+		assert lengthRatio != 0;
+		int[] curveData;
+		if (lengthRatio >= 1 && lengthRatio <= 17) {
+			// two byte curve data neeeded
+			curveData = new int[2];
+			curveData[0] = lengthRatio;
+			curveData[1] = ((int) (directHeading * 256 / 360));
+		} else {
+			// use compacted form
+			int compactedRatio = lengthRatio / 2 - 8;
+			if (compactedRatio == -8)
+				compactedRatio = 7;
+			curveData = new int[1];
+			curveData[0] = ((compactedRatio << 5) | (int) (directHeading * 32 / 360) & 0xff);
+			if ((curveData[0] & 0xe0) == 0)
+				log.error("failed to encode length ratio", lengthRatio);
 		}
-		int[] curveData = new int[1];
-		curveData[0] = encodedHeading;
 		return curveData;
 	}
 
Index: src/uk/me/parabola/imgfmt/app/net/RouteNode.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/net/RouteNode.java	(revision 3028)
+++ src/uk/me/parabola/imgfmt/app/net/RouteNode.java	(working copy)
@@ -335,7 +335,7 @@
 		return false;
 	}
 
-	private static boolean rightTurnRequired(int inHeading, int outHeading, int sideHeading) {
+	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
@@ -346,13 +346,13 @@
 		while(outHeading > 180)
 			outHeading -= 360;
 
-		sideHeading -= inHeading;
-		while(sideHeading < -180)
-			sideHeading += 360;
-		while(sideHeading > 180)
-			sideHeading -= 360;
+		otherHeading -= inHeading;
+		while(otherHeading < -180)
+			otherHeading += 360;
+		while(otherHeading > 180)
+			otherHeading -= 360;
 
-		return sideHeading > outHeading;
+		return otherHeading > outHeading;
 	}
 
 	private static final int ATH_OUTGOING = 1;
@@ -414,7 +414,7 @@
 					continue;
 				}
 
-				int inHeading = inArc.getFinalHeading();
+				float inHeading = inArc.getFinalHeading();
 				// determine the outgoing arc that is likely to be the
 				// same road as the incoming arc
 				RouteArc outArc = null;
@@ -507,8 +507,8 @@
 				// remember that this arc is an outgoing arc
 				outgoingArcs.add(outArc);
 
-				int outHeading = outArc.getInitialHeading();
-				int mainHeadingDelta = outHeading - inHeading;
+				float outHeading = outArc.getInitialHeading();
+				float mainHeadingDelta = outHeading - inHeading;
 				while(mainHeadingDelta > 180)
 					mainHeadingDelta -= 360;
 				while(mainHeadingDelta < -180)
@@ -561,19 +561,19 @@
 						continue;
 					}
 
-					int otherHeading = otherArc.getInitialHeading();
-					int outToOtherDelta = otherHeading - outHeading;
+					float otherHeading = otherArc.getInitialHeading();
+					float outToOtherDelta = otherHeading - outHeading;
 					while(outToOtherDelta > 180)
 						outToOtherDelta -= 360;
 					while(outToOtherDelta < -180)
 						outToOtherDelta += 360;
-					int inToOtherDelta = otherHeading - inHeading;
+					float inToOtherDelta = otherHeading - inHeading;
 					while(inToOtherDelta > 180)
 						inToOtherDelta -= 360;
 					while(inToOtherDelta < -180)
 						inToOtherDelta += 360;
 
-					int newHeading = otherHeading;
+					float newHeading = otherHeading;
 					if(rightTurnRequired(inHeading, outHeading, otherHeading)) {
 						// side road to the right
 						if((mask & ATH_OUTGOING) != 0 &&
@@ -581,7 +581,7 @@
 							newHeading = outHeading + MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS;
 						if((mask & ATH_INCOMING) != 0 &&
 						   Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) {
-							int nh = inHeading + MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
+							float nh = inHeading + MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
 							if(nh > newHeading)
 								newHeading = nh;
 						}
@@ -596,7 +596,7 @@
 							newHeading = outHeading - MIN_DIFF_BETWEEN_OUTGOING_AND_OTHER_ARCS;
 						if((mask & ATH_INCOMING) != 0 &&
 						   Math.abs(inToOtherDelta) < MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS) {
-							int nh = inHeading - MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
+							float nh = inHeading - MIN_DIFF_BETWEEN_INCOMING_AND_OTHER_ARCS;
 							if(nh < newHeading)
 								newHeading = nh;
 						}
Index: src/uk/me/parabola/mkgmap/general/RoadNetwork.java
===================================================================
--- src/uk/me/parabola/mkgmap/general/RoadNetwork.java	(revision 3028)
+++ src/uk/me/parabola/mkgmap/general/RoadNetwork.java	(working copy)
@@ -135,40 +135,46 @@
 					log.warn("Road " + road.getRoadDef() + " contains zero length arc at " + co.toOSMURL());
 
 
-				Coord bearingPoint = coordList.get(lastIndex + 1);
-				if(lastCoord.equals(bearingPoint)) {
+				Coord forwardBearingPoint = coordList.get(lastIndex + 1);
+				if(lastCoord.equals(forwardBearingPoint)) {
 					// 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);
+							forwardBearingPoint = 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)) {
+				Coord reverseBearingPoint = coordList.get(index - 1);
+				if(co.equals(reverseBearingPoint)) {
 					// 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);
+							reverseBearingPoint = coordList.get(bi);
 							break;
 						}
 					}
 				}
-				int reverseBearing = (int)co.bearingTo(bearingPoint);
-				int inverseReverseBearing = (int)bearingPoint.bearingTo(co);
+				
+				double forwardInitialBearing = lastCoord.bearingTo(forwardBearingPoint);
+				double forwardDirectBearing = (co == forwardBearingPoint) ? forwardInitialBearing: lastCoord.bearingTo(co); 
 
+				double reverseInitialBearing = co.bearingTo(reverseBearingPoint);
+				double reverseDirectBearing = (lastCoord == reverseBearingPoint) ? reverseInitialBearing: co.bearingTo(lastCoord); 
+
+				// TODO: maybe detect cases where bearing was already calculated above 
+				double forwardFinalBearing = reverseBearingPoint.bearingTo(co); 
+				double reverseFinalBearing = forwardBearingPoint.bearingTo(lastCoord);
+
 				// Create forward arc from node1 to node2
 				RouteArc arc = new RouteArc(road.getRoadDef(),
 											node1,
 											node2,
-											forwardBearing,
-											inverseReverseBearing,
+											forwardInitialBearing,
+											forwardFinalBearing,
+											forwardDirectBearing,
 											arcLength,
 											outputCurveData,
 											pointsHash);
@@ -179,8 +185,9 @@
 				// Create the reverse arc
 				arc = new RouteArc(road.getRoadDef(),
 								   node2, node1,
-								   reverseBearing,
-								   inverseForwardBearing,
+								   reverseInitialBearing,
+								   reverseFinalBearing,
+								   reverseDirectBearing,
 								   arcLength,
 								   outputCurveData,
 								   pointsHash);
