Attached patch improves the RoadMerger so that roads are reversed when it is required to be merged with another road.

A small test increased the mergerate by 2% (avg. 17% => 19% road network reduction).

Please check it. The p-road check is not yet implemented.
There are also some performance improvements possible which I will post with the next patch version.
Unit tests may fail.

WanMil

Hi Gerd,

Hi WanMil,

two points:
1) line 517 is obsolete:
mergePoints.add(end);
It just blows up the size of the list and processing time.

Yep.
I've found another important thing: the road merger can merge many more
ways when it reverses non oneway ways. This should be no problem so
let's do it :-)
I will post another patch.

Index: src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java	(revision 2954)
+++ src/uk/me/parabola/mkgmap/osmstyle/RoadMerger.java	(working copy)
@@ -15,10 +15,10 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -49,10 +49,8 @@
 	/** Contains a list of all roads (GType + Way) */
 	private final List<Road> roads;
 
-	/** maps the start point of a road to its road definition */
-	private final MultiIdentityHashMap<Coord, Road> startPoints = new MultiIdentityHashMap<Coord, Road>();
-	/** maps the end point of a road to its road definition */
-	private final MultiIdentityHashMap<Coord, Road> endPoints = new MultiIdentityHashMap<Coord, Road>();
+	/** maps the two end points of a road to its road definition */
+	private final MultiIdentityHashMap<Coord, Road> roadEndPoints = new MultiIdentityHashMap<Coord, Road>();
 
 	/**
 	 * Helper class to keep the Way and the GType object of a road. 
@@ -61,7 +59,7 @@
 	 * 
 	 * @author WanMil
 	 */
