[mkgmap] 01/06: Imported Upstream version 0.0.0+svn3649

Sebastiaan Couwenberg sebastic at moszumanska.debian.org
Sun Nov 1 11:09:13 UTC 2015


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 <sebastic at 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



More information about the Pkg-grass-devel mailing list