OsmAnd/OsmAnd-java/src/net/osmand/router/BinaryRoutePlanner.java

989 lines
36 KiB
Java
Raw Normal View History

package net.osmand.router;
import gnu.trove.map.hash.TLongObjectHashMap;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
2014-10-29 01:56:25 +01:00
import java.util.List;
import java.util.PriorityQueue;
import net.osmand.PlatformUtil;
import net.osmand.binary.RouteDataObject;
import net.osmand.osm.MapRenderingTypes;
import net.osmand.util.MapUtils;
import org.apache.commons.logging.Log;
public class BinaryRoutePlanner {
2016-07-02 13:19:27 +02:00
2014-09-22 02:08:34 +02:00
private static final int TEST_ID = 31370645;
2014-01-28 21:03:25 +01:00
private static final boolean TEST_SPECIFIC = false;
2016-07-02 13:19:27 +02:00
private static final int REVERSE_WAY_RESTRICTION_ONLY = 1024;
/*private*/ static final int STANDARD_ROAD_IN_QUEUE_OVERHEAD = 220;
/*private*/ static final int STANDARD_ROAD_VISITED_OVERHEAD = 150;
2016-07-02 13:19:27 +02:00
protected static final Log log = PlatformUtil.getLog(BinaryRoutePlanner.class);
2016-07-02 13:19:27 +02:00
private static final int ROUTE_POINTS = 11;
2014-11-28 00:58:02 +01:00
private static final boolean TRACE_ROUTING = false;
2016-07-02 13:19:27 +02:00
public static double squareRootDist(int x1, int y1, int x2, int y2) {
// translate into meters
2016-07-02 13:19:27 +02:00
double dy = MapUtils.convert31YToMeters(y1, y2);
double dx = MapUtils.convert31XToMeters(x1, x2);
return Math.sqrt(dx * dx + dy * dy);
// return measuredDist(x1, y1, x2, y2);
}
2016-07-02 13:19:27 +02:00
private static class SegmentsComparator implements Comparator<RouteSegment> {
final RoutingContext ctx;
2016-07-02 13:19:27 +02:00
public SegmentsComparator(RoutingContext ctx) {
this.ctx = ctx;
}
2016-07-02 13:19:27 +02:00
@Override
public int compare(RouteSegment o1, RouteSegment o2) {
return ctx.roadPriorityComparator(o1.distanceFromStart, o1.distanceToEnd, o2.distanceFromStart, o2.distanceToEnd);
}
}
2016-07-02 13:19:27 +02:00
private static class NonHeuristicSegmentsComparator implements Comparator<RouteSegment> {
public NonHeuristicSegmentsComparator() {
}
2016-07-02 13:19:27 +02:00
@Override
public int compare(RouteSegment o1, RouteSegment o2) {
return roadPriorityComparator(o1.distanceFromStart, o1.distanceToEnd, o2.distanceFromStart, o2.distanceToEnd, 0.5);
}
}
2016-07-02 13:19:27 +02:00
/**
* Calculate route between start.segmentEnd and end.segmentStart (using A* algorithm)
* return list of segments
*/
@SuppressWarnings("unused")
FinalRouteSegment searchRouteInternal(final RoutingContext ctx, RouteSegmentPoint start, RouteSegmentPoint end,
RouteSegment recalculationEnd ) throws InterruptedException, IOException {
// measure time
ctx.timeToLoad = 0;
ctx.visitedSegments = 0;
2016-07-02 13:19:27 +02:00
ctx.memoryOverhead = 1000;
ctx.timeToCalculate = System.nanoTime();
2016-07-02 13:19:27 +02:00
// Initializing priority queue to visit way segments
Comparator<RouteSegment> nonHeuristicSegmentsComparator = new NonHeuristicSegmentsComparator();
PriorityQueue<RouteSegment> graphDirectSegments = new PriorityQueue<RouteSegment>(50, new SegmentsComparator(ctx));
PriorityQueue<RouteSegment> graphReverseSegments = new PriorityQueue<RouteSegment>(50, new SegmentsComparator(ctx));
2016-07-02 13:19:27 +02:00
// Set to not visit one segment twice (stores road.id << X + segmentStart)
TLongObjectHashMap<RouteSegment> visitedDirectSegments = new TLongObjectHashMap<RouteSegment>();
TLongObjectHashMap<RouteSegment> visitedOppositeSegments = new TLongObjectHashMap<RouteSegment>();
2016-07-02 13:19:27 +02:00
initQueuesWithStartEnd(ctx, start, end, recalculationEnd, graphDirectSegments, graphReverseSegments);
2016-07-02 13:19:27 +02:00
// Extract & analyze segment with min(f(x)) from queue while final segment is not found
2014-01-28 21:03:25 +01:00
boolean forwardSearch = true;
2016-07-02 13:19:27 +02:00
PriorityQueue<RouteSegment> graphSegments = graphDirectSegments;
FinalRouteSegment finalSegment = null;
2014-01-28 21:03:25 +01:00
boolean onlyBackward = ctx.getPlanRoadDirection() < 0;
2016-07-02 13:19:27 +02:00
boolean onlyForward = ctx.getPlanRoadDirection() > 0;
while (!graphSegments.isEmpty()) {
RouteSegment segment = graphSegments.poll();
// use accumulative approach
2016-07-02 13:19:27 +02:00
ctx.memoryOverhead = (visitedDirectSegments.size() + visitedOppositeSegments.size()) * STANDARD_ROAD_VISITED_OVERHEAD +
(graphDirectSegments.size() +
graphReverseSegments.size()) * STANDARD_ROAD_IN_QUEUE_OVERHEAD;
2016-07-02 13:19:27 +02:00
if (TRACE_ROUTING) {
2014-03-01 15:49:21 +01:00
printRoad(">", segment, !forwardSearch);
}
2015-04-14 20:39:58 +02:00
// if(segment.getParentRoute() != null)
// System.out.println(segment.getRoad().getId() + " - " + segment.getParentRoute().getRoad().getId());
2016-07-02 13:19:27 +02:00
if (segment instanceof FinalRouteSegment) {
if (RoutingContext.SHOW_GC_SIZE) {
log.warn("Estimated overhead " + (ctx.memoryOverhead / (1 << 20)) + " mb");
printMemoryConsumption("Memory occupied after calculation : ");
}
finalSegment = (FinalRouteSegment) segment;
2016-07-02 13:19:27 +02:00
if (TRACE_ROUTING) {
2014-01-28 21:03:25 +01:00
println("Final segment found");
}
break;
}
if (ctx.memoryOverhead > ctx.config.memoryLimitation * 0.95 && RoutingContext.SHOW_GC_SIZE) {
printMemoryConsumption("Memory occupied before exception : ");
}
2016-07-02 13:19:27 +02:00
if (ctx.memoryOverhead > ctx.config.memoryLimitation * 0.95) {
throw new IllegalStateException("There is no enough memory " + ctx.config.memoryLimitation / (1 << 20) + " Mb");
}
ctx.visitedSegments++;
2014-01-28 21:03:25 +01:00
if (forwardSearch) {
boolean doNotAddIntersections = onlyBackward;
2016-07-02 13:19:27 +02:00
processRouteSegment(ctx, false, graphDirectSegments, visitedDirectSegments,
2014-01-28 21:03:25 +01:00
segment, visitedOppositeSegments, doNotAddIntersections);
} else {
2014-01-28 21:03:25 +01:00
boolean doNotAddIntersections = onlyForward;
processRouteSegment(ctx, true, graphReverseSegments, visitedOppositeSegments, segment,
2014-01-28 21:03:25 +01:00
visitedDirectSegments, doNotAddIntersections);
}
updateCalculationProgress(ctx, graphDirectSegments, graphReverseSegments);
2016-07-02 13:19:27 +02:00
2014-10-29 01:56:25 +01:00
checkIfGraphIsEmpty(ctx, ctx.getPlanRoadDirection() <= 0, graphReverseSegments, end, visitedOppositeSegments,
"Route is not found to selected target point.");
checkIfGraphIsEmpty(ctx, ctx.getPlanRoadDirection() >= 0, graphDirectSegments, start, visitedDirectSegments,
"Route is not found from selected start point.");
2014-01-28 21:03:25 +01:00
if (ctx.planRouteIn2Directions()) {
2014-01-30 00:22:12 +01:00
forwardSearch = (nonHeuristicSegmentsComparator.compare(graphDirectSegments.peek(), graphReverseSegments.peek()) < 0);
2014-07-18 17:50:38 +02:00
// if (graphDirectSegments.size() * 2 > graphReverseSegments.size()) {
// forwardSearch = false;
// } else if (graphDirectSegments.size() < 2 * graphReverseSegments.size()) {
// forwardSearch = true;
// }
} else {
// different strategy : use onedirectional graph
2014-01-28 21:03:25 +01:00
forwardSearch = onlyForward;
2016-07-02 13:19:27 +02:00
if (onlyBackward && !graphDirectSegments.isEmpty()) {
2014-01-28 21:03:25 +01:00
forwardSearch = true;
}
2016-07-02 13:19:27 +02:00
if (onlyForward && !graphReverseSegments.isEmpty()) {
2014-01-28 21:03:25 +01:00
forwardSearch = false;
}
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
if (forwardSearch) {
graphSegments = graphDirectSegments;
2014-01-28 21:03:25 +01:00
} else {
graphSegments = graphReverseSegments;
}
// check if interrupted
2016-07-02 13:19:27 +02:00
if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) {
throw new InterruptedException("Route calculation interrupted");
}
}
printDebugMemoryInformation(ctx, graphDirectSegments, graphReverseSegments, visitedDirectSegments, visitedOppositeSegments);
return finalSegment;
}
2014-10-29 01:56:25 +01:00
protected void checkIfGraphIsEmpty(final RoutingContext ctx, boolean allowDirection,
PriorityQueue<RouteSegment> graphSegments, RouteSegmentPoint pnt, TLongObjectHashMap<RouteSegment> visited,
String msg) {
if (allowDirection && graphSegments.isEmpty()) {
if (pnt.others != null) {
Iterator<RouteSegmentPoint> pntIterator = pnt.others.iterator();
while (pntIterator.hasNext()) {
RouteSegmentPoint next = pntIterator.next();
boolean visitedAlready = false;
if (next.getSegmentStart() > 0 && visited.containsKey(calculateRoutePointId(next, false))) {
visitedAlready = true;
} else if (next.getSegmentStart() < next.getRoad().getPointsLength() - 1
&& visited.containsKey(calculateRoutePointId(next, true))) {
visitedAlready = true;
}
pntIterator.remove();
if (!visitedAlready) {
float estimatedDistance = (float) estimatedDistance(ctx, ctx.targetX, ctx.targetY, ctx.startX,
ctx.startY);
RouteSegment pos = next.initRouteSegment(true);
RouteSegment neg = next.initRouteSegment(false);
if (pos != null) {
pos.distanceToEnd = estimatedDistance;
graphSegments.add(pos);
}
if (neg != null) {
neg.distanceToEnd = estimatedDistance;
graphSegments.add(neg);
}
println("Reiterate point with new start/destination " + next.getRoad());
break;
}
}
if (graphSegments.isEmpty()) {
throw new IllegalArgumentException(msg);
}
}
}
}
2016-07-02 13:19:27 +02:00
2014-11-28 00:57:34 +01:00
public RouteSegment initRouteSegment(final RoutingContext ctx, RouteSegment segment, boolean positiveDirection) {
2016-07-02 13:19:27 +02:00
if (segment.getSegmentStart() == 0 && !positiveDirection && segment.getRoad().getPointsLength() > 0) {
2014-11-28 00:57:34 +01:00
segment = loadSameSegment(ctx, segment, 1);
2016-07-02 13:19:27 +02:00
} else if (segment.getSegmentStart() == segment.getRoad().getPointsLength() - 1 && positiveDirection && segment.getSegmentStart() > 0) {
segment = loadSameSegment(ctx, segment, segment.getSegmentStart() - 1);
2014-11-28 00:57:34 +01:00
}
2016-07-02 13:19:27 +02:00
if (segment == null) {
2014-11-28 00:57:34 +01:00
return null;
}
return segment.initRouteSegment(positiveDirection);
}
protected RouteSegment loadSameSegment(final RoutingContext ctx, RouteSegment segment, int ind) {
int x31 = segment.getRoad().getPoint31XTile(ind);
int y31 = segment.getRoad().getPoint31YTile(ind);
RouteSegment s = ctx.loadRouteSegment(x31, y31, 0);
2016-07-02 13:19:27 +02:00
while (s != null) {
if (s.getRoad().getId() == segment.getRoad().getId()) {
2014-11-28 00:57:34 +01:00
segment = s;
break;
}
s = s.getNext();
}
return segment;
}
2014-10-29 01:56:25 +01:00
2014-01-28 21:03:25 +01:00
private void initQueuesWithStartEnd(final RoutingContext ctx, RouteSegment start, RouteSegment end,
RouteSegment recalculationEnd, PriorityQueue<RouteSegment> graphDirectSegments, PriorityQueue<RouteSegment> graphReverseSegments) {
2014-11-28 00:57:34 +01:00
RouteSegment startPos = initRouteSegment(ctx, start, true);
RouteSegment startNeg = initRouteSegment(ctx, start, false);
RouteSegment endPos = initRouteSegment(ctx, end, true);
RouteSegment endNeg = initRouteSegment(ctx, end, false);
2014-01-28 21:03:25 +01:00
// for start : f(start) = g(start) + h(start) = 0 + h(start) = h(start)
2016-07-02 13:19:27 +02:00
if (ctx.config.initialDirection != null) {
2014-01-28 21:03:25 +01:00
// mark here as positive for further check
double plusDir = start.getRoad().directionRoute(start.getSegmentStart(), true);
2016-07-02 13:19:27 +02:00
double diff = plusDir - ctx.config.initialDirection;
if (Math.abs(MapUtils.alignAngleDifference(diff)) <= Math.PI / 3) {
if (startNeg != null) {
2014-01-28 21:03:25 +01:00
startNeg.distanceFromStart += 500;
}
2016-07-02 13:19:27 +02:00
} else if (Math.abs(MapUtils.alignAngleDifference(diff - Math.PI)) <= Math.PI / 3) {
if (startPos != null) {
2014-01-28 21:03:25 +01:00
startPos.distanceFromStart += 500;
}
}
}
2016-07-02 13:19:27 +02:00
if (recalculationEnd != null) {
ctx.targetX = recalculationEnd.getRoad().getPoint31XTile(recalculationEnd.getSegmentStart());
ctx.targetY = recalculationEnd.getRoad().getPoint31YTile(recalculationEnd.getSegmentStart());
}
2014-01-28 21:03:25 +01:00
float estimatedDistance = (float) estimatedDistance(ctx, ctx.targetX, ctx.targetY, ctx.startX, ctx.startY);
2016-07-02 13:19:27 +02:00
if (startPos != null) {
2014-01-28 21:03:25 +01:00
startPos.distanceToEnd = estimatedDistance;
graphDirectSegments.add(startPos);
}
2016-07-02 13:19:27 +02:00
if (startNeg != null) {
2014-01-28 21:03:25 +01:00
startNeg.distanceToEnd = estimatedDistance;
graphDirectSegments.add(startNeg);
}
2016-07-02 13:19:27 +02:00
if (recalculationEnd != null) {
graphReverseSegments.add(recalculationEnd);
} else {
if (endPos != null) {
endPos.distanceToEnd = estimatedDistance;
graphReverseSegments.add(endPos);
}
if (endNeg != null) {
endNeg.distanceToEnd = estimatedDistance;
graphReverseSegments.add(endNeg);
}
2014-01-28 21:03:25 +01:00
}
}
2016-07-02 13:19:27 +02:00
private void printMemoryConsumption(String string) {
long h1 = RoutingContext.runGCUsedMemory();
float mb = (1 << 20);
2016-07-02 13:19:27 +02:00
log.warn(string + h1 / mb);
}
private void updateCalculationProgress(final RoutingContext ctx, PriorityQueue<RouteSegment> graphDirectSegments,
PriorityQueue<RouteSegment> graphReverseSegments) {
2016-07-02 13:19:27 +02:00
if (ctx.calculationProgress != null) {
ctx.calculationProgress.reverseSegmentQueueSize = graphReverseSegments.size();
ctx.calculationProgress.directSegmentQueueSize = graphDirectSegments.size();
2016-07-02 13:19:27 +02:00
if (graphDirectSegments.size() > 0 && ctx.getPlanRoadDirection() >= 0) {
2014-02-02 14:06:09 +01:00
RouteSegment peek = graphDirectSegments.peek();
2016-07-02 13:19:27 +02:00
ctx.calculationProgress.distanceFromBegin = Math.max(peek.distanceFromStart,
2014-02-02 14:06:09 +01:00
ctx.calculationProgress.distanceFromBegin);
ctx.calculationProgress.directDistance = peek.distanceFromStart + peek.distanceToEnd;
}
2016-07-02 13:19:27 +02:00
if (graphReverseSegments.size() > 0 && ctx.getPlanRoadDirection() <= 0) {
2014-02-02 14:06:09 +01:00
RouteSegment peek = graphReverseSegments.peek();
ctx.calculationProgress.distanceFromEnd = Math.max(peek.distanceFromStart + peek.distanceToEnd,
ctx.calculationProgress.distanceFromEnd);
ctx.calculationProgress.reverseDistance = peek.distanceFromStart + peek.distanceToEnd;
}
}
}
2014-03-01 15:49:21 +01:00
private void printRoad(String prefix, RouteSegment segment, Boolean reverseWaySearch) {
String pr;
2016-07-02 13:19:27 +02:00
if (segment.parentRoute != null) {
pr = " pend=" + segment.parentSegmentEnd + " parent=" + segment.parentRoute.road;
} else {
pr = "";
}
2014-03-01 15:49:21 +01:00
String p = "";
2015-04-14 14:44:43 +02:00
if (reverseWaySearch != null) {
p = (reverseWaySearch ? "B" : "F");
2014-03-01 15:49:21 +01:00
}
2016-07-02 13:19:27 +02:00
println(p + prefix + "" + segment.road + " dir=" + segment.getDirectionAssigned() + " ind=" + segment.getSegmentStart() +
" ds=" + ((float) segment.distanceFromStart) + " es=" + ((float) segment.distanceToEnd) + pr);
}
private float estimatedDistance(final RoutingContext ctx, int targetEndX, int targetEndY,
int startX, int startY) {
double distance = squareRootDist(startX, startY, targetEndX, targetEndY);
return (float) (distance / ctx.getRouter().getMaxDefaultSpeed());
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
protected static float h(RoutingContext ctx, int begX, int begY, int endX, int endY) {
2016-07-02 13:19:27 +02:00
double distToFinalPoint = squareRootDist(begX, begY, endX, endY);
2014-01-28 21:03:25 +01:00
double result = distToFinalPoint / ctx.getRouter().getMaxDefaultSpeed();
2016-07-02 13:19:27 +02:00
if (ctx.precalculatedRouteDirection != null) {
float te = ctx.precalculatedRouteDirection.timeEstimate(begX, begY, endX, endY);
if (te > 0) {
2014-01-19 21:29:11 +01:00
return te;
}
}
2016-07-02 13:19:27 +02:00
return (float) result;
}
2016-07-02 13:19:27 +02:00
private static void println(String logMsg) {
// log.info(logMsg);
System.out.println(logMsg);
}
2016-07-02 13:19:27 +02:00
private static void printInfo(String logMsg) {
log.warn(logMsg);
}
public void printDebugMemoryInformation(RoutingContext ctx, PriorityQueue<RouteSegment> graphDirectSegments, PriorityQueue<RouteSegment> graphReverseSegments,
TLongObjectHashMap<RouteSegment> visitedDirectSegments,TLongObjectHashMap<RouteSegment> visitedOppositeSegments) {
2014-01-28 21:03:25 +01:00
printInfo("Time to calculate : " + (System.nanoTime() - ctx.timeToCalculate) / 1e6 +
", time to load : " + ctx.timeToLoad / 1e6 + ", time to load headers : " + ctx.timeToLoadHeaders / 1e6 +
", time to calc dev : " + ctx.timeNanoToCalcDeviation/ 1e6);
int maxLoadedTiles = Math.max(ctx.maxLoadedTiles, ctx.getCurrentlyLoadedTiles());
printInfo("Current loaded tiles : " + ctx.getCurrentlyLoadedTiles() + ", maximum loaded tiles " + maxLoadedTiles);
2016-07-02 13:19:27 +02:00
printInfo("Loaded tiles " + ctx.loadedTiles + " (distinct " + ctx.distinctLoadedTiles + "), unloaded tiles " + ctx.unloadedTiles +
", loaded more than once same tiles "
2016-07-02 13:19:27 +02:00
+ ctx.loadedPrevUnloadedTiles);
printInfo("Visited roads " + ctx.visitedSegments + ", relaxed roads " + ctx.relaxedSegments);
if (graphDirectSegments != null && graphReverseSegments != null) {
printInfo("Priority queues sizes : " + graphDirectSegments.size() + "/" + graphReverseSegments.size());
}
if (visitedDirectSegments != null && visitedOppositeSegments != null) {
printInfo("Visited interval sizes: " + visitedDirectSegments.size() + "/" + visitedOppositeSegments.size());
}
2016-07-02 13:19:27 +02:00
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
@SuppressWarnings("unused")
private void processRouteSegment(final RoutingContext ctx, boolean reverseWaySearch,
PriorityQueue<RouteSegment> graphSegments, TLongObjectHashMap<RouteSegment> visitedSegments,
2014-01-28 21:03:25 +01:00
RouteSegment segment, TLongObjectHashMap<RouteSegment> oppositeSegments, boolean doNotAddIntersections) throws IOException {
final RouteDataObject road = segment.road;
2014-01-28 21:03:25 +01:00
boolean initDirectionAllowed = checkIfInitialMovementAllowedOnSegment(ctx, reverseWaySearch, visitedSegments, segment, road);
2016-07-02 13:19:27 +02:00
if (TEST_SPECIFIC && road.getId() == TEST_ID) {
2014-03-01 15:49:21 +01:00
printRoad(" ! " + +segment.distanceFromStart + " ", segment, reverseWaySearch);
}
2014-01-28 21:03:25 +01:00
boolean directionAllowed = initDirectionAllowed;
2016-07-02 13:19:27 +02:00
if (!directionAllowed) {
if (TRACE_ROUTING) {
2014-01-28 21:03:25 +01:00
println(" >> Already visited");
}
2014-01-28 21:03:25 +01:00
return;
}
2014-01-28 21:03:25 +01:00
// Go through all point of the way and find ways to continue
// ! Actually there is small bug when there is restriction to move forward on the way (it doesn't take into account)
float obstaclesTime = 0;
float segmentDist = 0;
// +/- diff from middle point
2014-01-28 21:03:25 +01:00
short segmentPoint = segment.getSegmentStart();
2014-02-05 21:10:04 +01:00
boolean[] processFurther = new boolean[1];
RouteSegment previous = segment;
2014-01-28 21:03:25 +01:00
boolean dir = segment.isPositive();
while (directionAllowed) {
2014-01-28 21:03:25 +01:00
// mark previous interval as visited and move to next intersection
short prevInd = segmentPoint;
2016-07-02 13:19:27 +02:00
if (dir) {
segmentPoint++;
} else {
2016-07-02 13:19:27 +02:00
segmentPoint--;
}
2014-01-28 21:03:25 +01:00
if (segmentPoint < 0 || segmentPoint >= road.getPointsLength()) {
directionAllowed = false;
continue;
}
2014-01-28 21:03:25 +01:00
// store <segment> in order to not have unique <segment, direction> in visitedSegments
2016-07-02 13:19:27 +02:00
visitedSegments.put(calculateRoutePointId(segment.getRoad(), segment.isPositive() ? segmentPoint - 1 : segmentPoint,
2014-02-05 21:10:04 +01:00
segment.isPositive()), previous != null ? previous : segment);
2014-01-28 21:03:25 +01:00
final int x = road.getPoint31XTile(segmentPoint);
final int y = road.getPoint31YTile(segmentPoint);
final int prevx = road.getPoint31XTile(prevInd);
final int prevy = road.getPoint31YTile(prevInd);
2016-07-02 13:19:27 +02:00
if (x == prevx && y == prevy) {
continue;
}
2016-07-02 13:19:27 +02:00
// 2. calculate point and try to load neighbor ways if they are not loaded
2016-07-02 13:19:27 +02:00
segmentDist += squareRootDist(x, y, prevx, prevy);
// 2.1 calculate possible obstacle plus time
2014-01-28 21:03:25 +01:00
double obstacle = ctx.getRouter().defineRoutingObstacle(road, segmentPoint);
if (obstacle < 0) {
directionAllowed = false;
continue;
}
obstaclesTime += obstacle;
2016-07-02 13:19:27 +02:00
boolean alreadyVisited = checkIfOppositieSegmentWasVisited(ctx, reverseWaySearch, graphSegments, segment, oppositeSegments,
2014-01-28 21:03:25 +01:00
segmentPoint, segmentDist, obstaclesTime);
if (alreadyVisited) {
directionAllowed = false;
continue;
}
2014-01-28 21:03:25 +01:00
// correct way of handling precalculatedRouteDirection
2016-07-02 13:19:27 +02:00
if (ctx.precalculatedRouteDirection != null) {
2014-01-28 21:03:25 +01:00
// long nt = System.nanoTime();
// float devDistance = ctx.precalculatedRouteDirection.getDeviationDistance(x, y);
// // 1. linear method
2014-03-03 14:55:32 +01:00
// segmentDist = segmentDist * (1 + devDistance / ctx.config.DEVIATION_RADIUS);
2014-01-28 21:03:25 +01:00
// // 2. exponential method
// segmentDist = segmentDist * (float) Math.pow(1.5, devDistance / 500);
2014-03-03 14:55:32 +01:00
// 3. next by method
// segmentDist = devDistance ;
2014-01-28 21:03:25 +01:00
// ctx.timeNanoToCalcDeviation += (System.nanoTime() - nt);
}
// could be expensive calculation
// 3. get intersected ways
final RouteSegment roadNext = ctx.loadRouteSegment(x, y, ctx.config.memoryLimitation - ctx.memoryOverhead);
2016-07-02 13:19:27 +02:00
float distStartObstacles = segment.distanceFromStart + calculateTimeWithObstacles(ctx, road, segmentDist, obstaclesTime);
if (ctx.precalculatedRouteDirection != null && ctx.precalculatedRouteDirection.isFollowNext()) {
2014-03-03 14:55:32 +01:00
// reset to f
2014-03-03 18:44:17 +01:00
// distStartObstacles = 0;
2014-03-03 14:55:32 +01:00
// more precise but slower
2014-03-03 18:44:17 +01:00
distStartObstacles = ctx.precalculatedRouteDirection.getDeviationDistance(x, y) / ctx.getRouter().getMaxDefaultSpeed();
2014-03-03 14:55:32 +01:00
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
// We don't check if there are outgoing connections
2014-02-05 21:10:04 +01:00
previous = processIntersections(ctx, graphSegments, visitedSegments, distStartObstacles,
segment, segmentPoint, roadNext, reverseWaySearch, doNotAddIntersections, processFurther);
if (!processFurther[0]) {
2014-01-28 21:03:25 +01:00
directionAllowed = false;
continue;
}
}
2016-07-02 13:19:27 +02:00
if (initDirectionAllowed && ctx.visitor != null) {
2014-01-28 21:03:25 +01:00
ctx.visitor.visitSegment(segment, segmentPoint, true);
}
}
private boolean checkIfInitialMovementAllowedOnSegment(final RoutingContext ctx, boolean reverseWaySearch,
2014-01-28 21:03:25 +01:00
TLongObjectHashMap<RouteSegment> visitedSegments, RouteSegment segment, final RouteDataObject road) {
boolean directionAllowed;
int oneway = ctx.getRouter().isOneWay(road);
// use positive direction as agreed
if (!reverseWaySearch) {
2016-07-02 13:19:27 +02:00
if (segment.isPositive()) {
directionAllowed = oneway >= 0;
} else {
directionAllowed = oneway <= 0;
}
} else {
2016-07-02 13:19:27 +02:00
if (segment.isPositive()) {
directionAllowed = oneway <= 0;
} else {
directionAllowed = oneway >= 0;
}
}
2016-07-02 13:19:27 +02:00
if (directionAllowed && visitedSegments.containsKey(calculateRoutePointId(segment, segment.isPositive()))) {
2014-01-28 21:03:25 +01:00
directionAllowed = false;
}
return directionAllowed;
}
2016-07-02 13:19:27 +02:00
2015-01-16 01:53:19 +01:00
private boolean checkViaRestrictions(RouteSegment from, RouteSegment to) {
2016-07-02 13:19:27 +02:00
if (from != null && to != null) {
2015-01-16 01:53:19 +01:00
long fid = to.getRoad().getId();
2016-07-02 13:19:27 +02:00
for (int i = 0; i < from.getRoad().getRestrictionLength(); i++) {
2015-01-16 01:53:19 +01:00
long id = from.getRoad().getRestrictionId(i);
2016-07-02 13:19:27 +02:00
if (fid == id) {
2015-01-16 01:53:19 +01:00
int tp = from.getRoad().getRestrictionType(i);
2016-07-02 13:19:27 +02:00
if (tp == MapRenderingTypes.RESTRICTION_NO_LEFT_TURN ||
tp == MapRenderingTypes.RESTRICTION_NO_RIGHT_TURN ||
tp == MapRenderingTypes.RESTRICTION_NO_STRAIGHT_ON ||
2015-01-16 01:53:19 +01:00
tp == MapRenderingTypes.RESTRICTION_NO_U_TURN) {
return false;
}
break;
}
}
}
return true;
}
2016-07-02 13:19:27 +02:00
2015-01-16 01:53:19 +01:00
private RouteSegment getParentDiffId(RouteSegment s) {
2016-07-02 13:19:27 +02:00
while (s.getParentRoute() != null && s.getParentRoute().getRoad().getId() == s.getRoad().getId()) {
2015-01-16 01:53:19 +01:00
s = s.getParentRoute();
}
return s.getParentRoute();
}
private boolean checkIfOppositieSegmentWasVisited(final RoutingContext ctx, boolean reverseWaySearch,
PriorityQueue<RouteSegment> graphSegments, RouteSegment segment, TLongObjectHashMap<RouteSegment> oppositeSegments,
2014-01-28 21:03:25 +01:00
int segmentPoint, float segmentDist, float obstaclesTime) {
RouteDataObject road = segment.getRoad();
long opp = calculateRoutePointId(road, segment.isPositive() ? segmentPoint - 1 : segmentPoint, !segment.isPositive());
if (oppositeSegments.containsKey(opp)) {
RouteSegment opposite = oppositeSegments.get(opp);
2015-01-16 01:53:19 +01:00
RouteSegment to = reverseWaySearch ? getParentDiffId(segment) : getParentDiffId(opposite);
RouteSegment from = !reverseWaySearch ? getParentDiffId(segment) : getParentDiffId(opposite);
if (checkViaRestrictions(from, to)) {
FinalRouteSegment frs = new FinalRouteSegment(road, segmentPoint);
float distStartObstacles = segment.distanceFromStart
+ calculateTimeWithObstacles(ctx, road, segmentDist, obstaclesTime);
frs.setParentRoute(segment);
frs.setParentSegmentEnd(segmentPoint);
frs.reverseWaySearch = reverseWaySearch;
frs.distanceFromStart = opposite.distanceFromStart + distStartObstacles;
frs.distanceToEnd = 0;
frs.opposite = opposite;
graphSegments.add(frs);
if (TRACE_ROUTING) {
printRoad(" >> Final segment : ", frs, reverseWaySearch);
}
return true;
}
}
return false;
}
2016-07-02 13:19:27 +02:00
private float calculateTimeWithObstacles(RoutingContext ctx, RouteDataObject road, float distOnRoadToPass, float obstaclesTime) {
float priority = ctx.getRouter().defineSpeedPriority(road);
2014-02-02 14:06:09 +01:00
float speed = (ctx.getRouter().defineRoutingSpeed(road) * priority);
if (speed == 0) {
speed = (ctx.getRouter().getMinDefaultSpeed() * priority);
}
// speed can not exceed max default speed according to A*
2016-07-02 13:19:27 +02:00
if (speed > ctx.getRouter().getMaxDefaultSpeed()) {
speed = ctx.getRouter().getMaxDefaultSpeed();
}
2013-07-31 20:22:39 +02:00
return obstaclesTime + distOnRoadToPass / speed;
}
private long calculateRoutePointId(final RouteDataObject road, int intervalId, boolean positive) {
2016-07-02 13:19:27 +02:00
if (intervalId < 0) {
2014-01-28 21:03:25 +01:00
// should be assert
throw new IllegalStateException("Assert failed");
}
return (road.getId() << ROUTE_POINTS) + (intervalId << 1) + (positive ? 1 : 0);
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
private long calculateRoutePointId(RouteSegment segm, boolean direction) {
2016-07-02 13:19:27 +02:00
if (segm.getSegmentStart() == 0 && !direction) {
throw new IllegalStateException("Assert failed");
2014-01-28 21:03:25 +01:00
}
2016-07-02 13:19:27 +02:00
if (segm.getSegmentStart() == segm.getRoad().getPointsLength() - 1 && direction) {
throw new IllegalStateException("Assert failed");
2014-01-28 21:03:25 +01:00
}
return calculateRoutePointId(segm.getRoad(),
direction ? segm.getSegmentStart() : segm.getSegmentStart() - 1, direction);
}
2015-01-16 01:53:19 +01:00
private boolean proccessRestrictions(RoutingContext ctx, RouteSegment segment, RouteSegment inputNext, boolean reverseWay) {
2016-07-02 13:19:27 +02:00
if (!ctx.getRouter().restrictionsAware()) {
return false;
}
2015-01-16 01:53:19 +01:00
RouteDataObject road = segment.getRoad();
RouteSegment parent = getParentDiffId(segment);
2016-07-02 13:19:27 +02:00
if (!reverseWay && road.getRestrictionLength() == 0 &&
2015-01-16 01:53:19 +01:00
(parent == null || parent.getRoad().getRestrictionLength() == 0)) {
return false;
}
2015-01-16 01:53:19 +01:00
ctx.segmentsToVisitPrescripted.clear();
ctx.segmentsToVisitNotForbidden.clear();
processRestriction(ctx, inputNext, reverseWay, false, road);
2016-07-02 13:19:27 +02:00
if (parent != null) {
2015-01-16 01:53:19 +01:00
processRestriction(ctx, inputNext, reverseWay, true, parent.getRoad());
}
return true;
}
protected void processRestriction(RoutingContext ctx, RouteSegment inputNext, boolean reverseWay, boolean via,
RouteDataObject road) {
RouteSegment next = inputNext;
boolean exclusiveRestriction = false;
while (next != null) {
int type = -1;
if (!reverseWay) {
for (int i = 0; i < road.getRestrictionLength(); i++) {
if (road.getRestrictionId(i) == next.road.id) {
type = road.getRestrictionType(i);
break;
}
}
} else {
for (int i = 0; i < next.road.getRestrictionLength(); i++) {
int rt = next.road.getRestrictionType(i);
long restrictedTo = next.road.getRestrictionId(i);
if (restrictedTo == road.id) {
type = rt;
break;
}
// Check if there is restriction only to the other than current road
if (rt == MapRenderingTypes.RESTRICTION_ONLY_RIGHT_TURN || rt == MapRenderingTypes.RESTRICTION_ONLY_LEFT_TURN
|| rt == MapRenderingTypes.RESTRICTION_ONLY_STRAIGHT_ON) {
// check if that restriction applies to considered junk
RouteSegment foundNext = inputNext;
while (foundNext != null) {
if (foundNext.getRoad().id == restrictedTo) {
break;
}
foundNext = foundNext.next;
}
if (foundNext != null) {
type = REVERSE_WAY_RESTRICTION_ONLY; // special constant
}
}
}
}
if (type == REVERSE_WAY_RESTRICTION_ONLY) {
// next = next.next; continue;
} else if (type == -1 && exclusiveRestriction) {
// next = next.next; continue;
} else if (type == MapRenderingTypes.RESTRICTION_NO_LEFT_TURN || type == MapRenderingTypes.RESTRICTION_NO_RIGHT_TURN
|| type == MapRenderingTypes.RESTRICTION_NO_STRAIGHT_ON || type == MapRenderingTypes.RESTRICTION_NO_U_TURN) {
// next = next.next; continue;
2016-07-02 13:19:27 +02:00
if (via) {
2015-01-16 01:53:19 +01:00
ctx.segmentsToVisitPrescripted.remove(next);
}
} else if (type == -1) {
// case no restriction
ctx.segmentsToVisitNotForbidden.add(next);
} else {
2015-01-16 01:53:19 +01:00
if (!via) {
// case exclusive restriction (only_right, only_straight, ...)
// 1. in case we are going backward we should not consider only_restriction
// as exclusive because we have many "in" roads and one "out"
// 2. in case we are going forward we have one "in" and many "out"
if (!reverseWay) {
exclusiveRestriction = true;
ctx.segmentsToVisitNotForbidden.clear();
ctx.segmentsToVisitPrescripted.add(next);
} else {
ctx.segmentsToVisitNotForbidden.add(next);
}
}
}
next = next.next;
}
2016-07-02 13:19:27 +02:00
if (!via) {
2015-01-16 01:53:19 +01:00
ctx.segmentsToVisitPrescripted.addAll(ctx.segmentsToVisitNotForbidden);
}
}
2014-02-05 21:10:04 +01:00
private RouteSegment processIntersections(RoutingContext ctx, PriorityQueue<RouteSegment> graphSegments,
2014-03-03 14:55:32 +01:00
TLongObjectHashMap<RouteSegment> visitedSegments, float distFromStart, RouteSegment segment,
2014-02-05 21:10:04 +01:00
short segmentPoint, RouteSegment inputNext, boolean reverseWaySearch, boolean doNotAddIntersections,
boolean[] processFurther) {
2016-07-02 13:19:27 +02:00
boolean thereAreRestrictions;
2014-02-05 21:10:04 +01:00
RouteSegment itself = null;
processFurther[0] = true;
Iterator<RouteSegment> nextIterator = null;
2016-07-02 13:19:27 +02:00
if (inputNext != null && inputNext.getRoad().getId() == segment.getRoad().getId() && inputNext.next == null) {
2014-01-28 21:03:25 +01:00
thereAreRestrictions = false;
} else {
2015-01-16 01:53:19 +01:00
thereAreRestrictions = proccessRestrictions(ctx, segment, inputNext, reverseWaySearch);
2014-01-28 21:03:25 +01:00
if (thereAreRestrictions) {
nextIterator = ctx.segmentsToVisitPrescripted.iterator();
if (TRACE_ROUTING) {
println(" >> There are restrictions");
}
}
}
2014-01-28 21:03:25 +01:00
int targetEndX = reverseWaySearch ? ctx.startX : ctx.targetX;
int targetEndY = reverseWaySearch ? ctx.startY : ctx.targetY;
float distanceToEnd = h(ctx, segment.getRoad().getPoint31XTile(segmentPoint), segment.getRoad()
.getPoint31YTile(segmentPoint), targetEndX, targetEndY);
// Calculate possible ways to put into priority queue
RouteSegment next = inputNext;
boolean hasNext = nextIterator == null || nextIterator.hasNext();
while (hasNext) {
if (nextIterator != null) {
next = nextIterator.next();
}
2014-01-28 21:03:25 +01:00
if (next.getSegmentStart() == segmentPoint && next.getRoad().getId() == segment.getRoad().id) {
// find segment itself
// (and process it as other with small exception that we don't add to graph segments and process immediately)
2014-02-05 21:10:04 +01:00
itself = next.initRouteSegment(segment.isPositive());
2016-07-02 13:19:27 +02:00
if (itself == null) {
2014-01-28 21:03:25 +01:00
// do nothing
} else if (itself.getParentRoute() == null
|| ctx.roadPriorityComparator(itself.distanceFromStart, itself.distanceToEnd, distFromStart,
distanceToEnd) > 0) {
itself.distanceFromStart = distFromStart;
itself.distanceToEnd = distanceToEnd;
itself.setParentRoute(segment);
itself.setParentSegmentEnd(segmentPoint);
} else {
// we already processed that segment earlier or it is in graph segments
// and we had better results (so we shouldn't process)
2014-02-05 21:10:04 +01:00
processFurther[0] = false;
}
2016-07-02 13:19:27 +02:00
} else if (!doNotAddIntersections) {
2014-01-28 21:03:25 +01:00
RouteSegment nextPos = next.initRouteSegment(true);
RouteSegment nextNeg = next.initRouteSegment(false);
processOneRoadIntersection(ctx, graphSegments, visitedSegments, distFromStart, distanceToEnd, segment, segmentPoint,
nextPos);
processOneRoadIntersection(ctx, graphSegments, visitedSegments, distFromStart, distanceToEnd, segment, segmentPoint,
nextNeg);
}
// iterate to next road
if (nextIterator == null) {
next = next.next;
hasNext = next != null;
} else {
hasNext = nextIterator.hasNext();
}
}
2014-02-05 21:10:04 +01:00
return itself;
2014-01-28 21:03:25 +01:00
}
@SuppressWarnings("unused")
private void processOneRoadIntersection(RoutingContext ctx, PriorityQueue<RouteSegment> graphSegments,
TLongObjectHashMap<RouteSegment> visitedSegments, float distFromStart, float distanceToEnd, RouteSegment segment,
int segmentPoint, RouteSegment next) {
if (next != null) {
float obstaclesTime = (float) ctx.getRouter().calculateTurnTime(next, next.isPositive()?
next.getRoad().getPointsLength() - 1 : 0,
segment, segmentPoint);
distFromStart += obstaclesTime;
2016-07-02 13:19:27 +02:00
if (TEST_SPECIFIC && next.road.getId() == TEST_ID) {
printRoad(" !? distFromStart=" + +distFromStart + " from " + segment.getRoad().getId() +
" dir=" + segment.getDirectionAssigned() +
2014-01-28 21:03:25 +01:00
" distToEnd=" + distanceToEnd +
2016-07-02 13:19:27 +02:00
" segmentPoint=" + segmentPoint + " -- ", next, true);
2014-01-28 21:03:25 +01:00
}
if (!visitedSegments.containsKey(calculateRoutePointId(next, next.isPositive()))) {
if (next.getParentRoute() == null
2014-01-28 21:03:25 +01:00
|| ctx.roadPriorityComparator(next.distanceFromStart, next.distanceToEnd,
distFromStart, distanceToEnd) > 0) {
next.distanceFromStart = distFromStart;
next.distanceToEnd = distanceToEnd;
2014-01-28 21:03:25 +01:00
if (TRACE_ROUTING) {
2014-03-01 15:49:21 +01:00
printRoad(" >>", next, null);
}
// put additional information to recover whole route after
next.setParentRoute(segment);
2014-01-28 21:03:25 +01:00
next.setParentSegmentEnd(segmentPoint);
graphSegments.add(next);
}
2014-01-28 21:03:25 +01:00
} else {
// the segment was already visited! We need to follow better route if it exists
2014-01-28 21:03:25 +01:00
// that is very exceptional situation and almost exception, it can happen
// 1. when we underestimate distnceToEnd - wrong h()
// 2. because we process not small segments but the whole road, it could be that
// deviation from the road is faster than following the whole road itself!
if (distFromStart < next.distanceFromStart) {
if (ctx.config.heuristicCoefficient <= 1) {
System.err.println("! Alert distance from start " + distFromStart + " < "
2016-07-02 13:19:27 +02:00
+ next.distanceFromStart + " id=" + next.road.id);
}
2014-01-28 21:03:25 +01:00
// A: we can't change parent route just here, because we need to update visitedSegments
// presumably we can do visitedSegments.put(calculateRoutePointId(next), next);
// next.distanceFromStart = distFromStart;
// next.setParentRoute(segment);
// next.setParentSegmentEnd(segmentPoint);
if (ctx.visitor != null) {
2014-01-28 21:03:25 +01:00
// ctx.visitor.visitSegment(next, false);
}
}
}
}
}
/*public */static int roadPriorityComparator(double o1DistanceFromStart, double o1DistanceToEnd,
double o2DistanceFromStart, double o2DistanceToEnd, double heuristicCoefficient ) {
// f(x) = g(x) + h(x) --- g(x) - distanceFromStart, h(x) - distanceToEnd (not exact)
return Double.compare(o1DistanceFromStart + heuristicCoefficient * o1DistanceToEnd,
o2DistanceFromStart + heuristicCoefficient * o2DistanceToEnd);
}
public interface RouteSegmentVisitor {
public void visitSegment(RouteSegment segment, int segmentEnd, boolean poll);
}
2014-10-29 01:56:25 +01:00
public static class RouteSegmentPoint extends RouteSegment {
2015-12-09 23:24:06 +01:00
public RouteSegmentPoint(RouteDataObject road, int segmentStart, double distSquare) {
2014-09-22 02:08:34 +02:00
super(road, segmentStart);
2015-12-09 23:24:06 +01:00
this.distSquare = distSquare;
2014-09-22 02:08:34 +02:00
}
2014-10-29 01:56:25 +01:00
2015-12-09 23:24:06 +01:00
public double distSquare;
2014-09-22 02:08:34 +02:00
public int preciseX;
public int preciseY;
2014-10-29 01:56:25 +01:00
public List<RouteSegmentPoint> others;
2014-09-22 02:08:34 +02:00
}
2016-07-02 13:19:27 +02:00
public static class RouteSegment {
final short segStart;
final RouteDataObject road;
// needed to store intersection of routes
RouteSegment next = null;
2014-01-28 21:03:25 +01:00
RouteSegment oppositeDirection = null;
2016-07-02 13:19:27 +02:00
// search context (needed for searching route)
// Initially it should be null (!) because it checks was it segment visited before
RouteSegment parentRoute = null;
short parentSegmentEnd = 0;
// 1 - positive , -1 - negative, 0 not assigned
byte directionAssgn = 0;
2016-07-02 13:19:27 +02:00
// distance measured in time (seconds)
float distanceFromStart = 0;
float distanceToEnd = 0;
2016-07-02 13:19:27 +02:00
public RouteSegment(RouteDataObject road, int segmentStart) {
this.road = road;
this.segStart = (short) segmentStart;
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
public RouteSegment initRouteSegment(boolean positiveDirection) {
2016-07-02 13:19:27 +02:00
if (segStart == 0 && !positiveDirection) {
2014-01-28 21:03:25 +01:00
return null;
}
2016-07-02 13:19:27 +02:00
if (segStart == road.getPointsLength() - 1 && positiveDirection) {
2014-01-28 21:03:25 +01:00
return null;
}
RouteSegment rs = this;
2016-07-02 13:19:27 +02:00
if (directionAssgn == 0) {
2014-01-28 21:03:25 +01:00
rs.directionAssgn = (byte) (positiveDirection ? 1 : -1);
} else {
2016-07-02 13:19:27 +02:00
if (positiveDirection != (directionAssgn == 1)) {
if (oppositeDirection == null) {
2014-01-28 21:03:25 +01:00
oppositeDirection = new RouteSegment(road, segStart);
oppositeDirection.directionAssgn = (byte) (positiveDirection ? 1 : -1);
}
if ((oppositeDirection.directionAssgn == 1) != positiveDirection) {
throw new IllegalStateException();
}
rs = oppositeDirection;
}
}
return rs;
}
2016-07-02 13:19:27 +02:00
public byte getDirectionAssigned() {
return directionAssgn;
}
2016-07-02 13:19:27 +02:00
public RouteSegment getParentRoute() {
return parentRoute;
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
public boolean isPositive() {
return directionAssgn == 1;
}
2016-07-02 13:19:27 +02:00
public void setParentRoute(RouteSegment parentRoute) {
this.parentRoute = parentRoute;
}
2016-07-02 13:19:27 +02:00
public void assignDirection(byte b) {
directionAssgn = b;
}
2016-07-02 13:19:27 +02:00
public void setParentSegmentEnd(int parentSegmentEnd) {
this.parentSegmentEnd = (short) parentSegmentEnd;
}
2016-07-02 13:19:27 +02:00
public int getParentSegmentEnd() {
return parentSegmentEnd;
}
2016-07-02 13:19:27 +02:00
public RouteSegment getNext() {
return next;
}
2016-07-02 13:19:27 +02:00
2014-01-28 21:03:25 +01:00
public short getSegmentStart() {
return segStart;
}
2016-07-02 13:19:27 +02:00
2014-03-06 01:57:31 +01:00
public float getDistanceFromStart() {
return distanceFromStart;
}
2016-07-02 13:19:27 +02:00
2014-03-06 01:57:31 +01:00
public void setDistanceFromStart(float distanceFromStart) {
this.distanceFromStart = distanceFromStart;
}
2016-07-02 13:19:27 +02:00
public RouteDataObject getRoad() {
return road;
}
2016-07-02 13:19:27 +02:00
public String getTestName() {
return MessageFormat.format("s{0,number,#.##} e{1,number,#.##}", ((float) distanceFromStart), ((float) distanceToEnd));
}
2016-07-02 13:19:27 +02:00
public Iterator<RouteSegment> getIterator() {
return new Iterator<BinaryRoutePlanner.RouteSegment>() {
RouteSegment next = RouteSegment.this;
2016-07-02 13:19:27 +02:00
@Override
public void remove() {
throw new UnsupportedOperationException();
}
2016-07-02 13:19:27 +02:00
@Override
public RouteSegment next() {
RouteSegment c = next;
2016-07-02 13:19:27 +02:00
if (next != null) {
next = next.next;
}
return c;
}
2016-07-02 13:19:27 +02:00
@Override
public boolean hasNext() {
return next != null;
}
};
}
}
2016-07-02 13:19:27 +02:00
static class FinalRouteSegment extends RouteSegment {
2016-07-02 13:19:27 +02:00
boolean reverseWaySearch;
RouteSegment opposite;
public FinalRouteSegment(RouteDataObject road, int segmentStart) {
super(road, segmentStart);
}
2016-07-02 13:19:27 +02:00
}
2016-07-02 13:19:27 +02:00
}