Hi Thorsten I can't explain the turn instructions being in the wrong direction for some ranges of angles. What are you seeing this on - Basecamp / MapSource / Garmin Device?
At the moment I'm more concerned with why, for your first example, we get different arcs being adjusted. I've added more diagnostics to show if the continuous way has been split and been given different ids. Can you send me the same sections of the log file, the first should look like this: fixSharpAngles_v4 at 49.478507,10.942814 mask 2 smallestAngle 15.375715 nArcs 3 nDirectArcs 3 nGroups 3 Arc heading -32.3756 328° angToPrev 166.0431 class 2 speed 3 way 144732811 isFake false rdDef 318472903 name null paved true Arc heading -16.999884 343° angToPrev 15.375715 class 1 speed 3 way 36138336 isFake false rdDef 1286211837 name null paved true Arc heading 161.5813 162° angToPrev 178.58118 class 1 speed 3 way 36138336 isFake false rdDef 1286211837 name null paved true Angle 15.375715 between -32.3756 and -16.999884 minAngle 46.0 wantedInc 30.624285 deltaPred 120.043106 deltaNext 132.58118 decreasing arc with heading 328° by 30.624285 modInitialHeading arc from -32.3756 by -30.624285 to -62.999886 If, in this example, way 36138338 has been split, I need to see if there is any way of finding the associated bits of road Attached is new patch. Regards Ticker On Tue, 2024-08-13 at 19:44 +0200, Thorsten Kukuk wrote: > Hi Ticker, > > The code is identical. > > But: how do you explain, that on all junctions, where your logging > reports headings > in the 3xx° area, Garmin navigation instructions are wrong, and where > the > headings are < 180°, the navigation instructions are correct? In between > it looks like, turn instructions for both directions are given. > And I don't speak here about the two junctions I brought up in this mail > thread, > I selected several random junctions from the log file with "sharp angle" > and created routes for it. > And for all, where the headings are in the 3xx° area, the notifications > were "wrong", and in all, where > it was below 180°, it was correct. > > Examples for "wrong" routing instructions: > sharp angle 13.183327 ° at 49.294696,11.107571 headings 307° 320° speeds > 2 2 classes 3 1 > increasing arc with heading 320° by 32.816673 > > sharp angle 32.202377 ° at 49.445028,11.075047 headings 322° 354° speeds > 3 2 classes 1 3 > decreasing arc with heading 322° by 13.797623 > > My observation: > headings below < 180° seem to be fine. Or I had always luck when > testing. > headings around 270° seems to give you instructions for both directions. > headings in the 3xx° area will give you "wrong" instructions. > > Regards, > Thorsten
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); }
_______________________________________________ mkgmap-dev mailing list mkgmap-dev@lists.mkgmap.org.uk https://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev