Index: src/uk/me/parabola/imgfmt/app/net/AngleChecker.java
===================================================================
--- src/uk/me/parabola/imgfmt/app/net/AngleChecker.java	(revision 4921)
+++ src/uk/me/parabola/imgfmt/app/net/AngleChecker.java	(working copy)
@@ -20,6 +20,7 @@
 
 import uk.me.parabola.log.Logger;
 import uk.me.parabola.util.EnhancedProperties;
+import uk.me.parabola.mkgmap.reader.osm.FakeIdGenerator;
 
 /**
  * Find sharp angles at junctions. The Garmin routing algorithm doesn't
@@ -68,8 +69,9 @@
 	// If this is >= above, then compactDirs will be used unless other, non-vehicle access angles are sharp
 	private static final float SHARP_DEGREES = COMPACT_DIR_DEGREES;
 	// Experimentation found no benefit in increasing angle beyond 2 "sectors"
-	private static final float MIN_ANGLE = 11.25f; // don't reduce angles to less than this (arbitrary)
-	
+	private static final float MIN_ANGLE = 23f; // don't reduce angles to less than this (1 sector + bit)
+	private static final float STRAIGHT_EPSILON = 3f; // for angle.straightEnough()
+
 	// helper class to collect multiple arcs with (nearly) the same initial headings
 	private class ArcGroup {
 		float initialHeading;
@@ -106,7 +108,27 @@
 		public boolean isForward() {
 			return isForwardTrueCount == arcs.size();
 		}
-		
+
+		public boolean sameWay(ArcGroup other) {
+			for (RoadDef thisRoad : roadDefs)
+				for (RoadDef otherRoad : other.roadDefs) {
+					if (thisRoad.getId() == otherRoad.getId())
+						return true;
+				}
+			return false;
+		}
+
+		public boolean sameName(ArcGroup other) {
+			for (RoadDef thisRoad : roadDefs) {
+				String thisName = thisRoad.getName();
+				if (thisName != null)
+					for (RoadDef otherRoad : other.roadDefs)
+						if (thisName.equals(otherRoad.getName()))
+							return true;
+			}
+			return false;
+		}
+
 		public void modInitialHeading(float modIH) {
 			initialHeading += modIH;
 			if (initialHeading >= 180)
@@ -113,6 +135,7 @@
 				initialHeading -= 360;
 			else if (initialHeading < -180)
 				initialHeading += 360;
+			log.info("modInitialHeading arc from", arcs.get(0).getInitialHeading(), "by", modIH, "to", initialHeading);
 
 			for (RouteArc arc : arcs) {
 				arc.modInitialHeading(modIH);
@@ -183,7 +206,7 @@
 		Iterator<RouteArc> iter = directArcs.listIterator();
 		RouteArc arc1 = iter.next();
 		boolean addArc1 = false;
-		float minAngle = 180;
+		float smallestAngle = 180;
 		while (iter.hasNext() || addArc1) {
 			ArcGroup ag = new ArcGroup();
 			ag.initialHeading = arc1.getInitialHeading();
@@ -198,8 +221,8 @@
 						log.warn("sharp angle < 1° at", node.getCoord(), ",maybe duplicated OSM way with bearing", getCompassBearing(arc1.getInitialHeading()));
 					ag.addArc(arc2);
 				} else {
-					if (angleBetween < minAngle)
-						minAngle = angleBetween;
+					if (angleBetween < smallestAngle)
+						smallestAngle = angleBetween;
 					arc1 = arc2;
 					if (!iter.hasNext())
 						addArc1 = true;
@@ -219,17 +242,41 @@
 			for (RouteArc arc : arcGroups.get(lastInx).arcs)
 				arcGroups.get(0).addArc(arc);
 			arcGroups.remove(lastInx);
-		} else if (angleBetween < minAngle) {
-			minAngle = angleBetween;
+		} else if (angleBetween < smallestAngle) {
+			smallestAngle = angleBetween;
 		}
 		
-		if (minAngle >= SHARP_DEGREES) {
-			if (minAngle >= COMPACT_DIR_DEGREES)
+		if (smallestAngle >= SHARP_DEGREES) {
+			if (smallestAngle >= COMPACT_DIR_DEGREES)
 				// RouteNode default is setUseCompactDirs(false);
 				node.setUseCompactDirs(true);
 			return;
 		}
 
+		if (log.isInfoEnabled()) {
+			log.info("fixSharpAngles_v4 at", node.getCoord(),
+					 "mask", sharpAnglesCheckMask,
+					 "smallestAngle", smallestAngle,
+					 "nArcs", arcs.size(),
+					 "nDirectArcs", directArcs.size(),
+					 "nGroups", arcGroups.size());
+			float lastHeading = directArcs.get(directArcs.size()-1).getInitialHeading() - 360;
+			for (RouteArc arc : directArcs) {
+				RoadDef rd = arc.getRoadDef();
+				log.info("Arc", /*arc,*/
+						 "heading", arc.getInitialHeading(), getCompassBearing(arc.getInitialHeading()),
+						 "angToPrev", arc.getInitialHeading() - lastHeading,
+						 "class", rd.getRoadClass(),
+						 "speed", rd.getRoadSpeed(),
+						 "way", rd.getId(), "isFake", FakeIdGenerator.isFakeId(rd.getId()),
+						 "rdDef", System.identityHashCode(rd),
+						 "name", rd.getName(),
+						 "paved", rd.paved()
+						 );
+				lastHeading = arc.getInitialHeading();
+				}
+		}
+
 		final int n = arcGroups.size();
 		// scan the angles and see what needs attention
 		// Note: This algorithm won't spot and fix a sharp angle where there is a 'no-access' arc between
