v2 - no longer requires the node to have role "junction" This patch also includes two other changes to the heading adjustment code:
When matching arcs, as a last resort, match road class and speed. If at a node, an outgoing arc cannot be matched to an incoming arc by possiblySameRoad(), try to find a single outgoing arc that has the same road class and speed as the incoming arc and use that. and: When adjusting turn headings apply all relevant adjustments. Previously, if a heading needed adjusting because the delta from the outgoing arc was too small, it did not then consider whether the delta from the incoming arc was too small also. Now, it considers both cases and adjusts the heading as appropriate. So please test even if you are not adding any through_route relations and if you see any notable changes (good or bad) please say. -------------------- This new relation (type through_route) specifies which 2 ways are the "through route" at a junction. The relation requires 2 ways (no role required) and a node with role = junction. It should only be used at those junctions where the through route cannot be reliably inferred from the ways' names and/or refs. Here's an example from my local town: | 1 | / | / |/ A | | | | B /| / | / | 2 | The vertical road is the "through route", i.e. you can drive from one end to the other without crossing any white lines. But, from point 1 to point 2 is a B road so ways 1-A, A-B, and B-2 all have some ref, say, B1234. Without the through_route relation, mkgmap assumes (erroneously) that the through route is 1-2 because all of the ways in that route have the ref B1234. By adding 2 through_route relations (one at A and the other at B), mkgmap is informed of the real through route and will take that into account when adjusting the turn headings. The end result is that the quality of the routing directions is improved (we hope). In this example, we need to have 2 relations because the B road joins/leaves the through route at two places. So if you know of any junctions that would benefit from this relation, please add it to the map data and try this patch to see if it improves the routing instructions. Mark
diff --git a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java index de87495..e6a187e 100644 --- a/src/uk/me/parabola/imgfmt/app/net/RoadDef.java +++ b/src/uk/me/parabola/imgfmt/app/net/RoadDef.java @@ -555,6 +555,10 @@ public class RoadDef implements Comparable { nod2Flags |= (speed << 1); } + public int getRoadSpeed() { + return tabAInfo & 7; + } + public void setOneway() { tabAInfo |= TABA_FLAG_ONEWAY; netFlags |= NET_FLAG_ONEWAY; diff --git a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java index 45d6010..edfd266 100644 --- a/src/uk/me/parabola/imgfmt/app/net/RouteNode.java +++ b/src/uk/me/parabola/imgfmt/app/net/RouteNode.java @@ -67,6 +67,7 @@ public class RouteNode implements Comparable<RouteNode> { private final CoordNode coord; private char latOff; private char lonOff; + private List<RouteArc[]> throughRoutes; // this is for setting destination class on arcs // we're taking the maximum of roads this node is @@ -395,17 +396,55 @@ public class RouteNode implements Comparable<RouteNode> { // determine the outgoing arc that is likely to be the // same road as the incoming arc RouteArc outArc = null; - for(RouteArc oa : arcs) { - 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; + + if(throughRoutes != null) { + for(RouteArc[] pair : throughRoutes) { + if(pair[0] == inArc) { + outArc = pair[1]; + log.info("Found through route from " + inArc.getRoadDef() + " to " + outArc.getRoadDef()); break; } } } + + if(outArc == null) { + for(RouteArc oa : arcs) { + 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(outArc == null) { + // last ditch attempt to find the outgoing arc - + // try and find a single arc that has the same + // road class and speed as the incoming arc + int inArcClass = inArc.getRoadDef().getRoadClass(); + int inArcSpeed = inArc.getRoadDef().getRoadSpeed(); + for(RouteArc oa : arcs) { + if(oa.getDest() != inArc.getSource() && + oa.getRoadDef().getRoadClass() == inArcClass && + oa.getRoadDef().getRoadSpeed() == inArcSpeed) { + if(outArc != null) { + // multiple arcs have the same road + // class as the incoming arc so don't + // use any of them as the outgoing arc + outArc = null; + break; + } + outArc = oa; + } + } + if(outArc != null) + log.info("Matched outgoing arc " + outArc.getRoadDef() + " to " + inArc.getRoadDef() + " using road class (" + inArcClass + ") and speed (" + inArcSpeed + ")"); + } + // if we did not find the outgoing arc, give up with // this incoming arc if(outArc == null) { @@ -449,11 +488,16 @@ public class RouteNode implements Comparable<RouteNode> { continue; } + /* mb - I think this will have to go because it + * will stop the through routes working (and I + * can't remember why it's here anyway!) + if(possiblySameRoad(inArc, otherArc) || possiblySameRoad(outArc, otherArc)) { // not obviously a different road so give up continue; } + */ if(inArc.getRoadDef().isLinkRoad() && otherArc.getRoadDef().isLinkRoad()) { @@ -483,9 +527,12 @@ public class RouteNode implements Comparable<RouteNode> { if((mask & ATH_OUTGOING) != 0 && outToOtherDelta < minDiffBetweenOutgoingAndOtherArcs) newHeading = outHeading + minDiffBetweenOutgoingAndOtherArcs; - else if((mask & ATH_INCOMING) != 0 && - inToOtherDelta < minDiffBetweenIncomingAndOtherArcs) - newHeading = inHeading + minDiffBetweenIncomingAndOtherArcs; + if((mask & ATH_INCOMING) != 0 && + inToOtherDelta < minDiffBetweenIncomingAndOtherArcs) { + int nh = inHeading + minDiffBetweenIncomingAndOtherArcs; + if(nh > newHeading) + newHeading = nh; + } if(newHeading > 180) newHeading -= 360; @@ -495,9 +542,12 @@ public class RouteNode implements Comparable<RouteNode> { if((mask & ATH_OUTGOING) != 0 && outToOtherDelta > -minDiffBetweenOutgoingAndOtherArcs) newHeading = outHeading - minDiffBetweenOutgoingAndOtherArcs; - else if((mask & ATH_INCOMING) != 0 && - inToOtherDelta > -minDiffBetweenIncomingAndOtherArcs) - newHeading = inHeading - minDiffBetweenIncomingAndOtherArcs; + if((mask & ATH_INCOMING) != 0 && + inToOtherDelta > -minDiffBetweenIncomingAndOtherArcs) { + int nh = inHeading - minDiffBetweenIncomingAndOtherArcs; + if(nh < newHeading) + newHeading = nh; + } if(newHeading < -180) newHeading += 360; @@ -732,4 +782,23 @@ public class RouteNode implements Comparable<RouteNode> { } } } + + public void addThroughRoute(long roadIdA, long roadIdB) { + if(throughRoutes == null) + throughRoutes = new ArrayList<RouteArc[]>(); + for(RouteArc arc1 : incomingArcs) { + if(arc1.getRoadDef().getId() == roadIdA) { + for(RouteArc arc2 : arcs) { + if(arc2.getRoadDef().getId() == roadIdB) + throughRoutes.add(new RouteArc[] { arc1, arc2 }); + } + } + else if(arc1.getRoadDef().getId() == roadIdB) { + for(RouteArc arc2 : arcs) { + if(arc2.getRoadDef().getId() == roadIdA) + throughRoutes.add(new RouteArc[] { arc1, arc2 }); + } + } + } + } } diff --git a/src/uk/me/parabola/mkgmap/general/MapCollector.java b/src/uk/me/parabola/mkgmap/general/MapCollector.java index 07b32d9..6ca5bac 100644 --- a/src/uk/me/parabola/mkgmap/general/MapCollector.java +++ b/src/uk/me/parabola/mkgmap/general/MapCollector.java @@ -73,4 +73,10 @@ public interface MapCollector { * @param exceptMask For exceptions eg. no-left-turn except for buses. */ public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask); + + /** + * Add a through route to the map. + * + */ + public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB); } diff --git a/src/uk/me/parabola/mkgmap/general/MapDetails.java b/src/uk/me/parabola/mkgmap/general/MapDetails.java index a5707c5..30e638a 100644 --- a/src/uk/me/parabola/mkgmap/general/MapDetails.java +++ b/src/uk/me/parabola/mkgmap/general/MapDetails.java @@ -119,6 +119,10 @@ public class MapDetails implements MapCollector, MapDataSource { roadNetwork.addRestriction(fromNode, toNode, viaNode, exceptMask); } + public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) { + roadNetwork.addThroughRoute(junctionNodeId, roadIdA, roadIdB); + } + /** * Add the given point to the total bounds for the map. * diff --git a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java index 6be2d0c..2740665 100644 --- a/src/uk/me/parabola/mkgmap/general/RoadNetwork.java +++ b/src/uk/me/parabola/mkgmap/general/RoadNetwork.java @@ -285,4 +285,10 @@ public class RoadNetwork { vn.addRestriction(new RouteRestriction(fa, ta, exceptMask)); } + public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) { + RouteNode node = nodes.get(junctionNodeId); + assert node != null : "Can't find node with id " + junctionNodeId; + + node.addThroughRoute(roadIdA, roadIdB); + } } diff --git a/src/uk/me/parabola/mkgmap/main/StyleTester.java b/src/uk/me/parabola/mkgmap/main/StyleTester.java index 998b195..cf994ae 100644 --- a/src/uk/me/parabola/mkgmap/main/StyleTester.java +++ b/src/uk/me/parabola/mkgmap/main/StyleTester.java @@ -713,6 +713,9 @@ public class StyleTester implements OsmConverter { public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) { } + + public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) { + } } /** @@ -753,6 +756,9 @@ public class StyleTester implements OsmConverter { public void addRestriction(CoordNode fromNode, CoordNode toNode, CoordNode viaNode, byte exceptMask) { } + public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) { + } + public long getStart() { return start; } diff --git a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java index 784f238..0ea50ed 100644 --- a/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java +++ b/src/uk/me/parabola/mkgmap/osmstyle/StyledConverter.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import java.util.regex.Pattern; @@ -80,6 +81,8 @@ public class StyledConverter implements OsmConverter { // Coord corresponding to the restrictions' 'via' node private final Map<Coord, List<RestrictionRelation>> restrictions = new IdentityHashMap<Coord, List<RestrictionRelation>>(); + private final List<Relation> throughRouteRelations = new ArrayList<Relation>(); + // originalWay associates Ways that have been created due to // splitting or clipping with the Ways that they were derived // from @@ -386,6 +389,43 @@ public class StyledConverter implements OsmConverter { rr.addRestriction(collector); } } + + for(Relation relation : throughRouteRelations) { + Node node = null; + Way w1 = null; + Way w2 = null; + for(Map.Entry<String,Element> member : relation.getElements()) { + if(member.getValue() instanceof Node) { + if(node == null) + node = (Node)member.getValue(); + else + log.error("Through route relation " + relation.toBrowseURL() + " has more than 1 node"); + } + else if(member.getValue() instanceof Way) { + Way w = (Way)member.getValue(); + if(w1 == null) + w1 = w; + else if(w2 == null) + w2 = w; + else + log.error("Through route relation " + relation.toBrowseURL() + " has more than 2 ways"); + } + } + Integer nodeId = null; + if(node == null) + log.error("Through route relation " + relation.toBrowseURL() + " is missing the junction node"); + else { + nodeId = nodeIdMap.get(node.getLocation()); + if(nodeId == null) + log.error("Through route relation " + relation.toBrowseURL() + " junction node is not a routing node"); + } + if(w1 == null || w2 == null) + log.error("Through route relation " + relation.toBrowseURL() + " should have 2 ways that meet at the junction node"); + if(nodeId != null && w1 != null && w2 != null) { + log.info("Adding through route for ways " + w1.toBrowseURL() + " and " + w2.toBrowseURL() + " at " + node.getLocation().toOSMURL()); + collector.addThroughRoute(nodeId, w1.getId(), w2.getId()); + } + } } /** @@ -411,6 +451,9 @@ public class StyledConverter implements OsmConverter { lrr.add(rr); } } + else if("through_route".equals(relation.getTag("type"))) { + throughRouteRelations.add(relation); + } } private void addLine(Way way, GType gt) { diff --git a/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java b/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java index dd5a54e..2d6e541 100644 --- a/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java +++ b/src/uk/me/parabola/mkgmap/reader/overview/OverviewMapDataSource.java @@ -144,6 +144,10 @@ public class OverviewMapDataSource extends MapperBasedMapDataSource getRoadNetwork().addRestriction(fromNode, toNode, viaNode, exceptMask); } + public void addThroughRoute(long junctionNodeId, long roadIdA, long roadIdB) { + getRoadNetwork().addThroughRoute(junctionNodeId, roadIdA, roadIdB); + } + public int getShift() { return 24 - (topBits - 1); }
_______________________________________________ mkgmap-dev mailing list mkgmap-dev@lists.mkgmap.org.uk http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev