Skip to content
Permalink
6065d44c9a
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
1197 lines (1119 sloc) 42.4 KB
package net.osmand.router;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.set.hash.TIntHashSet;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import net.osmand.PlatformUtil;
import net.osmand.binary.BinaryMapIndexReader;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule;
import net.osmand.binary.RouteDataObject;
import net.osmand.data.LatLon;
import net.osmand.router.BinaryRoutePlanner.FinalRouteSegment;
import net.osmand.router.BinaryRoutePlanner.RouteSegment;
import net.osmand.router.RoutePlannerFrontEnd.RouteCalculationMode;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
public class RouteResultPreparation {
public static boolean PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST = false;
private static final float TURN_DEGREE_MIN = 45;
private Log log = PlatformUtil.getLog(RouteResultPreparation.class);
/**
* Helper method to prepare final result
*/
List<RouteSegmentResult> prepareResult(RoutingContext ctx, FinalRouteSegment finalSegment) throws IOException {
List<RouteSegmentResult> result = convertFinalSegmentToResults(ctx, finalSegment);
prepareResult(ctx, result);
return result;
}
List<RouteSegmentResult> prepareResult(RoutingContext ctx, List<RouteSegmentResult> result) throws IOException {
validateAllPointsConnected(result);
splitRoadsAndAttachRoadSegments(ctx, result);
calculateTimeSpeed(ctx, result);
for (int i = 0; i < result.size(); i ++) {
TurnType turnType = getTurnInfo(result, i, ctx.leftSideNavigation);
result.get(i).setTurnType(turnType);
}
determineTurnsToMerge(ctx.leftSideNavigation, result);
justifyUTurns(ctx.leftSideNavigation, result);
addTurnInfoDescriptions(result);
return result;
}
private void justifyUTurns(boolean leftSide, List<RouteSegmentResult> result) {
int next;
for (int i = 0; i < result.size() - 1; i = next) {
next = i + 1;
TurnType t = result.get(i).getTurnType();
// justify turn
if (t != null) {
TurnType jt = justifyUTurn(leftSide, result, i, t);
if (jt != null) {
result.get(i).setTurnType(jt);
next = i + 2;
}
}
}
}
private void calculateTimeSpeed(RoutingContext ctx, List<RouteSegmentResult> result) throws IOException {
for (int i = 0; i < result.size(); i++) {
RouteSegmentResult rr = result.get(i);
RouteDataObject road = rr.getObject();
double distOnRoadToPass = 0;
double speed = ctx.getRouter().defineVehicleSpeed(road);
if (speed == 0) {
speed = ctx.getRouter().getMinDefaultSpeed();
} else {
if(speed > 15) {
// decrease speed proportionally from 15ms=50kmh -
// reference speed 30ms=108kmh - 7kmh
speed = speed - ((speed - 15f) / (30f - 15f) * 2f);
}
}
boolean plus = rr.getStartPointIndex() < rr.getEndPointIndex();
int next;
double distance = 0;
for (int j = rr.getStartPointIndex(); j != rr.getEndPointIndex(); j = next) {
next = plus ? j + 1 : j - 1;
double d = measuredDist(road.getPoint31XTile(j), road.getPoint31YTile(j), road.getPoint31XTile(next),
road.getPoint31YTile(next));
distance += d;
double obstacle = ctx.getRouter().defineObstacle(road, j);
if (obstacle < 0) {
obstacle = 0;
}
distOnRoadToPass += d / speed + obstacle;
}
// last point turn time can be added
// if(i + 1 < result.size()) { distOnRoadToPass += ctx.getRouter().calculateTurnTime(); }
rr.setSegmentTime((float) distOnRoadToPass);
rr.setSegmentSpeed((float) speed);
rr.setDistance((float) distance);
}
}
private void splitRoadsAndAttachRoadSegments(RoutingContext ctx, List<RouteSegmentResult> result) throws IOException {
for (int i = 0; i < result.size(); i++) {
if (ctx.checkIfMemoryLimitCritical(ctx.config.memoryLimitation)) {
ctx.unloadUnusedTiles(ctx.config.memoryLimitation);
}
RouteSegmentResult rr = result.get(i);
RouteDataObject road = rr.getObject();
checkAndInitRouteRegion(ctx, road);
boolean plus = rr.getStartPointIndex() < rr.getEndPointIndex();
int next;
for (int j = rr.getStartPointIndex(); j != rr.getEndPointIndex(); j = next) {
next = plus ? j + 1 : j - 1;
if (j == rr.getStartPointIndex()) {
attachRoadSegments(ctx, result, i, j, plus);
}
if (next != rr.getEndPointIndex()) {
attachRoadSegments(ctx, result, i, next, plus);
}
List<RouteSegmentResult> attachedRoutes = rr.getAttachedRoutes(next);
boolean tryToSplit = next != rr.getEndPointIndex() && !rr.getObject().roundabout() && attachedRoutes != null;
if(rr.getDistance(next, plus ) == 0) {
// same point will be processed next step
tryToSplit = false;
}
if (tryToSplit) {
// avoid small zigzags
float before = rr.getBearing(next, !plus);
float after = rr.getBearing(next, plus);
if(rr.getDistance(next, plus ) < 5) {
after = before + 180;
} else if(rr.getDistance(next, !plus ) < 5) {
before = after - 180;
}
boolean straight = Math.abs(MapUtils.degreesDiff(before + 180, after)) < TURN_DEGREE_MIN;
boolean isSplit = false;
// split if needed
for (RouteSegmentResult rs : attachedRoutes) {
double diff = MapUtils.degreesDiff(before + 180, rs.getBearingBegin());
if (Math.abs(diff) <= TURN_DEGREE_MIN) {
isSplit = true;
} else if (!straight && Math.abs(diff) < 100) {
isSplit = true;
}
}
if (isSplit) {
int endPointIndex = rr.getEndPointIndex();
RouteSegmentResult split = new RouteSegmentResult(rr.getObject(), next, endPointIndex);
split.copyPreattachedRoutes(rr, Math.abs(next - rr.getStartPointIndex()));
rr.setEndPointIndex(next);
result.add(i + 1, split);
i++;
// switch current segment to the splitted
rr = split;
}
}
}
}
}
private void checkAndInitRouteRegion(RoutingContext ctx, RouteDataObject road) throws IOException {
BinaryMapIndexReader reader = ctx.reverseMap.get(road.region);
if(reader != null) {
reader.initRouteRegion(road.region);
}
}
private void validateAllPointsConnected(List<RouteSegmentResult> result) {
for (int i = 1; i < result.size(); i++) {
RouteSegmentResult rr = result.get(i);
RouteSegmentResult pr = result.get(i - 1);
double d = MapUtils.getDistance(pr.getPoint(pr.getEndPointIndex()), rr.getPoint(rr.getStartPointIndex()));
if (d > 0) {
System.err.println("Points are not connected : " + pr.getObject() + "(" + pr.getEndPointIndex() + ") -> " + rr.getObject()
+ "(" + rr.getStartPointIndex() + ") " + d + " meters");
}
}
}
private List<RouteSegmentResult> convertFinalSegmentToResults(RoutingContext ctx, FinalRouteSegment finalSegment) {
List<RouteSegmentResult> result = new ArrayList<RouteSegmentResult>();
if (finalSegment != null) {
ctx.routingTime = finalSegment.distanceFromStart;
println("Routing calculated time distance " + finalSegment.distanceFromStart);
// Get results from opposite direction roads
RouteSegment segment = finalSegment.reverseWaySearch ? finalSegment :
finalSegment.opposite.getParentRoute();
int parentSegmentStart = finalSegment.reverseWaySearch ? finalSegment.opposite.getSegmentStart() :
finalSegment.opposite.getParentSegmentEnd();
float parentRoutingTime = -1;
while (segment != null) {
RouteSegmentResult res = new RouteSegmentResult(segment.road, parentSegmentStart, segment.getSegmentStart());
parentRoutingTime = calcRoutingTime(parentRoutingTime, finalSegment, segment, res);
parentSegmentStart = segment.getParentSegmentEnd();
segment = segment.getParentRoute();
addRouteSegmentToResult(ctx, result, res, false);
}
// reverse it just to attach good direction roads
Collections.reverse(result);
segment = finalSegment.reverseWaySearch ? finalSegment.opposite.getParentRoute() : finalSegment;
int parentSegmentEnd = finalSegment.reverseWaySearch ? finalSegment.opposite.getParentSegmentEnd() : finalSegment.opposite.getSegmentStart();
parentRoutingTime = -1;
while (segment != null) {
RouteSegmentResult res = new RouteSegmentResult(segment.road, segment.getSegmentStart(), parentSegmentEnd);
parentRoutingTime = calcRoutingTime(parentRoutingTime, finalSegment, segment, res);
parentSegmentEnd = segment.getParentSegmentEnd();
segment = segment.getParentRoute();
// happens in smart recalculation
addRouteSegmentToResult(ctx, result, res, true);
}
Collections.reverse(result);
// checkTotalRoutingTime(result);
}
return result;
}
protected void checkTotalRoutingTime(List<RouteSegmentResult> result) {
float totalRoutingTime = 0;
for(RouteSegmentResult r : result) {
totalRoutingTime += r.getRoutingTime();
}
println("Total routing time ! " + totalRoutingTime);
}
private float calcRoutingTime(float parentRoutingTime, RouteSegment finalSegment, RouteSegment segment,
RouteSegmentResult res) {
if(segment != finalSegment) {
if(parentRoutingTime != -1) {
res.setRoutingTime(parentRoutingTime - segment.distanceFromStart);
}
parentRoutingTime = segment.distanceFromStart;
}
return parentRoutingTime;
}
private void addRouteSegmentToResult(RoutingContext ctx, List<RouteSegmentResult> result, RouteSegmentResult res, boolean reverse) {
if (res.getStartPointIndex() != res.getEndPointIndex()) {
if (result.size() > 0) {
RouteSegmentResult last = result.get(result.size() - 1);
if (last.getObject().id == res.getObject().id && ctx.calculationMode != RouteCalculationMode.BASE) {
if (combineTwoSegmentResult(res, last, reverse)) {
return;
}
}
}
result.add(res);
}
}
private boolean combineTwoSegmentResult(RouteSegmentResult toAdd, RouteSegmentResult previous,
boolean reverse) {
boolean ld = previous.getEndPointIndex() > previous.getStartPointIndex();
boolean rd = toAdd.getEndPointIndex() > toAdd.getStartPointIndex();
if (rd == ld) {
if (toAdd.getStartPointIndex() == previous.getEndPointIndex() && !reverse) {
previous.setEndPointIndex(toAdd.getEndPointIndex());
previous.setRoutingTime(previous.getRoutingTime() + toAdd.getRoutingTime());
return true;
} else if (toAdd.getEndPointIndex() == previous.getStartPointIndex() && reverse) {
previous.setStartPointIndex(toAdd.getStartPointIndex());
previous.setRoutingTime(previous.getRoutingTime() + toAdd.getRoutingTime());
return true;
}
}
return false;
}
void printResults(RoutingContext ctx, LatLon start, LatLon end, List<RouteSegmentResult> result) {
float completeTime = 0;
float completeDistance = 0;
for(RouteSegmentResult r : result) {
completeTime += r.getSegmentTime();
completeDistance += r.getDistance();
}
println("ROUTE : ");
double startLat = start.getLatitude();
double startLon = start.getLongitude();
double endLat = end.getLatitude();
double endLon = end.getLongitude();
String msg = MessageFormat.format("<test regions=\"\" description=\"\" best_percent=\"\" vehicle=\"{4}\" \n"
+ " start_lat=\"{0}\" start_lon=\"{1}\" target_lat=\"{2}\" target_lon=\"{3}\" {5} >",
startLat + "", startLon + "", endLat + "", endLon + "", ctx.config.routerName,
"loadedTiles = \"" + ctx.loadedTiles + "\" " + "visitedSegments = \"" + ctx.visitedSegments + "\" " +
"complete_distance = \"" + completeDistance + "\" " + "complete_time = \"" + completeTime + "\" " +
"routing_time = \"" + ctx.routingTime + "\" ");
log.info(msg);
println(msg);
if (PRINT_TO_CONSOLE_ROUTE_INFORMATION_TO_TEST) {
for (RouteSegmentResult res : result) {
String name = res.getObject().getName();
String ref = res.getObject().getRef();
if (name == null) {
name = "";
}
if (ref != null) {
name += " (" + ref + ") ";
}
StringBuilder additional = new StringBuilder();
additional.append("time = \"").append(res.getSegmentTime()).append("\" ");
additional.append("rtime = \"").append(res.getRoutingTime()).append("\" ");
additional.append("name = \"").append(name).append("\" ");
// float ms = res.getSegmentSpeed();
float ms = res.getObject().getMaximumSpeed(res.isForwardDirection());
if(ms > 0) {
additional.append("maxspeed = \"").append(ms * 3.6f).append("\" ").append(res.getObject().getHighway()).append(" ");
}
additional.append("distance = \"").append(res.getDistance()).append("\" ");
if (res.getTurnType() != null) {
additional.append("turn = \"").append(res.getTurnType()).append("\" ");
additional.append("turn_angle = \"").append(res.getTurnType().getTurnAngle()).append("\" ");
if (res.getTurnType().getLanes() != null) {
additional.append("lanes = \"").append(Arrays.toString(res.getTurnType().getLanes())).append("\" ");
}
}
additional.append("start_bearing = \"").append(res.getBearingBegin()).append("\" ");
additional.append("end_bearing = \"").append(res.getBearingEnd()).append("\" ");
additional.append("description = \"").append(res.getDescription()).append("\" ");
println(MessageFormat.format("\t<segment id=\"{0}\" start=\"{1}\" end=\"{2}\" {3}/>", (res.getObject().getId()) + "",
res.getStartPointIndex() + "", res.getEndPointIndex() + "", additional.toString()));
printAdditionalPointInfo(res);
}
}
println("</test>");
}
private void printAdditionalPointInfo(RouteSegmentResult res) {
boolean plus = res.getStartPointIndex() < res.getEndPointIndex();
for(int k = res.getStartPointIndex(); k != res.getEndPointIndex(); ) {
int[] tp = res.getObject().getPointTypes(k);
String[] pointNames = res.getObject().getPointNames(k);
int[] pointNameTypes = res.getObject().getPointNameTypes(k);
if (tp != null || pointNameTypes != null) {
StringBuilder bld = new StringBuilder();
bld.append("<point ");
if (tp != null) {
for (int t = 0; t < tp.length; t++) {
RouteTypeRule rr = res.getObject().region.quickGetEncodingRule(tp[t]);
bld.append(" " + rr.getTag() + "=\"" + rr.getValue() + "\"");
}
}
if (pointNameTypes != null) {
for (int t = 0; t < pointNameTypes.length; t++) {
RouteTypeRule rr = res.getObject().region.quickGetEncodingRule(pointNameTypes[t]);
bld.append(" " + rr.getTag() + "=\"" + pointNames[t] + "\"");
}
}
bld.append("/>");
println("\t"+bld.toString());
}
if(plus) {
k++;
} else {
k--;
}
}
}
protected void addTurnInfoDescriptions(List<RouteSegmentResult> result) {
int prevSegment = -1;
float dist = 0;
for (int i = 0; i <= result.size(); i++) {
if (i == result.size() || result.get(i).getTurnType() != null) {
if (prevSegment >= 0) {
String turn = result.get(prevSegment).getTurnType().toString();
final int[] lns = result.get(prevSegment).getTurnType().getLanes();
if (lns != null) {
String s = "[ ";
for (int h = 0; h < lns.length; h++) {
if (h > 0) {
s += ", ";
}
if (lns[h] % 2 == 1) {
s += "+";
}
int pt = TurnType.getPrimaryTurn(lns[h]);
if (pt == 0) {
pt = 1;
}
s += TurnType.valueOf(pt, false).toXmlString();
int st = TurnType.getSecondaryTurn(lns[h]);
if (st != 0) {
s += ";" + TurnType.valueOf(st, false).toXmlString();
}
}
s += "]";
turn += s;
}
result.get(prevSegment).setDescription(
turn + MessageFormat.format(" and go {0,number,#.##} meters", dist));
if (result.get(prevSegment).getTurnType().isSkipToSpeak()) {
result.get(prevSegment).setDescription("-*" + result.get(prevSegment).getDescription());
}
}
prevSegment = i;
dist = 0;
}
if (i < result.size()) {
dist += result.get(i).getDistance();
}
}
}
protected TurnType justifyUTurn(boolean leftside, List<RouteSegmentResult> result, int i, TurnType t) {
boolean tl = TurnType.TL == t.getValue();
boolean tr = TurnType.TR == t.getValue();
if(tl || tr) {
TurnType tnext = result.get(i + 1).getTurnType();
if (tnext != null && result.get(i).getDistance() < 35) { //
boolean ut = true;
if (i > 0) {
double uTurn = MapUtils.degreesDiff(result.get(i - 1).getBearingEnd(), result
.get(i + 1).getBearingBegin());
if (Math.abs(uTurn) < 120) {
ut = false;
}
}
String highway = result.get(i).getObject().getHighway();
if(highway == null || highway.endsWith("track") || highway.endsWith("services") || highway.endsWith("service")
|| highway.endsWith("path")) {
ut = false;
}
if (ut) {
tnext.setSkipToSpeak(true);
if (tl && TurnType.TL == tnext.getValue()) {
return TurnType.valueOf(TurnType.TU, false);
} else if (tr && TurnType.TR == tnext.getValue()) {
return TurnType.valueOf(TurnType.TU, true);
}
}
}
}
return null;
}
private void determineTurnsToMerge(boolean leftside, List<RouteSegmentResult> result) {
RouteSegmentResult nextSegment = null;
double dist = 0;
for (int i = result.size() - 1; i >= 0; i--) {
RouteSegmentResult currentSegment = result.get(i);
TurnType currentTurn = currentSegment.getTurnType();
dist += currentSegment.getDistance();
if (currentTurn == null || currentTurn.getLanes() == null) {
// skip
} else {
if (nextSegment != null) {
String hw = currentSegment.getObject().getHighway();
double mergeDistance = 200;
if (hw != null && (hw.startsWith("trunk") || hw.startsWith("motorway"))) {
mergeDistance = 400;
}
if (dist < mergeDistance) {
mergeTurnLanes(leftside, currentSegment, nextSegment);
}
}
nextSegment = currentSegment;
dist = 0;
}
}
}
private class MergeTurnLaneTurn {
TurnType turn;
int[] originalLanes;
int[] disabledLanes;
int activeStartIndex = -1;
int activeEndIndex = -1;
int activeLen = 0;
public MergeTurnLaneTurn(RouteSegmentResult segment) {
this.turn = segment.getTurnType();
if(turn != null) {
originalLanes = turn.getLanes();
}
if(originalLanes != null) {
disabledLanes = new int[originalLanes.length];
for (int i = 0; i < originalLanes.length; i++) {
int ln = originalLanes[i];
disabledLanes[i] = ln & ~1;
if ((ln & 1) > 0) {
if (activeStartIndex == -1) {
activeStartIndex = i;
}
activeEndIndex = i;
activeLen++;
}
}
}
}
public boolean isActiveTurnMostLeft() {
return activeStartIndex == 0;
}
public boolean isActiveTurnMostRight() {
return activeEndIndex == originalLanes.length - 1;
}
}
private void mergeTurnLanes(boolean leftSide, RouteSegmentResult currentSegment, RouteSegmentResult nextSegment) {
MergeTurnLaneTurn active = new MergeTurnLaneTurn(currentSegment);
MergeTurnLaneTurn target = new MergeTurnLaneTurn(nextSegment);
if (active.activeLen < 2) {
return;
}
if (target.activeStartIndex == -1) {
return;
}
boolean changed = false;
if (target.isActiveTurnMostLeft()) {
// let only the most left lanes be enabled
if (target.activeLen <= active.activeLen) {
active.activeEndIndex -= (active.activeLen - target.activeLen);
changed = true;
}
} else if (target.isActiveTurnMostRight()) {
// next turn is right
// let only the most right lanes be enabled
if (target.activeLen < active.activeLen) {
active.activeStartIndex += (active.activeLen - target.activeLen);
changed = true;
}
} else {
// next turn is get through (take out the left and the right turn)
if (target.activeLen < active.activeLen) {
float ratio = (active.activeLen - target.activeLen) / 2f;
active.activeEndIndex = (int) Math.ceil(active.activeEndIndex - ratio);
active.activeStartIndex = (int) Math.floor(active.activeStartIndex + ratio);
changed = true;
}
}
if (!changed) {
return;
}
// set the allowed lane bit
for (int i = 0; i < active.disabledLanes.length; i++) {
if (i >= active.activeStartIndex && i <= active.activeEndIndex) {
active.disabledLanes[i] |= 1;
}
}
TurnType currentTurn = currentSegment.getTurnType();
currentTurn.setLanes(active.disabledLanes);
int turn = inferTurnFromLanes(active.disabledLanes);
if (turn != 0 && turn != currentTurn.getValue()) {
TurnType newTurnType = TurnType.valueOf(turn, leftSide);
newTurnType.setLanes(active.disabledLanes);
newTurnType.setSkipToSpeak(currentTurn.isSkipToSpeak());
currentSegment.setTurnType(newTurnType);
}
}
private static final int MAX_SPEAK_PRIORITY = 5;
private int highwaySpeakPriority(String highway) {
if(highway == null || highway.endsWith("track") || highway.endsWith("services") || highway.endsWith("service")
|| highway.endsWith("path")) {
return MAX_SPEAK_PRIORITY;
}
if (highway.endsWith("_link") || highway.endsWith("unclassified") || highway.endsWith("road")
|| highway.endsWith("living_street") || highway.endsWith("residential") ) {
return 1;
}
return 0;
}
private TurnType getTurnInfo(List<RouteSegmentResult> result, int i, boolean leftSide) {
if (i == 0) {
return TurnType.valueOf(TurnType.C, false);
}
RouteSegmentResult prev = result.get(i - 1) ;
if(prev.getObject().roundabout()) {
// already analyzed!
return null;
}
RouteSegmentResult rr = result.get(i);
if (rr.getObject().roundabout()) {
return processRoundaboutTurn(result, i, leftSide, prev, rr);
}
TurnType t = null;
if (prev != null) {
boolean noAttachedRoads = rr.getAttachedRoutes(rr.getStartPointIndex()).size() == 0;
// add description about turn
double mpi = MapUtils.degreesDiff(prev.getBearingEnd(), rr.getBearingBegin());
if(noAttachedRoads){
// TODO VICTOR : look at the comment inside direction route
// ? avoid small zigzags is covered at (search for "zigzags")
// double begin = rr.getObject().directionRoute(rr.getStartPointIndex(), rr.getStartPointIndex() <
// rr.getEndPointIndex(), 25);
// mpi = MapUtils.degreesDiff(prev.getBearingEnd(), begin);
}
if (mpi >= TURN_DEGREE_MIN) {
if (mpi < 45) {
// Slight turn detection here causes many false positives where drivers would expect a "normal" TL. Best use limit-angle=TURN_DEGREE_MIN, this reduces TSL to the turn-lanes cases.
t = TurnType.valueOf(TurnType.TSLL, leftSide);
} else if (mpi < 120) {
t = TurnType.valueOf(TurnType.TL, leftSide);
} else if (mpi < 150 || leftSide) {
t = TurnType.valueOf(TurnType.TSHL, leftSide);
} else {
t = TurnType.valueOf(TurnType.TU, leftSide);
}
int[] lanes = getTurnLanesInfo(prev, t.getValue());
t.setLanes(lanes);
} else if (mpi < -TURN_DEGREE_MIN) {
if (mpi > -45) {
t = TurnType.valueOf(TurnType.TSLR, leftSide);
} else if (mpi > -120) {
t = TurnType.valueOf(TurnType.TR, leftSide);
} else if (mpi > -150 || !leftSide) {
t = TurnType.valueOf(TurnType.TSHR, leftSide);
} else {
t = TurnType.valueOf(TurnType.TRU, leftSide);
}
int[] lanes = getTurnLanesInfo(prev, t.getValue());
t.setLanes(lanes);
} else {
t = attachKeepLeftInfoAndLanes(leftSide, prev, rr);
}
if (t != null) {
t.setTurnAngle((float) -mpi);
}
}
return t;
}
private int[] getTurnLanesInfo(RouteSegmentResult prevSegm, int mainTurnType) {
String turnLanes = getTurnLanesString(prevSegm);
if (turnLanes == null) {
return null;
}
int[] lanesArray = calculateRawTurnLanes(turnLanes, mainTurnType);
// Manually set the allowed lanes.
boolean isSet = setAllowedLanes(mainTurnType, lanesArray);
if(!isSet && lanesArray.length > 0) {
// In some cases (at least in the US), the rightmost lane might not have a right turn indicated as per turn:lanes,
// but is allowed and being used here. This section adds in that indicator. The same applies for where leftSide is true.
boolean leftTurn = TurnType.isLeftTurn(mainTurnType);
int ind = leftTurn? 0 : lanesArray.length - 1;
final int tt = TurnType.getPrimaryTurn(lanesArray[ind]);
if (leftTurn) {
if (!TurnType.isLeftTurn(tt)) {
// This was just to make sure that there's no bad data.
TurnType.setPrimaryTurnAndReset(lanesArray, ind, TurnType.TL);
TurnType.setSecondaryTurn(lanesArray, ind, tt);
lanesArray[ind] |= 1;
}
} else {
if (!TurnType.isRightTurn(tt)) {
// This was just to make sure that there's no bad data.
TurnType.setPrimaryTurnAndReset(lanesArray, ind, TurnType.TR);
TurnType.setSecondaryTurn(lanesArray, ind, tt);
lanesArray[ind] |= 1;
}
}
setAllowedLanes(tt, lanesArray);
}
return lanesArray;
}
protected boolean setAllowedLanes(int mainTurnType, int[] lanesArray) {
boolean turnSet = false;
for (int i = 0; i < lanesArray.length; i++) {
if (TurnType.getPrimaryTurn(lanesArray[i]) == mainTurnType) {
lanesArray[i] |= 1;
turnSet = true;
}
}
return turnSet;
}
private TurnType processRoundaboutTurn(List<RouteSegmentResult> result, int i, boolean leftSide, RouteSegmentResult prev,
RouteSegmentResult rr) {
int exit = 1;
RouteSegmentResult last = rr;
for (int j = i; j < result.size(); j++) {
RouteSegmentResult rnext = result.get(j);
last = rnext;
if (rnext.getObject().roundabout()) {
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);
t.setTurnAngle((float) MapUtils.degreesDiff(last.getBearingBegin(), prev.getBearingEnd())) ;
return t;
}
private class RoadSplitStructure {
boolean keepLeft = false;
boolean keepRight = false;
boolean speak = false;
int leftLanes = 0;
int rightLanes = 0;
int roadsOnLeft = 0;
int addRoadsOnLeft = 0;
int roadsOnRight = 0;
int addRoadsOnRight = 0;
}
private TurnType attachKeepLeftInfoAndLanes(boolean leftSide, RouteSegmentResult prevSegm, RouteSegmentResult currentSegm) {
List<RouteSegmentResult> attachedRoutes = currentSegm.getAttachedRoutes(currentSegm.getStartPointIndex());
if(attachedRoutes == null || attachedRoutes.size() == 0) {
return null;
}
// keep left/right
RoadSplitStructure rs = calculateRoadSplitStructure(prevSegm, currentSegm, attachedRoutes);
if(rs.roadsOnLeft + rs.roadsOnRight == 0) {
return null;
}
// turn lanes exist
String turnLanes = getTurnLanesString(prevSegm);
if (turnLanes != null) {
return createKeepLeftRightTurnBasedOnTurnTypes(rs, prevSegm, currentSegm, turnLanes, leftSide);
}
// turn lanes don't exist
if (rs.keepLeft || rs.keepRight) {
return createSimpleKeepLeftRightTurn(leftSide, prevSegm, currentSegm, rs);
}
return null;
}
protected TurnType createKeepLeftRightTurnBasedOnTurnTypes(RoadSplitStructure rs, RouteSegmentResult prevSegm,
RouteSegmentResult currentSegm, String turnLanes,boolean leftSide) {
// Maybe going straight at a 90-degree intersection
TurnType t = TurnType.valueOf(TurnType.C, leftSide);
int[] rawLanes = calculateRawTurnLanes(turnLanes, TurnType.C);
if (rs.keepLeft || rs.keepRight) {
String[] splitLaneOptions = turnLanes.split("\\|", -1);
int activeBeginIndex = findActiveIndex(rawLanes, splitLaneOptions, rs.leftLanes, true,
rs.roadsOnLeft, rs.addRoadsOnLeft);
int activeEndIndex = findActiveIndex(rawLanes, splitLaneOptions, rs.rightLanes, false,
rs.roadsOnRight, rs.addRoadsOnRight);
if (activeBeginIndex == -1 || activeEndIndex == -1 || activeBeginIndex > activeEndIndex) {
// something went wrong
return createSimpleKeepLeftRightTurn(leftSide, prevSegm, currentSegm, rs);
}
for (int k = 0; k < rawLanes.length; k++) {
if (k >= activeBeginIndex && k <= activeEndIndex) {
rawLanes[k] |= 1;
}
}
int tp = inferTurnFromLanes(rawLanes);
if (tp != t.getValue() && tp != 0) {
t = TurnType.valueOf(tp, leftSide);
}
} else {
for (int k = 0; k < rawLanes.length; k++) {
int turn = rawLanes[k];
boolean active = false;
if (TurnType.getPrimaryTurn(turn) == TurnType.C) {
active = true;
} else if (TurnType.isRightTurn(turn) && rs.roadsOnRight == 0) {
// some turns go through many segments (to turn right or left)
// so on one first segment the lane could be available and may be only 1 possible
// all undesired lanes will be disabled through the 2nd pass
active = true;
} else if (TurnType.isLeftTurn(turn) && rs.roadsOnLeft == 0) {
active = true;
}
if (active) {
rawLanes[k] |= 1;
}
}
}
t.setSkipToSpeak(!rs.speak);
t.setLanes(rawLanes);
return t;
}
protected int findActiveIndex(int[] rawLanes, String[] splitLaneOptions, int lanes, boolean left,
int roads, int addRoads) {
int activeStartIndex = -1;
boolean lookupSlightTurn = addRoads > 0;
for (int i = 0; i < rawLanes.length; i++) {
int ind = left ? i : (rawLanes.length - i - 1);
if (!lookupSlightTurn ||
TurnType.isSlightTurn(TurnType.getPrimaryTurn(rawLanes[ind]))
|| TurnType.isSlightTurn(TurnType.getSecondaryTurn(rawLanes[ind]))) {
int cnt = countOccurrences(splitLaneOptions[ind], ';') + 1;
if(cnt > 1) {
// sometimes slight right turn goes to the road with 2 lanes
// the better situation to group all the lanes and
// when ';' we know for sure the lane combines 2 group
roads --;
}
lanes -= cnt;
// we already found slight turn others are turn in different direction
lookupSlightTurn = false;
}
if (lanes < 0 || roads < 0) {
activeStartIndex = ind;
break;
}
}
return activeStartIndex;
}
protected RoadSplitStructure calculateRoadSplitStructure(RouteSegmentResult prevSegm, RouteSegmentResult currentSegm,
List<RouteSegmentResult> attachedRoutes) {
RoadSplitStructure rs = new RoadSplitStructure();
int speakPriority = Math.max(highwaySpeakPriority(prevSegm.getObject().getHighway()), highwaySpeakPriority(currentSegm.getObject().getHighway()));
for (RouteSegmentResult attached : attachedRoutes) {
double ex = MapUtils.degreesDiff(attached.getBearingBegin(), currentSegm.getBearingBegin());
double mpi = Math.abs(MapUtils.degreesDiff(prevSegm.getBearingEnd(), attached.getBearingBegin()));
int rsSpeakPriority = highwaySpeakPriority(attached.getObject().getHighway());
if (rsSpeakPriority != MAX_SPEAK_PRIORITY || speakPriority == MAX_SPEAK_PRIORITY) {
int lanes = countLanesMinOne(attached);
boolean smallStraightVariation = mpi < TURN_DEGREE_MIN;
boolean smallTargetVariation = Math.abs(ex) < TURN_DEGREE_MIN;
boolean attachedOnTheRight = ex >= 0;
if (attachedOnTheRight) {
rs.roadsOnRight++;
} else {
rs.roadsOnLeft++;
}
if (smallTargetVariation || smallStraightVariation) {
if (attachedOnTheRight) {
rs.keepLeft = true;
rs.rightLanes += lanes;
} else {
rs.keepRight = true;
rs.leftLanes += lanes;
}
rs.speak = rs.speak || rsSpeakPriority <= speakPriority;
} else {
if (attachedOnTheRight) {
rs.addRoadsOnRight++;
} else {
rs.addRoadsOnLeft++;
}
}
}
}
return rs;
}
protected TurnType createSimpleKeepLeftRightTurn(boolean leftSide, RouteSegmentResult prevSegm,
RouteSegmentResult currentSegm, RoadSplitStructure rs) {
int current = countLanesMinOne(currentSegm);
int ls = current + rs.leftLanes + rs.rightLanes;
int[] lanes = new int[ls];
for (int it = 0; it < ls; it++) {
if (it < rs.leftLanes || it >= rs.leftLanes + current) {
lanes[it] = 0;
} else {
lanes[it] = 1;
}
}
// sometimes links are
if ((current <= rs.leftLanes + rs.rightLanes) && (rs.leftLanes > 1 || rs.rightLanes > 1)) {
rs.speak = true;
}
double devation = Math.abs(MapUtils.degreesDiff(prevSegm.getBearingEnd(), currentSegm.getBearingBegin()));
boolean makeSlightTurn = devation > 5 && (!isMotorway(prevSegm) || !isMotorway(currentSegm));
TurnType t = null;
if (rs.keepLeft && rs.keepRight) {
t = TurnType.valueOf(TurnType.C, leftSide);
} else if (rs.keepLeft) {
t = TurnType.valueOf(makeSlightTurn ? TurnType.TSLL : TurnType.KL, leftSide);
} else if (rs.keepRight) {
t = TurnType.valueOf(makeSlightTurn ? TurnType.TSLR : TurnType.KR, leftSide);
} else {
return t;
}
t.setSkipToSpeak(!rs.speak);
t.setLanes(lanes);
return t;
}
protected int countLanesMinOne(RouteSegmentResult attached) {
final boolean oneway = attached.getObject().getOneway() != 0;
int lns = attached.getObject().getLanes();
if(lns == 0) {
String tls = getTurnLanesString(attached);
if(tls != null) {
return Math.max(1, countOccurrences(tls, '|'));
}
}
if (oneway) {
return Math.max(1, lns);
}
try {
if (attached.isForwardDirection() && attached.getObject().getValue("lanes:forward") != null) {
return Integer.parseInt(attached.getObject().getValue("lanes:forward"));
} else if (!attached.isForwardDirection() && attached.getObject().getValue("lanes:backward") != null) {
return Integer.parseInt(attached.getObject().getValue("lanes:backward"));
}
} catch(NumberFormatException e) {
e.printStackTrace();
}
return Math.max(1, (lns + 1) / 2);
}
protected static String getTurnLanesString(RouteSegmentResult segment) {
if (segment.getObject().getOneway() == 0) {
if (segment.isForwardDirection()) {
return segment.getObject().getValue("turn:lanes:forward");
} else {
return segment.getObject().getValue("turn:lanes:backward");
}
} else {
return segment.getObject().getValue("turn:lanes");
}
}
private int countOccurrences(String haystack, char needle) {
int count = 0;
for (int i = 0; i < haystack.length(); i++) {
if (haystack.charAt(i) == needle) {
count++;
}
}
return count;
}
public static int[] parseTurnLanes(RouteDataObject ro, double dirToNorthEastPi) {
String turnLanes = null;
if (ro.getOneway() == 0) {
// we should get direction to detect forward or backward
double cmp = ro.directionRoute(0, true);
if(Math.abs(MapUtils.alignAngleDifference(dirToNorthEastPi -cmp)) < Math.PI / 2) {
turnLanes = ro.getValue("turn:lanes:forward");
} else {
turnLanes = ro.getValue("turn:lanes:backward");
}
} else {
turnLanes = ro.getValue("turn:lanes");
}
if(turnLanes == null) {
return null;
}
return calculateRawTurnLanes(turnLanes, 0);
}
public static int[] parseLanes(RouteDataObject ro, double dirToNorthEastPi) {
int lns = 0;
try {
if (ro.getOneway() == 0) {
// we should get direction to detect forward or backward
double cmp = ro.directionRoute(0, true);
if(Math.abs(MapUtils.alignAngleDifference(dirToNorthEastPi -cmp)) < Math.PI / 2) {
if(ro.getValue("lanes:forward") != null) {
lns = Integer.parseInt(ro.getValue("lanes:forward"));
}
} else {
if(ro.getValue("lanes:backward") != null) {
lns = Integer.parseInt(ro.getValue("lanes:backward"));
}
}
if (lns == 0 && ro.getValue("lanes") != null) {
lns = Integer.parseInt(ro.getValue("lanes")) / 2;
}
} else {
lns = Integer.parseInt(ro.getValue("lanes"));
}
if(lns > 0 ) {
return new int[lns];
}
} catch (NumberFormatException e) {
}
return null;
}
private static int[] calculateRawTurnLanes(String turnLanes, int calcTurnType) {
String[] splitLaneOptions = turnLanes.split("\\|", -1);
int[] lanes = new int[splitLaneOptions.length];
for (int i = 0; i < splitLaneOptions.length; i++) {
String[] laneOptions = splitLaneOptions[i].split(";");
for (int j = 0; j < laneOptions.length; j++) {
int turn;
if (laneOptions[j].equals("none") || laneOptions[j].equals("through")) {
turn = TurnType.C;
} else if (laneOptions[j].equals("slight_right")) {
turn = TurnType.TSLR;
} else if (laneOptions[j].equals("slight_left")) {
turn = TurnType.TSLL;
} else if (laneOptions[j].equals("right")) {
turn = TurnType.TR;
} else if (laneOptions[j].equals("left")) {
turn = TurnType.TL;
} else if (laneOptions[j].equals("sharp_right")) {
turn = TurnType.TSHR;
} else if (laneOptions[j].equals("sharp_left")) {
turn = TurnType.TSHL;
} else if (laneOptions[j].equals("reverse")) {
turn = TurnType.TU;
} else {
// Unknown string
continue;
}
final int primary = TurnType.getPrimaryTurn(lanes[i]);
if (primary == 0) {
TurnType.setPrimaryTurnAndReset(lanes, i, turn);
} else {
if (turn == calcTurnType ||
(TurnType.isRightTurn(calcTurnType) && TurnType.isRightTurn(turn)) ||
(TurnType.isLeftTurn(calcTurnType) && TurnType.isLeftTurn(turn))
) {
TurnType.setPrimaryTurnAndReset(lanes, i, turn);
TurnType.setSecondaryTurn(lanes, i, primary);
} else {
TurnType.setSecondaryTurn(lanes, i, turn);
}
break; // Move on to the next lane
}
}
}
return lanes;
}
private int inferTurnFromLanes(int[] oLanes) {
TIntHashSet possibleTurns = new TIntHashSet();
for (int i = 0; i < oLanes.length; i++) {
if ((oLanes[i] & 1) == 0) {
continue;
}
if (possibleTurns.isEmpty()) {
// Nothing is in the list to compare to, so add the first elements
possibleTurns.add(TurnType.getPrimaryTurn(oLanes[i]));
if (TurnType.getSecondaryTurn(oLanes[i]) != 0) {
possibleTurns.add(TurnType.getSecondaryTurn(oLanes[i]));
}
} else {
TIntArrayList laneTurns = new TIntArrayList();
laneTurns.add(TurnType.getPrimaryTurn(oLanes[i]));
if (TurnType.getSecondaryTurn(oLanes[i]) != 0) {
laneTurns.add(TurnType.getSecondaryTurn(oLanes[i]));
}
possibleTurns.retainAll(laneTurns);
if (possibleTurns.isEmpty()) {
// No common turns, so can't determine anything.
return 0;
}
}
}
// Remove all turns from lanes not selected...because those aren't it
for (int i = 0; i < oLanes.length; i++) {
if ((oLanes[i] & 1) == 0 && !possibleTurns.isEmpty()) {
possibleTurns.remove((Integer) TurnType.getPrimaryTurn(oLanes[i]));
if (TurnType.getSecondaryTurn(oLanes[i]) != 0) {
possibleTurns.remove((Integer) TurnType.getSecondaryTurn(oLanes[i]));
}
}
}
// Checking to see that there is only one unique turn
if (possibleTurns.size() == 1) {
int infer = possibleTurns.iterator().next();
for(int i = 0; i < oLanes.length; i++) {
if(TurnType.getSecondaryTurn(oLanes[i]) == infer) {
int pt = TurnType.getPrimaryTurn(oLanes[i]);
int en = oLanes[i] & 1;
TurnType.setPrimaryTurnAndReset(oLanes, i, infer);
oLanes[i] |= en;
TurnType.setSecondaryTurn(oLanes, i, pt);
}
}
return infer;
}
return 0;
}
private boolean isMotorway(RouteSegmentResult s){
String h = s.getObject().getHighway();
return "motorway".equals(h) || "motorway_link".equals(h) ||
"trunk".equals(h) || "trunk_link".equals(h);
}
private void attachRoadSegments(RoutingContext ctx, List<RouteSegmentResult> result, int routeInd, int pointInd, boolean plus) throws IOException {
RouteSegmentResult rr = result.get(routeInd);
RouteDataObject road = rr.getObject();
long nextL = pointInd < road.getPointsLength() - 1 ? getPoint(road, pointInd + 1) : 0;
long prevL = pointInd > 0 ? getPoint(road, pointInd - 1) : 0;
// attach additional roads to represent more information about the route
RouteSegmentResult previousResult = null;
// by default make same as this road id
long previousRoadId = road.getId();
if (pointInd == rr.getStartPointIndex() && routeInd > 0) {
previousResult = result.get(routeInd - 1);
previousRoadId = previousResult.getObject().getId();
if (previousRoadId != road.getId()) {
if (previousResult.getStartPointIndex() < previousResult.getEndPointIndex()
&& previousResult.getEndPointIndex() < previousResult.getObject().getPointsLength() - 1) {
rr.attachRoute(pointInd, new RouteSegmentResult(previousResult.getObject(), previousResult.getEndPointIndex(),
previousResult.getObject().getPointsLength() - 1));
} else if (previousResult.getStartPointIndex() > previousResult.getEndPointIndex()
&& previousResult.getEndPointIndex() > 0) {
rr.attachRoute(pointInd, new RouteSegmentResult(previousResult.getObject(), previousResult.getEndPointIndex(), 0));
}
}
}
Iterator<RouteSegment> it;
if(rr.getPreAttachedRoutes(pointInd) != null) {
final RouteSegmentResult[] list = rr.getPreAttachedRoutes(pointInd);
it = new Iterator<BinaryRoutePlanner.RouteSegment>() {
int i = 0;
@Override
public boolean hasNext() {
return i < list.length;
}
@Override
public RouteSegment next() {
RouteSegmentResult r = list[i++];
return new RouteSegment(r.getObject(), r.getStartPointIndex());
}
@Override
public void remove() {
}
};
} else {
RouteSegment rt = ctx.loadRouteSegment(road.getPoint31XTile(pointInd), road.getPoint31YTile(pointInd), ctx.config.memoryLimitation);
it = rt == null ? null : rt.getIterator();
}
// try to attach all segments except with current id
while (it != null && it.hasNext()) {
RouteSegment routeSegment = it.next();
if (routeSegment.road.getId() != road.getId() && routeSegment.road.getId() != previousRoadId) {
RouteDataObject addRoad = routeSegment.road;
checkAndInitRouteRegion(ctx, addRoad);
// TODO restrictions can be considered as well
int oneWay = ctx.getRouter().isOneWay(addRoad);
if (oneWay >= 0 && routeSegment.getSegmentStart() < addRoad.getPointsLength() - 1) {
long pointL = getPoint(addRoad, routeSegment.getSegmentStart() + 1);
if(pointL != nextL && pointL != prevL) {
// if way contains same segment (nodes) as different way (do not attach it)
rr.attachRoute(pointInd, new RouteSegmentResult(addRoad, routeSegment.getSegmentStart(), addRoad.getPointsLength() - 1));
}
}
if (oneWay <= 0 && routeSegment.getSegmentStart() > 0) {
long pointL = getPoint(addRoad, routeSegment.getSegmentStart() - 1);
// if way contains same segment (nodes) as different way (do not attach it)
if(pointL != nextL && pointL != prevL) {
rr.attachRoute(pointInd, new RouteSegmentResult(addRoad, routeSegment.getSegmentStart(), 0));
}
}
}
}
}
private static void println(String logMsg) {
// log.info(logMsg);
System.out.println(logMsg);
}
private long getPoint(RouteDataObject road, int pointInd) {
return (((long) road.getPoint31XTile(pointInd)) << 31) + (long) road.getPoint31YTile(pointInd);
}
private static double measuredDist(int x1, int y1, int x2, int y2) {
return MapUtils.getDistance(MapUtils.get31LatitudeY(y1), MapUtils.get31LongitudeX(x1),
MapUtils.get31LatitudeY(y2), MapUtils.get31LongitudeX(x2));
}
}