diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java b/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java index 2d9abaf280a..e061d615ec9 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java @@ -1,13 +1,6 @@ package net.osmand.binary; -import static net.osmand.router.GeneralRouter.*; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - import gnu.trove.map.hash.TIntObjectHashMap; - import net.osmand.Location; import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteRegion; import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule; @@ -16,6 +9,12 @@ import net.osmand.util.MapUtils; import net.osmand.util.TransliterationHelper; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import static net.osmand.router.GeneralRouter.GeneralRouterProfile; + public class RouteDataObject { /*private */static final int RESTRICTION_SHIFT = 3; /*private */static final int RESTRICTION_MASK = 7; @@ -702,6 +701,31 @@ public boolean roundabout() { return false; } + public boolean isClockwise(boolean leftSide) { + if (pointTypes != null) { + for (int[] tt : pointTypes) { + if (tt == null) { + continue; + } + for (int t : tt) { + RouteTypeRule r = region.quickGetEncodingRule(t); + if (r.getTag().equals("direction")) { + if (r.getValue().equals("clockwise")) { + return true; + } + if (r.getValue().equals("anticlockwise")) { + return false; + } + } + } + } + } + if (leftSide) { + return true; + } + return false; + } + public boolean tunnel() { int sz = types.length; for (int i = 0; i < sz; i++) { diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoadSplitStructure.java b/OsmAnd-java/src/main/java/net/osmand/router/RoadSplitStructure.java new file mode 100644 index 00000000000..4158d6e4a40 --- /dev/null +++ b/OsmAnd-java/src/main/java/net/osmand/router/RoadSplitStructure.java @@ -0,0 +1,44 @@ +package net.osmand.router; + +import java.util.ArrayList; +import java.util.List; + +public class RoadSplitStructure { + boolean keepLeft = false; + boolean keepRight = false; + boolean speak = false; + private static final float TURN_SLIGHT_DEGREE = 5; + + List leftLanesInfo = new ArrayList<>(); + int leftLanes = 0; + int leftMaxPrio = 0; + int roadsOnLeft = 0; + + List rightLanesInfo = new ArrayList<>(); + int rightLanes = 0; + int rightMaxPrio = 0; + int roadsOnRight = 0; + + public boolean allAreStraight() { + for (AttachedRoadInfo angle : leftLanesInfo) { + if (Math.abs(angle.attachedAngle) > TURN_SLIGHT_DEGREE) { + return false; + } + } + for (AttachedRoadInfo angle : rightLanesInfo) { + if (Math.abs(angle.attachedAngle) > TURN_SLIGHT_DEGREE) { + return false; + } + } + return true; + } + + public static class AttachedRoadInfo { + int[] parsedLanes; + double attachedAngle; + int lanes; + int speakPriority; + public boolean attachedOnTheRight; + public int turnType; + } +} diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoundaboutTurn.java b/OsmAnd-java/src/main/java/net/osmand/router/RoundaboutTurn.java new file mode 100644 index 00000000000..9bba244de1f --- /dev/null +++ b/OsmAnd-java/src/main/java/net/osmand/router/RoundaboutTurn.java @@ -0,0 +1,176 @@ +package net.osmand.router; + +import net.osmand.util.Algorithms; +import net.osmand.util.MapUtils; + +import java.util.List; + +public class RoundaboutTurn { + private final List routeSegmentResults; + private final RouteSegmentResult current; + private final RouteSegmentResult prev; + private final int iteration; + private final boolean roundabout; + private final boolean miniRoundabout; + private final boolean prevRoundabout; + private final boolean leftSide; + + public RoundaboutTurn(List routeSegmentResults, int i, boolean leftSide) { + this.routeSegmentResults = routeSegmentResults; + this.leftSide = leftSide; + iteration = i; + current = routeSegmentResults.size() > i ? routeSegmentResults.get(i) : null; + prev = i > 0 && routeSegmentResults.size() > i ? routeSegmentResults.get(i - 1) : null; + roundabout = current != null && current.getObject().roundabout(); + prevRoundabout = prev != null && prev.getObject().roundabout(); + miniRoundabout = isMiniRoundabout(prev, current); + } + + public boolean isRoundaboutExist() { + return roundabout || miniRoundabout || prevRoundabout; + } + + public TurnType getRoundaboutType() { + if (prev == null || current == null) { + return null; + } + if (prevRoundabout) { + // already analyzed! + return null; + } + if (roundabout) { + return processRoundaboutTurn(); + } + if (miniRoundabout) { + return processMiniRoundaboutTurn(); + } + return null; + } + + private boolean isMiniRoundabout(RouteSegmentResult prev, RouteSegmentResult current) { + if (prev == null || current == null) { + return false; + } + int[] prevTypes = prev.getObject().getPointTypes(prev.getEndPointIndex()); + int[] currentTypes = current.getObject().getPointTypes(current.getStartPointIndex()); + if (prevTypes != null && currentTypes != null) { + Integer miniType = prev.getObject().region.decodingRules.get("highway#mini_roundabout"); + if (miniType == null) { + return false; + } + boolean p = false; + boolean c = false; + for (int t : prevTypes) { + if (t == miniType) { + p = true; + break; + } + } + for (int t : currentTypes) { + if (t == miniType) { + c = true; + break; + } + } + return p && c; + } + return false; + } + + private TurnType processRoundaboutTurn() { + int exit = 1; + RouteSegmentResult last = current; + RouteSegmentResult firstRoundabout = current; + RouteSegmentResult lastRoundabout = current; + + for (int j = iteration; j < routeSegmentResults.size(); j++) { + RouteSegmentResult rnext = routeSegmentResults.get(j); + last = rnext; + if (rnext.getObject().roundabout()) { + lastRoundabout = rnext; + boolean plus = rnext.getStartPointIndex() < rnext.getEndPointIndex(); + int k = rnext.getStartPointIndex(); + if (j == iteration) { + // first exit could be immediately after roundabout enter +// k = plus ? k + 1 : k - 1; + } + while (k != rnext.getEndPointIndex()) { + int attachedRoads = rnext.getAttachedRoutes(k).size(); + if(attachedRoads > 0) { + exit++; + } + k = plus ? k + 1 : k - 1; + } + } else { + break; + } + } + // combine all roundabouts + TurnType t = TurnType.getExitTurn(exit, 0, leftSide); + // usually covers more than expected + float turnAngleBasedOnOutRoads = (float) MapUtils.degreesDiff(last.getBearingBegin(), prev.getBearingEnd()); + // Angle based on circle method tries + // 1. to calculate antinormal to roundabout circle on roundabout entrance and + // 2. normal to roundabout circle on roundabout exit + // 3. calculate angle difference + // This method doesn't work if you go from S to N touching only 1 point of roundabout, + // but it is very important to identify very sharp or very large angle to understand did you pass whole roundabout or small entrance + float turnAngleBasedOnCircle = (float) -MapUtils.degreesDiff(firstRoundabout.getBearingBegin(), lastRoundabout.getBearingEnd() + 180); + if (Math.abs(turnAngleBasedOnOutRoads) > 120) { + // correctly identify if angle is +- 180, so we approach from left or right side + t.setTurnAngle(turnAngleBasedOnCircle) ; + } else { + t.setTurnAngle(turnAngleBasedOnOutRoads) ; + } + return t; + } + + private TurnType processMiniRoundaboutTurn() { + List attachedRoutes = current.getAttachedRoutes(current.getStartPointIndex()); + boolean clockwise = current.getObject().isClockwise(leftSide); + if(!Algorithms.isEmpty(attachedRoutes)) { + RoadSplitStructure rs = calculateSimpleRoadSplitStructure(attachedRoutes); + int rightAttaches = rs.roadsOnRight; + int leftAttaches = rs.roadsOnLeft; + int exit = 1; + if (clockwise) { + exit += leftAttaches; + } else { + exit += rightAttaches; + } + TurnType t = TurnType.getExitTurn(exit, 0, leftSide); + float turnAngleBasedOnOutRoads = (float) MapUtils.degreesDiff(current.getBearingBegin(), prev.getBearingEnd()); + float turnAngleBasedOnCircle = (float) -MapUtils.degreesDiff(current.getBearingBegin(), prev.getBearingEnd() + 180); + if (Math.abs(turnAngleBasedOnOutRoads) > 120) { + t.setTurnAngle(turnAngleBasedOnCircle) ; + } else { + t.setTurnAngle(turnAngleBasedOnOutRoads) ; + } + return t; + } + return null; + } + + private RoadSplitStructure calculateSimpleRoadSplitStructure(List attachedRoutes) { + double prevAngle = MapUtils.normalizeDegrees360(prev.getBearingBegin() - 180); + double currentAngle = MapUtils.normalizeDegrees360(current.getBearingBegin()); + RoadSplitStructure rs = new RoadSplitStructure(); + for (RouteSegmentResult attached : attachedRoutes) { + double attachedAngle = MapUtils.normalizeDegrees360(attached.getBearingBegin()); + boolean rightSide; + if (prevAngle > currentAngle) { + rightSide = attachedAngle > currentAngle && attachedAngle < prevAngle; + } else { + boolean leftSide = attachedAngle > prevAngle && attachedAngle < currentAngle; + rightSide = !leftSide; + } + + if (rightSide) { + rs.roadsOnRight++; + } else { + rs.roadsOnLeft++; + } + } + return rs; + } +} diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java index 37ae3173f16..e08ef7adb1f 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java @@ -1,26 +1,5 @@ package net.osmand.router; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; - -import org.apache.commons.logging.Log; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import gnu.trove.list.array.TIntArrayList; import gnu.trove.set.hash.TIntHashSet; import net.osmand.PlatformUtil; @@ -40,6 +19,16 @@ import net.osmand.util.Algorithms; import net.osmand.util.MapAlgorithms; import net.osmand.util.MapUtils; +import org.apache.commons.logging.Log; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import net.osmand.router.RoadSplitStructure.*; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.text.MessageFormat; +import java.util.*; public class RouteResultPreparation { @@ -1271,15 +1260,12 @@ private TurnType getTurnInfo(List result, int i, boolean lef if (i == 0) { return TurnType.valueOf(TurnType.C, false); } - RouteSegmentResult prev = result.get(i - 1) ; - if(prev.getObject().roundabout()) { - // already analyzed! - return null; + RoundaboutTurn roundaboutTurn = new RoundaboutTurn(result, i, leftSide); + if (roundaboutTurn.isRoundaboutExist()) { + return roundaboutTurn.getRoundaboutType(); } + RouteSegmentResult prev = result.get(i - 1) ; RouteSegmentResult rr = result.get(i); - if (rr.getObject().roundabout()) { - return processRoundaboutTurn(result, i, leftSide, prev, rr); - } TurnType t = null; if (prev != null) { // avoid small zigzags is covered at (search for "zigzags") @@ -1408,94 +1394,6 @@ protected boolean setAllowedLanes(int mainTurnType, int[] lanesArray) { return turnSet; } - private TurnType processRoundaboutTurn(List result, int i, boolean leftSide, RouteSegmentResult prev, - RouteSegmentResult rr) { - int exit = 1; - RouteSegmentResult last = rr; - RouteSegmentResult firstRoundabout = rr; - RouteSegmentResult lastRoundabout = rr; - - for (int j = i; j < result.size(); j++) { - RouteSegmentResult rnext = result.get(j); - last = rnext; - if (rnext.getObject().roundabout()) { - lastRoundabout = rnext; - boolean plus = rnext.getStartPointIndex() < rnext.getEndPointIndex(); - int k = rnext.getStartPointIndex(); - if (j == i) { - // first exit could be immediately after roundabout enter -// k = plus ? k + 1 : k - 1; - } - while (k != rnext.getEndPointIndex()) { - int attachedRoads = rnext.getAttachedRoutes(k).size(); - if(attachedRoads > 0) { - exit++; - } - k = plus ? k + 1 : k - 1; - } - } else { - break; - } - } - // combine all roundabouts - TurnType t = TurnType.getExitTurn(exit, 0, leftSide); - // usually covers more than expected - float turnAngleBasedOnOutRoads = (float) MapUtils.degreesDiff(last.getBearingBegin(), prev.getBearingEnd()); - // Angle based on circle method tries - // 1. to calculate antinormal to roundabout circle on roundabout entrance and - // 2. normal to roundabout circle on roundabout exit - // 3. calculate angle difference - // This method doesn't work if you go from S to N touching only 1 point of roundabout, - // but it is very important to identify very sharp or very large angle to understand did you pass whole roundabout or small entrance - float turnAngleBasedOnCircle = (float) -MapUtils.degreesDiff(firstRoundabout.getBearingBegin(), lastRoundabout.getBearingEnd() + 180); - if (Math.abs(turnAngleBasedOnOutRoads) > 120) { - // correctly identify if angle is +- 180, so we approach from left or right side - t.setTurnAngle(turnAngleBasedOnCircle) ; - } else { - t.setTurnAngle(turnAngleBasedOnOutRoads) ; - } - return t; - } - - private static class AttachedRoadInfo { - int[] parsedLanes; - double attachedAngle; - int lanes; - int speakPriority; - public boolean attachedOnTheRight; - public int turnType; - } - private class RoadSplitStructure { - boolean keepLeft = false; - boolean keepRight = false; - boolean speak = false; - - List leftLanesInfo = new ArrayList<>(); - int leftLanes = 0; - int leftMaxPrio = 0; - int roadsOnLeft = 0; - - List rightLanesInfo = new ArrayList<>(); - int rightLanes = 0; - int rightMaxPrio = 0; - int roadsOnRight = 0; - - public boolean allAreStraight() { - for (AttachedRoadInfo angle : leftLanesInfo) { - if (Math.abs(angle.attachedAngle) > TURN_SLIGHT_DEGREE) { - return false; - } - } - for (AttachedRoadInfo angle : rightLanesInfo) { - if (Math.abs(angle.attachedAngle) > TURN_SLIGHT_DEGREE) { - return false; - } - } - return true; - } - } - - private TurnType attachKeepLeftInfoAndLanes(boolean leftSide, RouteSegmentResult prevSegm, RouteSegmentResult currentSegm, boolean twiceRoadPresent) { List attachedRoutes = currentSegm.getAttachedRoutes(currentSegm.getStartPointIndex()); if(attachedRoutes == null || attachedRoutes.isEmpty()) {