Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mini_roundabout #21007

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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++) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AttachedRoadInfo> leftLanesInfo = new ArrayList<>();
int leftLanes = 0;
int leftMaxPrio = 0;
int roadsOnLeft = 0;

List<AttachedRoadInfo> 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;
}
}
176 changes: 176 additions & 0 deletions OsmAnd-java/src/main/java/net/osmand/router/RoundaboutTurn.java
Original file line number Diff line number Diff line change
@@ -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<RouteSegmentResult> 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<RouteSegmentResult> 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<RouteSegmentResult> 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<RouteSegmentResult> 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;
}
}
Loading
Loading