-	private static class Road {
+	private static class Road implements Comparable<Road> {
 		/** gives the index of the original position in the way/road list */
 		private final int index;
 		private final Way way;
@@ -139,6 +137,7 @@
 			Coord cEnd = way.getPoints().get(way.getPoints().size() - 1);
 			if (cStart != mergePoint && cEnd != mergePoint) {
 				// it doesn't => roads not mergeable at mergePoint
+				log.debug("Merge point not at start/end of",way.getId());
 				return false;
 			}
 
@@ -149,17 +148,20 @@
 			if (cOtherStart != mergePoint && cOtherEnd != mergePoint) {
 				// otherRoad does not start or stop at mergePoint =>
 				// roads not mergeable at mergePoint
+				log.debug("Merge point not at start/end of",otherRoad.way.getId());
 				return false;
 			}
 
 			// check if merging would create a closed way - which should not
 			// be done (why? WanMil)
-			if (cStart == cOtherEnd) {
+			if ((cStart == cOtherEnd && cStart != mergePoint) || (cEnd == cOtherStart && cEnd != mergePoint)) {
+				log.debug("Do not merge",way.getId(),"and",otherRoad.way.getId(),"- closed way");
 				return false;
 			}
 			
 			// check if the GType objects are the same
 			if (isGTypeMergable(otherRoad.getGtype()) == false) {
+				log.debug("Do not merge",way.getId(),"and",otherRoad.way.getId(),"- different gtype");
 				return false;
 			}
 			
@@ -252,7 +254,7 @@
 				
 				if (thisStart == otherStart) {
 					// both ways are oneway but they have a different direction
-					log.warn("oneway with different direction", way.getId(),
+					log.debug("oneway with different direction", way.getId(),
 							otherWay.getId());
 					return false;
 				}
@@ -322,10 +324,10 @@
 				// restriction is quite low and there have been requests
 				// for this: http://www.mkgmap.org.uk/pipermail/mkgmap-dev/2013q3/018649.html
 				
-				log.info("Do not merge ways",getWay().getId(),"and",otherWay.getId(),"because they span a too big angle",angle,"°");
+				log.debug("Do not merge ways",getWay().getId(),"and",otherWay.getId(),"because they span a too big angle",angle,"°");
 				return false;
 			}
-
+			
 			return true;
 		}
 
@@ -358,6 +360,10 @@
 		public final int getIndex() {
 			return index;
 		}
+
+		public int compareTo(Road otherRoad) {
+			return Integer.compare(index, otherRoad.index);
+		}
 	}
 
 	public RoadMerger(List<Way> ways, List<GType> gtypes,
@@ -367,9 +373,10 @@
 
 		this.roads = new ArrayList<Road>(ways.size());
 
+		int roadIndex = 0;
 		for (int i = 0; i < ways.size(); i++) {
 			if (ways.get(i) != null)
-				roads.add(new Road(i, ways.get(i), gtypes.get(i)));
+				roads.add(new Road(roadIndex++, ways.get(i), gtypes.get(i)));
 		}
 
 		this.restrictions = new MultiIdentityHashMap<Coord, Long>();
@@ -436,7 +443,7 @@
 	}
 
 	private boolean hasRestriction(Coord c, Way w) {
-		List<Long> wayRestrictions = restrictions.get(c);
+		Set<Long> wayRestrictions = restrictions.get(c);
 		return wayRestrictions.contains(w.getId());
 	}
 
@@ -446,24 +453,37 @@
 	 * For the tag it should not matter because all tags used after the
 	 * RoadMerger are compared to be the same.
 	 * 
+	 * @param mergePoint merge the roads at this point
 	 * @param road1 first road (will keep the merged road)
 	 * @param road2 second road
 	 */
-	private void mergeRoads(Road road1, Road road2) {
+	private void mergeRoads(Coord mergePoint, Road road1, Road road2) {
 		// Removes the second line,
 		// Merges the points in the first one
+
+		// Remove the second road from the roads list
+		roads.set(road2.getIndex(), null);
+
 		List<Coord> points1 = road1.getWay().getPoints();
+		if (mergePoint == points1.get(0)) {
+			road1.getWay().reverse();
+			assert road1.getWay().isBoolTag("oneway") == false;
+		}
+
 		List<Coord> points2 = road2.getWay().getPoints();
+		if (mergePoint != points2.get(0)) {
+			road2.getWay().reverse();
+			assert road2.getWay().isBoolTag("oneway") == false;
+		}
 
-		Coord mergePoint = points2.get(0);
 		Coord endPoint= points2.get(points2.size()-1);
 		
-		startPoints.remove(mergePoint, road2);
-		endPoints.remove(endPoint, road2);
-		endPoints.remove(mergePoint, road1);
+		roadEndPoints.remove(mergePoint, road2);
+		roadEndPoints.remove(endPoint, road2);
+		roadEndPoints.remove(mergePoint, road1);
 
 		points1.addAll(points2.subList(1, points2.size()));
-		endPoints.add(endPoint, road1);
+		roadEndPoints.add(endPoint, road1);
 		
 		// merge the POI info
 		String wayPOI2 = road2.getWay().getTag(StyledConverter.WAY_POI_NODE_IDS);
@@ -486,6 +506,24 @@
 		
 	}
 
+	private static class MergeClassification implements Comparable<MergeClassification> {
+		Road road1;
+		Road road2;
+		double angle;
+		
+		public int compareTo(MergeClassification otherMC) {
+			if (this == otherMC) {
+				return 0;
+			}
+			int angleCmp = Double.compare(angle, otherMC.angle);
+			if (angleCmp != 0) {
+				return angleCmp;
+			}
+			
+			return Integer.compare(road1.getIndex(), otherMC.road1.getIndex());
+		}
+	}
+	
 	/**
 	 * Merge the roads and copy the results to the given lists.
 	 * @param resultingWays list for the merged (and not mergeable) ways
@@ -495,28 +533,25 @@
 
 		int noRoadsBeforeMerge = this.roads.size();
 		int noMerges = 0;
-		List<Road> roadsToMerge = new ArrayList<Road>(this.roads);
-		this.roads.clear();
-		
 		List<Coord> mergePoints = new ArrayList<>();
 
 		// first add all roads with their start and end points to the
 		// start/endpoint lists
-		for (Road road : roadsToMerge) {
+		for (Road road : this.roads) {
 			List<Coord> points = road.getWay().getPoints();
 			Coord start = points.get(0);
 			Coord end = points.get(points.size() - 1);
 
 			if (start == end) {
 				// do not merge closed roads
-				roads.add(road);
+				log.debug("Do not merge closed road", road.getWay().getId());
 				continue;
 			}
 
 			mergePoints.add(start);
 			mergePoints.add(end);
-			startPoints.add(start, road);
-			endPoints.add(end, road);
+			roadEndPoints.add(start, road);
+			roadEndPoints.add(end, road);
 		}
 
 		// a set of all points where no more merging is possible
@@ -525,42 +560,61 @@
 		// go through all start/end points and check if a merge is possible
 		for (Coord mergePoint : mergePoints) {
 			if (mergeCompletedPoints.contains(mergePoint)) {
-				// a previous run did not show any possible merge
+				// a previous run already processed the point
 				// do not check again
 				continue;
 			}
 			
-			// get all road that start with the merge point
-			List<Road> startRoads = startPoints.get(mergePoint);
-			// get all roads that end with the merge point
-			List<Road> endRoads = endPoints.get(mergePoint);
+			// get all road that start or end with the merge point
+			Set<Road> startRoads = roadEndPoints.get(mergePoint);
 			
-			if (endRoads.isEmpty() || startRoads.isEmpty()) {
-				// this might happen if another merge operation changed endPoints and/or startPoints
+			if (startRoads.size() < 2) {
+				// there is less than two roads => no merge possible
+				mergeCompletedPoints.add(mergePoint);
+				continue;
+			}
+			
+			ArrayList<Road> mergeableRoads = new ArrayList<>(startRoads.size());
+			for (Road road : startRoads) {
+				if (hasRestriction(mergePoint, road.getWay())) {
+					if (log.isDebugEnabled())
+						log.debug("Do not merge road", road.getWay().getId(), "at point", mergePoint, "because there is a restriction.");
+				} else {
+					mergeableRoads.add(road);
+				}
+			}
+			
+			// check again how many roads are known
+			if (mergeableRoads.size() < 2) {
+				// there is less than two roads => no merge possible
 				mergeCompletedPoints.add(mergePoint);
 				continue;
 			}
 			
 			// go through all combinations and test which combination is the best
-			double bestAngle = Double.MAX_VALUE;
-			Road mergeRoad1 = null;
-			Road mergeRoad2 = null;
+			List<MergeClassification> possibleMerges = new ArrayList<>(mergeableRoads.size()/2);
 			
-			for (Road road1 : endRoads) {
-				// check if the road has a restriction at the merge point
-				// which does not allow us to merge the road at this point
-				if (hasRestriction(mergePoint, road1.getWay())) {
-					continue;
-				}
-				
+			for (int firstIndex = 0; firstIndex < mergeableRoads.size(); firstIndex++) {
+				Road road1 = mergeableRoads.get(firstIndex);
 				List<Coord> points1 = road1.getWay().getPoints();
 				
+				int startCheckAt = firstIndex+1;
+				if (road1.getWay().isBoolTag("oneway")) {
+					// if the way is oneway all other ways need to be check
+					// because for oneway roads 
+					startCheckAt = 0;
+				}
+				
 				// go through all candidates to merge
-				for (Road road2 : startRoads) {
-					if (hasRestriction(mergePoint, road2.getWay())) {
+				for (int secondIndex = startCheckAt; secondIndex < mergeableRoads.size(); secondIndex++) {
+					if (secondIndex == firstIndex) {
 						continue;
 					}
+					
+					Road road2 = mergeableRoads.get(secondIndex);
 					List<Coord> points2 = road2.getWay().getPoints();
+
+					log.debug("Check if road",road1.getWay().getId(),"and",road2.getWay().getId(),"can be merged");
 					
 					// the second road is merged into the first road
 					// so only the id of the first road is kept
@@ -575,49 +629,81 @@
 						// yes they might be merged
 						// calculate the angle between them 
 						// if there is more then one road to merge the one with the lowest angle is merged 
-						double angle = Math.abs(Utils.getAngle(points1.get(points1.size()-2), mergePoint, points2.get(1)));
+						Coord c1 = null;
+						if (mergePoint == points1.get(0)) {
+							c1 = points1.get(1);
+						} else {
+							c1 = points1.get(points1.size()-2);
+						}
+						Coord c2 = null;
+						if (mergePoint == points2.get(0)) {
+							c2 = points2.get(1);
+						} else {
+							c2 = points2.get(points2.size()-2);
+						}
+						
+						double angle = Math.abs(Utils.getAngle(c1, mergePoint, c2));
 						log.debug("Road",road1.getWay().getId(),"and road",road2.getWay().getId(),"are mergeable with angle",angle);
-						if (angle < bestAngle) {
-							mergeRoad1 = road1;
-							mergeRoad2 = road2;
-							bestAngle = angle;
-						} 
+						MergeClassification mergeClassification = new MergeClassification();
+						mergeClassification.angle = angle;
+						mergeClassification.road1 = road1;
+						mergeClassification.road2 = road2;
+						possibleMerges.add(mergeClassification);
 					}
 				}
 			}
 			
-			// is there a pair of roads that can be merged?
-			if (mergeRoad1 != null && mergeRoad2 != null) {
-				// yes!! => merge them
-				log.debug("Merge",mergeRoad1.getWay().getId(),"and",mergeRoad2.getWay().getId(),"with angle",bestAngle);
-				mergeRoads(mergeRoad1, mergeRoad2);
-				noMerges++;
-			} else {
-				// no => do not check again this point again
-				mergeCompletedPoints.add(mergePoint);
+			Collections.sort(possibleMerges);
+			while (possibleMerges.isEmpty() == false) {
+				MergeClassification bestMerge = possibleMerges.remove(0);
+				
+				// remove all other merges with one of both roads
+				ListIterator<MergeClassification> otherMerges = possibleMerges.listIterator();
+				while (otherMerges.hasNext()) {
+					MergeClassification nextMerge = otherMerges.next();
+					if (nextMerge.road1 == bestMerge.road1 || 
+						nextMerge.road1 == bestMerge.road2 || 
+						nextMerge.road2 == bestMerge.road1 || 
+						nextMerge.road2 == bestMerge.road2) {
+						// these roads cannot be merge twice at the same point 
+						// so the nextMerge is not possible => remove it
+						otherMerges.remove();
+					}
+				}
+				
+				// perform the bestMerge
+					log.debug("Merge",bestMerge.road1.getWay().getId(),"and",bestMerge.road2.getWay().getId(),"with angle",bestMerge.angle);
+					mergeRoads(mergePoint, bestMerge.road1, bestMerge.road2);
+					noMerges++;
 			}
+			// the point is fully merged => do not process again
+			mergeCompletedPoints.add(mergePoint);
 		}
 
-		// copy all merged roads to the roads list
-		for (List<Road> mergedRoads : endPoints.values()) {
-			this.roads.addAll(mergedRoads);
-		}
+//		// copy all merged roads to the roads list
+//		for (Set<Road> mergedRoads : roadEndPoints.values()) {
+//			this.roads.addAll(mergedRoads);
+//		}
 
-		// sort the roads to ensure that the order of roads is constant for two runs
-		Collections.sort(this.roads, new Comparator<Road>() {
-			public int compare(Road o1, Road o2) {
-				return Integer.compare(o1.getIndex(), o2.getIndex());
-			}
-		});
+//		// sort the roads to ensure that the order of roads is constant for two runs
+//		Collections.sort(this.roads, new Comparator<Road>() {
+//			public int compare(Road o1, Road o2) {
+//				return Integer.compare(o1.getIndex(), o2.getIndex());
+//			}
+//		});
+		
 		
 		// copy the roads to the resulting lists
 		for (Road r : roads) {
+			if (r == null) {
+				continue;
+			}
 			resultingWays.add(r.getWay());
 			resultingGTypes.add(r.getGtype());
 		}
 		
 		// print out some statistics
-		int noRoadsAfterMerge = this.roads.size();
+		int noRoadsAfterMerge = resultingWays.size();
 		log.info("Roads before/after merge:", noRoadsBeforeMerge, "/",
 				noRoadsAfterMerge);
 		int percentage = (int) Math.round((noRoadsBeforeMerge - noRoadsAfterMerge) * 100.0d
Index: src/uk/me/parabola/util/MultiIdentityHashMap.java
===================================================================
--- src/uk/me/parabola/util/MultiIdentityHashMap.java	(revision 2949)
+++ src/uk/me/parabola/util/MultiIdentityHashMap.java	(working copy)
@@ -13,39 +13,38 @@
 
 package uk.me.parabola.util;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.IdentityHashMap;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 
 
-public class MultiIdentityHashMap<K,V> extends IdentityHashMap<K,List<V>> {
+public class MultiIdentityHashMap<K,V> extends IdentityHashMap<K,Set<V>> {
 
 	/**
-	* the empty list to be returned when there is key without values.
+	* the empty set to be returned when there is key without values.
 	*/
-	private final List<V> emptyList = Collections.unmodifiableList(new ArrayList<V>(0));
+	private final Set<V> emptySet = Collections.unmodifiableSet(Collections.<V>emptySet());
 
 	/**
-	* Returns the list of values associated with the given key.
+	* Returns the values set associated with the given key.
 	*
 	* @param key the key to get the values for.
 	* @return a list of values for the given keys or the empty list of no such
 	*         value exist.
 	*/
-	public List<V> get(Object key) {
-		List<V> result = super.get(key);
-		return result == null ? emptyList : result;
+	public Set<V> get(Object key) {
+		Set<V> result = super.get(key);
+		return result == null ? emptySet : result;
 	}
 
 
 	public V add(K key, V value )
 	{
 	    
-	    List<V> values = super.get(key);
+	    Set<V> values = super.get(key);
 	    if (values == null ) {
-	        values = new LinkedList<V>();
+	        values = new TreeSet<>();
 	        super.put( key, values );
 	    }
 	    
@@ -57,7 +56,7 @@
 	public V remove(K key, V value )
 	{
 	    
-	    List<V> values = super.get(key);
+		Set<V> values = super.get(key);
 	    if (values == null )
 			return null;
 	
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to