@@ -237,6 +284,11 @@
 		class AngleAttr {
 			float angle;
 			float minAngle;
+
+			private boolean straightEnough() {
+				return angle > 180-STRAIGHT_EPSILON && angle < 180+STRAIGHT_EPSILON;
+			}
+
 		}
 
 		AngleAttr[] angles = new AngleAttr[n];
@@ -295,10 +347,13 @@
 			else if (ag1.isOneway() && ag2.isOneway() && n == 3) {
 				// both arcs are one-ways, probably the road splits/joins carriageways here
 				ignoredReason = "because it seems to be a flare road";
-			}		
+				// could check that the one-way directions correspond to the driving side
+			}
+			/* I don't think this is a good reason to ignore the sharp angle
 			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()){
 					log.info("sharp angle", aa.angle, "° at", node.getCoord(),
@@ -319,7 +374,7 @@
 			float oldAngle = aa.angle;
 			ArcGroup ag1 = arcGroups.get(i);
 			ArcGroup ag2 = arcGroups.get(i+1 < n ? i+1 : 0);
-			if (log.isInfoEnabled()){
+			if (false /*log.isInfoEnabled()*/){
 				log.info("sharp angle", aa.angle, "° at", node.getCoord(),
 						 "headings", getCompassBearing(ag1.getInitialHeading()), getCompassBearing(ag2.getInitialHeading()),
 						 "speeds", ag1.maxRoadSpeed, ag2.maxRoadSpeed,
@@ -335,14 +390,62 @@
 			float deltaPred = predAA.angle - predAA.minAngle;
 			float deltaNext = nextAA.angle - nextAA.minAngle;
 
+			if (log.isInfoEnabled()) {
+				log.info("Angle", aa.angle,
+						 "between", ag1.getInitialHeading(),
+						 "and", ag2.getInitialHeading(),
+						 "minAngle", aa.minAngle,
+						 "wantedInc", wantedIncrement,
+						 "deltaPred", deltaPred,
+						 "deltaNext", deltaNext);
+			}
+
 			if (deltaNext > 0 && deltaPred > 0) { // can take from both
-				if (ag1.maxRoadClass == ag2.maxRoadClass &&
-					ag1.maxRoadSpeed == ag2.maxRoadSpeed) { // take from both in ratio to available
+				ArcGroup ag0 = null;
+				ArcGroup ag3 = null;
+				int chooseWhich = 0; // -ve to take from prev, +ve take from next
+				// order of {road_class, sameWay, straightness, roadName, road_speed}
+				// clauses can adjusted for priority.
+				// Hoping sameWay before road_class fixing more problems than it might cause
+				if (chooseWhich == 0) {
+					// if the two arcs either side of the sharp angle belong to the same
+					// way then change the arc on the other side hoping to trigger
+					// the correct turn-instruction
+					ag0 = arcGroups.get(i > 0 ? i-1 : n-1);
+					ag3 = n == 3 ? ag0 : arcGroups.get(i+2 < n ? i+2 : i+2-n);
+					if (ag2.sameWay(ag3))
+						chooseWhich = -1;
+					else if (ag1.sameWay(ag0))
+						chooseWhich = +1;
+				}
+				if (chooseWhich == 0)
+					chooseWhich = ag1.maxRoadClass - ag2.maxRoadClass;
+				if (chooseWhich == 0) {
+					// where there is a straight road with a sharp turn-off then just adjust
+					// the turn-off. This is treated as a special case because the general
+					// logic below will adjust the adjust the straight road slightly more
+					// than the turn-off and, in some cases, with compactDirs, turn-instructions
+					if (nextAA.straightEnough())
+						chooseWhich = -1;
+					else if (predAA.straightEnough())
+						chooseWhich = +1;
+				}
+				if (chooseWhich == 0) {
+					// NB set ag0 & 3 if move this before sameWay()
+					// Way might have changed but could still be the same road by name or ref
+					// This is best after straightEnough.
+					if (ag2.sameName(ag3))
+						chooseWhich = -1;
+					else if (ag1.sameName(ag0))
+						chooseWhich = +1;
+				}
+				if (chooseWhich == 0)
+					chooseWhich = ag1.maxRoadSpeed - ag2.maxRoadSpeed;
+
+				if (chooseWhich == 0 ) { // take from both in ratio to available
 					deltaNext = Math.min(deltaNext, wantedIncrement * deltaNext / (deltaNext + deltaPred));
 					deltaPred = Math.min(deltaPred, wantedIncrement - deltaNext);
-			    } else if (ag1.maxRoadClass > ag2.maxRoadClass ||
-						   (ag1.maxRoadClass == ag2.maxRoadClass &&
-							ag1.maxRoadSpeed > ag2.maxRoadSpeed)) { // take as much as poss from next
+				} else if (chooseWhich > 0) { // take as much as poss from next
 					if (deltaNext >= wantedIncrement) {
 						deltaNext = wantedIncrement;
 						deltaPred = 0;
@@ -349,7 +452,7 @@
 					} else {
 						deltaPred = Math.min(deltaPred, wantedIncrement - deltaNext);
 					}
-				} else  { // take as much as possible from pred
+				} else { // take as much as possible from pred
 					if (deltaPred >= wantedIncrement) {
 						deltaPred = wantedIncrement;
 						deltaNext = 0;
@@ -389,12 +492,12 @@
 		}
 
 		// see what the smallest angle is now; might be some that couldn't fix and some that didn't matter
-		minAngle = 180;
+		smallestAngle = 180;
 		for (int i=0; i < n; ++i) {
-			if (angles[i].angle < minAngle)
-				minAngle = angles[i].angle;
+			if (angles[i].angle < smallestAngle)
+				smallestAngle = angles[i].angle;
 		}
-		if (minAngle >= COMPACT_DIR_DEGREES)
+		if (smallestAngle >= COMPACT_DIR_DEGREES)
 			node.setUseCompactDirs(true);
 	}
 
