package net.osmand.plus.routing;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.osmand.Location;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteRegion;
import net.osmand.binary.BinaryMapRouteReaderAdapter.RouteTypeRule;
import net.osmand.data.LatLon;
import net.osmand.data.LocationPoint;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.R;
import net.osmand.router.RouteSegmentResult;
import net.osmand.router.TurnType;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import android.content.Context;
import static net.osmand.binary.RouteDataObject.HEIGHT_UNDEFINED;
public class RouteCalculationResult {
private static double distanceClosestToIntermediate = 400;
// could not be null and immodifiable!
private final List<Location> locations;
private final List<RouteDirectionInfo> directions;
private final List<RouteSegmentResult> segments;
private final List<AlarmInfo> alarmInfo;
private final String errorMessage;
private final int[] listDistance;
private final int[] intermediatePoints;
private final float routingTime;
protected int cacheCurrentTextDirectionInfo = -1;
protected List<RouteDirectionInfo> cacheAgreggatedDirections;
protected List<LocationPoint> locationPoints = new ArrayList<LocationPoint>();
// Note always currentRoute > get(currentDirectionInfo).routeOffset,
// but currentRoute <= get(currentDirectionInfo+1).routeOffset
protected int currentDirectionInfo = 0;
protected int currentRoute = 0;
protected int nextIntermediate = 0;
protected int currentWaypointGPX = 0;
protected int lastWaypointGPX = 0;
protected ApplicationMode appMode;
public RouteCalculationResult(String errorMessage) {
this.errorMessage = errorMessage;
this.routingTime = 0;
this.intermediatePoints = new int[0];
this.locations = new ArrayList<Location>();
this.segments = new ArrayList<RouteSegmentResult>();
this.listDistance = new int[0];
this.directions = new ArrayList<RouteDirectionInfo>();
this.alarmInfo = new ArrayList<AlarmInfo>();
}
public RouteCalculationResult(List<Location> list, List<RouteDirectionInfo> directions, RouteCalculationParams params, List<LocationPoint> waypoints, boolean addMissingTurns) {
this.routingTime = 0;
this.errorMessage = null;
this.intermediatePoints = new int[params.intermediates == null ? 0 : params.intermediates.size()];
List<Location> locations = list == null ? new ArrayList<Location>() : new ArrayList<Location>(list);
List<RouteDirectionInfo> localDirections = directions == null? new ArrayList<RouteDirectionInfo>() : new ArrayList<RouteDirectionInfo>(directions);
if (!locations.isEmpty()) {
checkForDuplicatePoints(locations, localDirections);
}
if(waypoints != null) {
this.locationPoints.addAll(waypoints);
}
if(addMissingTurns) {
removeUnnecessaryGoAhead(localDirections);
addMissingTurnsToRoute(locations, localDirections, params.start,params.end,
params.mode, params.ctx, params.leftSide);
// if there is no closest points to start - add it
introduceFirstPointAndLastPoint(locations, localDirections, null, params.start, params.end);
}
this.appMode = params.mode;
this.locations = Collections.unmodifiableList(locations);
this.segments = new ArrayList<RouteSegmentResult>();
this.listDistance = new int[locations.size()];
updateListDistanceTime(this.listDistance, this.locations);
this.alarmInfo = new ArrayList<AlarmInfo>();
calculateIntermediateIndexes(params.ctx, this.locations, params.intermediates, localDirections, this.intermediatePoints);
this.directions = Collections.unmodifiableList(localDirections);
updateDirectionsTime(this.directions, this.listDistance);
}
public RouteCalculationResult(List<RouteSegmentResult> list, Location start, LatLon end, List<LatLon> intermediates,
OsmandApplication ctx, boolean leftSide, float routingTime, List<LocationPoint> waypoints, ApplicationMode mode) {
this.routingTime = routingTime;
if(waypoints != null) {
this.locationPoints.addAll(waypoints);
}
List<RouteDirectionInfo> computeDirections = new ArrayList<RouteDirectionInfo>();
this.errorMessage = null;
this.intermediatePoints = new int[intermediates == null ? 0 : intermediates.size()];
List<Location> locations = new ArrayList<Location>();
ArrayList<AlarmInfo> alarms = new ArrayList<AlarmInfo>();
List<RouteSegmentResult> segments = convertVectorResult(computeDirections, locations, list, alarms, ctx);
introduceFirstPointAndLastPoint(locations, computeDirections, segments, start, end);
this.locations = Collections.unmodifiableList(locations);
this.segments = Collections.unmodifiableList(segments);
this.listDistance = new int[locations.size()];
calculateIntermediateIndexes(ctx, this.locations, intermediates, computeDirections, this.intermediatePoints);
updateListDistanceTime(this.listDistance, this.locations);
this.appMode = mode;
this.directions = Collections.unmodifiableList(computeDirections);
updateDirectionsTime(this.directions, this.listDistance);
this.alarmInfo = Collections.unmodifiableList(alarms);
}
public ApplicationMode getAppMode() {
return appMode;
}
public List<LocationPoint> getLocationPoints() {
return locationPoints;
}
public List<AlarmInfo> getAlarmInfo() {
return alarmInfo;
}
private static void calculateIntermediateIndexes(Context ctx, List<Location> locations,
List<LatLon> intermediates, List<RouteDirectionInfo> localDirections, int[] intermediatePoints) {
if(intermediates != null && localDirections != null) {
int[] interLocations = new int[intermediates.size()];
int currentIntermediate = 0;
int currentLocation = 0;
double distanceThreshold = 25;
double prevDistance = distanceThreshold * 4;
while((currentIntermediate < intermediates.size() || prevDistance > distanceThreshold)
&& currentLocation < locations.size()){
if(currentIntermediate < intermediates.size() &&
getDistanceToLocation(locations, intermediates.get(currentIntermediate), currentLocation) < distanceClosestToIntermediate) {
prevDistance = getDistanceToLocation(locations, intermediates.get(currentIntermediate), currentLocation);
interLocations[currentIntermediate] = currentLocation;
currentIntermediate++;
} else if(currentIntermediate > 0 && prevDistance > distanceThreshold &&
getDistanceToLocation(locations, intermediates.get(currentIntermediate - 1),
currentLocation) < prevDistance) {
prevDistance = getDistanceToLocation(locations, intermediates.get(currentIntermediate - 1), currentLocation);
interLocations[currentIntermediate - 1] = currentLocation;
}
currentLocation ++;
}
int currentDirection = 0;
currentIntermediate = 0;
while(currentIntermediate < intermediates.size() && currentDirection < localDirections.size()){
int locationIndex = localDirections.get(currentDirection).routePointOffset ;
if (locationIndex >= interLocations[currentIntermediate]) {
// split directions
if (locationIndex > interLocations[currentIntermediate]
&& getDistanceToLocation(locations, intermediates.get(currentIntermediate), locationIndex) > 50) {
RouteDirectionInfo toSplit = localDirections.get(currentDirection);
RouteDirectionInfo info = new RouteDirectionInfo(localDirections.get(currentDirection).getAverageSpeed(), TurnType.straight());
info.setRef(toSplit.getRef());
info.setStreetName(toSplit.getStreetName());
info.setDestinationName(toSplit.getDestinationName());
info.routePointOffset = interLocations[currentIntermediate];
info.setDescriptionRoute(ctx.getString(R.string.route_head));//; //$NON-NLS-1$
localDirections.add(currentDirection, info);
}
intermediatePoints[currentIntermediate] = currentDirection;
currentIntermediate++;
}
currentDirection ++;
}
}
}
private static double getDistanceToLocation(List<Location> locations, LatLon p, int currentLocation) {
return MapUtils.getDistance(p,
locations.get(currentLocation).getLatitude(), locations.get(currentLocation).getLongitude());
}
private static void attachAlarmInfo(List<AlarmInfo> alarms, RouteSegmentResult res, int intId, int locInd) {
int[] pointTypes = res.getObject().getPointTypes(intId);
if (pointTypes != null) {
RouteRegion reg = res.getObject().region;
for (int r = 0; r < pointTypes.length; r++) {
RouteTypeRule typeRule = reg.quickGetEncodingRule(pointTypes[r]);
int x31 = res.getObject().getPoint31XTile(intId);
int y31 = res.getObject().getPoint31YTile(intId);
Location loc = new Location("");
loc.setLatitude(MapUtils.get31LatitudeY(y31));
loc.setLongitude(MapUtils.get31LongitudeX(x31));
AlarmInfo info = AlarmInfo.createAlarmInfo(typeRule, locInd, loc);
if(info != null) {
alarms.add(info);
}
}
}
}
public List<RouteSegmentResult> getOriginalRoute() {
if (segments.size() == 0) {
return null;
}
List<RouteSegmentResult> list = new ArrayList<RouteSegmentResult>();
list.add(segments.get(0));
for (int i = 1; i < segments.size(); i++) {
if (segments.get(i - 1) != segments.get(i)) {
list.add(segments.get(i));
}
}
return list;
}
/**
* PREPARATION
*/
private static List<RouteSegmentResult> convertVectorResult(List<RouteDirectionInfo> directions, List<Location> locations, List<RouteSegmentResult> list,
List<AlarmInfo> alarms, OsmandApplication ctx) {
float prevDirectionTime = 0;
float prevDirectionDistance = 0;
double lastHeight = HEIGHT_UNDEFINED;
List<RouteSegmentResult> segmentsToPopulate = new ArrayList<RouteSegmentResult>();
for (int routeInd = 0; routeInd < list.size(); routeInd++) {
RouteSegmentResult s = list.get(routeInd);
float[] vls = s.getObject().calculateHeightArray();
boolean plus = s.getStartPointIndex() < s.getEndPointIndex();
int i = s.getStartPointIndex();
int prevLocationSize = locations.size();
while (true) {
Location n = new Location(""); //$NON-NLS-1$
LatLon point = s.getPoint(i);
n.setLatitude(point.getLatitude());
n.setLongitude(point.getLongitude());
if (i == s.getEndPointIndex() && routeInd != list.size() - 1) {
break;
}
if (vls != null && i * 2 + 1 < vls.length) {
float h = vls[2 * i + 1];
n.setAltitude(h);
if (lastHeight == HEIGHT_UNDEFINED && locations.size() > 0) {
for (Location l : locations) {
if (!l.hasAltitude()) {
l.setAltitude(h);
}
}
}
lastHeight = h;
}
locations.add(n);
attachAlarmInfo(alarms, s, i, locations.size());
segmentsToPopulate.add(s);
if (i == s.getEndPointIndex() ) {
break;
}
if (plus) {
i++;
} else {
i--;
}
}
TurnType turn = s.getTurnType();
if(turn != null) {
RouteDirectionInfo info = new RouteDirectionInfo(s.getSegmentSpeed(), turn);
if (routeInd < list.size()) {
int lind = routeInd;
if(turn.isRoundAbout()) {
int roundAboutEnd = prevLocationSize ;
// take next name for roundabout (not roundabout name)
while (lind < list.size() - 1 && list.get(lind).getObject().roundabout()) {
roundAboutEnd += Math.abs(list.get(lind).getEndPointIndex()
- list.get(lind).getStartPointIndex());
lind++;
}
// Consider roundabout end.
info.routeEndPointOffset = roundAboutEnd;
}
RouteSegmentResult next = list.get(lind);
info.setRef(next.getObject().getRef(ctx.getSettings().MAP_PREFERRED_LOCALE.get(),
ctx.getSettings().MAP_TRANSLITERATE_NAMES.get(), next.isForwardDirection()));
info.setStreetName(next.getObject().getName(ctx.getSettings().MAP_PREFERRED_LOCALE.get(),
ctx.getSettings().MAP_TRANSLITERATE_NAMES.get()));
info.setDestinationName(next.getObject().getDestinationName(ctx.getSettings().MAP_PREFERRED_LOCALE.get(),
ctx.getSettings().MAP_TRANSLITERATE_NAMES.get(), next.isForwardDirection()));
}
String description = toString(turn, ctx, false) + " " + RoutingHelper.formatStreetName(info.getStreetName(),
info.getRef(), info.getDestinationName(), ctx.getString(R.string.towards));
description = description.trim();
String[] pointNames = s.getObject().getPointNames(s.getStartPointIndex());
if(pointNames != null) {
for (int t = 0; t < pointNames.length; t++) {
description = description.trim();
description += " " + pointNames[t];
}
}
info.setDescriptionRoute(description);
info.routePointOffset = prevLocationSize;
if(directions.size() > 0 && prevDirectionTime > 0 && prevDirectionDistance > 0) {
RouteDirectionInfo prev = directions.get(directions.size() - 1);
prev.setAverageSpeed(prevDirectionDistance / prevDirectionTime);
prevDirectionDistance = 0;
prevDirectionTime = 0;
}
directions.add(info);
}
prevDirectionDistance += s.getDistance();
prevDirectionTime += s.getSegmentTime();
}
if(directions.size() > 0 && prevDirectionTime > 0 && prevDirectionDistance > 0) {
RouteDirectionInfo prev = directions.get(directions.size() - 1);
prev.setAverageSpeed(prevDirectionDistance / prevDirectionTime);
}
return segmentsToPopulate;
}
protected static void addMissingTurnsToRoute(List<Location> locations,
List<RouteDirectionInfo> originalDirections, Location start, LatLon end, ApplicationMode mode, Context ctx,
boolean leftSide){
if(locations.isEmpty()){
return;
}
// speed m/s
float speed = mode.getDefaultSpeed();
int minDistanceForTurn = mode.getMinDistanceForTurn();
List<RouteDirectionInfo> computeDirections = new ArrayList<RouteDirectionInfo>();
int[] listDistance = new int[locations.size()];
listDistance[locations.size() - 1] = 0;
for (int i = locations.size() - 1; i > 0; i--) {
listDistance[i - 1] = (int) Math.round(locations.get(i - 1).distanceTo(locations.get(i)));
listDistance[i - 1] += listDistance[i];
}
int previousLocation = 0;
int prevBearingLocation = 0;
RouteDirectionInfo previousInfo = new RouteDirectionInfo(speed, TurnType.straight());
previousInfo.routePointOffset = 0;
previousInfo.setDescriptionRoute(ctx.getString( R.string.route_head));
computeDirections.add(previousInfo);
int distForTurn = 0;
float previousBearing = 0;
int startTurnPoint = 0;
for (int i = 1; i < locations.size() - 1; i++) {
Location next = locations.get(i + 1);
Location current = locations.get(i);
float bearing = current.bearingTo(next);
// try to get close to current location if possible
while(prevBearingLocation < i - 1){
if(locations.get(prevBearingLocation + 1).distanceTo(current) > 70){
prevBearingLocation ++;
} else {
break;
}
}
if(distForTurn == 0){
// measure only after turn
previousBearing = locations.get(prevBearingLocation).bearingTo(current);
startTurnPoint = i;
}
TurnType type = null;
String description = null;
float delta = previousBearing - bearing;
while(delta < 0){
delta += 360;
}
while(delta > 360){
delta -= 360;
}
distForTurn += locations.get(i).distanceTo(locations.get(i + 1));
if (i < locations.size() - 1 && distForTurn < minDistanceForTurn) {
// For very smooth turn we try to accumulate whole distance
// simply skip that turn needed for situation
// 1) if you are going to have U-turn - not 2 left turns
// 2) if there is a small gap between roads (turn right and after 4m next turn left) - so the direction head
continue;
}
if(delta > 45 && delta < 315){
if(delta < 60){
type = TurnType.valueOf(TurnType.TSLL, leftSide);
description = ctx.getString( R.string.route_tsll);
} else if(delta < 120){
type = TurnType.valueOf(TurnType.TL, leftSide);
description = ctx.getString( R.string.route_tl);
} else if(delta < 150){
type = TurnType.valueOf(TurnType.TSHL, leftSide);
description = ctx.getString( R.string.route_tshl);
} else if(delta < 210){
type = TurnType.valueOf(TurnType.TU, leftSide);
description = ctx.getString( R.string.route_tu);
} else if(delta < 240){
description = ctx.getString( R.string.route_tshr);
type = TurnType.valueOf(TurnType.TSHR, leftSide);
} else if(delta < 300){
description = ctx.getString( R.string.route_tr);
type = TurnType.valueOf(TurnType.TR, leftSide);
} else {
description = ctx.getString( R.string.route_tslr);
type = TurnType.valueOf(TurnType.TSLR, leftSide);
}
// calculate for previousRoute
previousInfo.distance = listDistance[previousLocation]- listDistance[i];
type.setTurnAngle(360 - delta);
previousInfo = new RouteDirectionInfo(speed, type);
previousInfo.setDescriptionRoute(description);
previousInfo.routePointOffset = startTurnPoint;
computeDirections.add(previousInfo);
previousLocation = startTurnPoint;
prevBearingLocation = i; // for bearing using current location
}
// clear dist for turn
distForTurn = 0;
}
previousInfo.distance = listDistance[previousLocation];
if (originalDirections.isEmpty()) {
originalDirections.addAll(computeDirections);
} else {
int currentDirection = 0;
// one more
for (int i = 0; i <= originalDirections.size() && currentDirection < computeDirections.size(); i++) {
while (currentDirection < computeDirections.size()) {
int distanceAfter = 0;
if (i < originalDirections.size()) {
RouteDirectionInfo resInfo = originalDirections.get(i);
int r1 = computeDirections.get(currentDirection).routePointOffset;
int r2 = resInfo.routePointOffset;
distanceAfter = listDistance[resInfo.routePointOffset];
float dist = locations.get(r1).distanceTo(locations.get(r2));
// take into account that move roundabout is special turn that could be very lengthy
if (dist < 100) {
// the same turn duplicate
currentDirection++;
continue; // while cycle
} else if (computeDirections.get(currentDirection).routePointOffset > resInfo.routePointOffset) {
// check it at the next point
break;
}
}
// add turn because it was missed
RouteDirectionInfo toAdd = computeDirections.get(currentDirection);
if (i > 0) {
// update previous
RouteDirectionInfo previous = originalDirections.get(i - 1);
toAdd.setAverageSpeed(previous.getAverageSpeed());
}
toAdd.distance = listDistance[toAdd.routePointOffset] - distanceAfter;
if (i < originalDirections.size()) {
originalDirections.add(i, toAdd);
} else {
originalDirections.add(toAdd);
}
i++;
currentDirection++;
}
}
}
int sum = 0;
for (int i = originalDirections.size() - 1; i >= 0; i--) {
originalDirections.get(i).afterLeftTime = sum;
sum += originalDirections.get(i).getExpectedTime();
}
}
public static String toString(TurnType type, Context ctx, boolean shortName) {
if(type.isRoundAbout()){
if (shortName) {
return ctx.getString(R.string.route_roundabout_short, type.getExitOut());
} else {
return ctx.getString(R.string.route_roundabout, type.getExitOut());
}
} else if(type.getValue() == TurnType.C) {
return ctx.getString(R.string.route_head);
} else if(type.getValue() == TurnType.TSLL) {
return ctx.getString(R.string.route_tsll);
} else if(type.getValue() == TurnType.TL) {
return ctx.getString(R.string.route_tl);
} else if(type.getValue() == TurnType.TSHL) {
return ctx.getString(R.string.route_tshl);
} else if(type.getValue() == TurnType.TSLR) {
return ctx.getString(R.string.route_tslr);
} else if(type.getValue() == TurnType.TR) {
return ctx.getString(R.string.route_tr);
} else if(type.getValue() == TurnType.TSHR) {
return ctx.getString(R.string.route_tshr);
} else if(type.getValue() == TurnType.TU) {
return ctx.getString(R.string.route_tu);
} else if(type.getValue() == TurnType.TRU) {
return ctx.getString(R.string.route_tu);
} else if(type.getValue() == TurnType.KL) {
return ctx.getString(R.string.route_kl);
} else if(type.getValue() == TurnType.KR) {
return ctx.getString(R.string.route_kr);
}
return "";
}
public String getErrorMessage() {
return errorMessage;
}
/**
* PREPARATION
* Remove unnecessary go straight from CloudMade.
* Remove also last direction because it will be added after.
*/
private void removeUnnecessaryGoAhead(List<RouteDirectionInfo> directions) {
if (directions != null && directions.size() > 1) {
for (int i = 1; i < directions.size();) {
RouteDirectionInfo r = directions.get(i);
if (r.getTurnType().getValue() == TurnType.C) {
RouteDirectionInfo prev = directions.get(i - 1);
prev.setAverageSpeed((prev.distance + r.distance)
/ (prev.distance / prev.getAverageSpeed() + r.distance / r.getAverageSpeed()));
prev.setDistance(prev.distance + r.distance);
directions.remove(i);
} else {
i++;
}
}
}
}
/**
* PREPARATION
* Check points for duplicates (it is very bad for routing) - cloudmade could return it
*/
public static void checkForDuplicatePoints(List<Location> locations, List<RouteDirectionInfo> directions) {
//
for (int i = 0; i < locations.size() - 1;) {
if (locations.get(i).distanceTo(locations.get(i + 1)) == 0) {
locations.remove(i);
if (directions != null) {
for (RouteDirectionInfo info : directions) {
if (info.routePointOffset > i) {
info.routePointOffset--;
}
}
}
} else {
i++;
}
}
}
/**
* PREPARATION
* If beginning is too far from start point, then introduce GO Ahead
* @param end
*/
private static void introduceFirstPointAndLastPoint(List<Location> locations, List<RouteDirectionInfo> directions, List<RouteSegmentResult> segs, Location start,
LatLon end) {
if (!locations.isEmpty() && locations.get(0).distanceTo(start) > 50) {
// add start point
locations.add(0, start);
if(segs != null) {
segs.add(0, segs.get(0));
}
if (directions != null && !directions.isEmpty()) {
for (RouteDirectionInfo i : directions) {
i.routePointOffset++;
}
RouteDirectionInfo info = new RouteDirectionInfo(directions.get(0).getAverageSpeed(),
TurnType.straight());
info.routePointOffset = 0;
// info.setDescriptionRoute(ctx.getString( R.string.route_head));//; //$NON-NLS-1$
directions.add(0, info);
}
checkForDuplicatePoints(locations, directions);
}
RouteDirectionInfo lastDirInf = directions.size() > 0 ? directions.get(directions.size() - 1) : null;
if((lastDirInf == null || lastDirInf.routePointOffset < locations.size() - 1) && locations.size() - 1 > 0) {
int type = TurnType.C;
Location prevLast = locations.get(locations.size() - 2);
float lastBearing = prevLast.bearingTo(locations.get(locations.size() - 1));
float[] compute = new float[2];
Location.distanceBetween(prevLast.getLatitude(), prevLast.getLongitude(),
end.getLatitude(), end.getLongitude(), compute);
float bearingToEnd = compute[1];
double diff = MapUtils.degreesDiff(lastBearing, bearingToEnd);
if(Math.abs(diff) > 10) {
type = diff > 0 ? TurnType.KL : TurnType.KR;
}
// Wrong AvgSpeed for the last turn can cause significantly wrong total travel time if calculated route ends on a GPX route segment (then last turn is where GPX is joined again)
RouteDirectionInfo info = new RouteDirectionInfo(lastDirInf != null ? lastDirInf.getAverageSpeed() : 1, TurnType.valueOf(type, false));
info.distance = 0;
info.afterLeftTime = 0;
info.routePointOffset = locations.size() - 1;
directions.add(info);
}
}
/**
* PREPARATION
* At the end always update listDistance local vars and time
*/
private static void updateListDistanceTime(int[] listDistance, List<Location> locations) {
if (listDistance.length > 0) {
listDistance[locations.size() - 1] = 0;
for (int i = locations.size() - 1; i > 0; i--) {
listDistance[i - 1] = (int) Math.round(locations.get(i - 1).distanceTo(locations.get(i)));
listDistance[i - 1] += listDistance[i];
}
}
}
/**
* PREPARATION
* At the end always update listDistance local vars and time
*/
private static void updateDirectionsTime(List<RouteDirectionInfo> directions, int[] listDistance) {
int sum = 0;
for (int i = directions.size() - 1; i >= 0; i--) {
directions.get(i).afterLeftTime = sum;
directions.get(i).distance = listDistance[directions.get(i).routePointOffset];
if (i < directions.size() - 1) {
directions.get(i).distance -= listDistance[directions.get(i + 1).routePointOffset];
}
sum += directions.get(i).getExpectedTime();
}
}
//////////////////// MUST BE ALL SYNCHRONIZED ??? //////////////////////
public List<Location> getImmutableAllLocations() {
return locations;
}
public List<RouteDirectionInfo> getImmutableAllDirections() {
return directions;
}
public List<Location> getRouteLocations() {
if(currentRoute < locations.size()) {
return locations.subList(currentRoute, locations.size());
}
return Collections.emptyList();
}
public int getRouteDistanceToFinish(int posFromCurrentIndex) {
if(listDistance != null && currentRoute + posFromCurrentIndex < listDistance.length){
return listDistance[currentRoute + posFromCurrentIndex];
}
return 0;
}
public RouteSegmentResult getCurrentSegmentResult() {
int cs = currentRoute > 0 ? currentRoute - 1 : 0;
if(cs < segments.size()) {
return segments.get(cs);
}
return null;
}
public List<RouteSegmentResult> getUpcomingTunnel(float distToStart) {
int cs = currentRoute > 0 ? currentRoute - 1 : 0;
if(cs < segments.size()) {
RouteSegmentResult prev = null;
boolean tunnel = false;
while(cs < segments.size() && distToStart > 0) {
RouteSegmentResult segment = segments.get(cs);
if(segment != prev ) {
if(segment.getObject().tunnel()){
tunnel = true;
break;
} else {
distToStart -= segment.getDistance();
prev = segment;
}
}
cs++;
}
if(tunnel) {
List<RouteSegmentResult> list = new ArrayList<RouteSegmentResult>();
while(cs < segments.size()) {
RouteSegmentResult segment = segments.get(cs);
if(segment != prev ) {
if(segment.getObject().tunnel()) {
list.add(segment);
} else {
break;
}
prev = segment;
}
cs++;
}
return list;
}
}
return null;
}
public float getCurrentMaxSpeed() {
RouteSegmentResult res = getCurrentSegmentResult();
if(res != null) {
return res.getObject().getMaximumSpeed(res.isForwardDirection());
}
return 0;
}
public float getRoutingTime() {
return routingTime;
}
public int getWholeDistance() {
if(listDistance.length > 0) {
return listDistance[0];
}
return 0;
}
public boolean isCalculated() {
return !locations.isEmpty();
}
public boolean isEmpty() {
return locations.isEmpty() || currentRoute >= locations.size();
}
public void updateCurrentRoute(int currentRoute) {
this.currentRoute = currentRoute;
while (currentDirectionInfo < directions.size() - 1
&& directions.get(currentDirectionInfo + 1).routePointOffset < currentRoute
&& directions.get(currentDirectionInfo + 1).routeEndPointOffset < currentRoute) {
currentDirectionInfo++;
}
while(nextIntermediate < intermediatePoints.length) {
RouteDirectionInfo dir = directions.get(intermediatePoints[nextIntermediate]);
if(dir.routePointOffset < currentRoute) {
nextIntermediate ++;
} else {
break;
}
}
}
public int getCurrentRoute() {
return currentRoute;
}
public void passIntermediatePoint(){
nextIntermediate ++ ;
}
public int getNextIntermediate() {
return nextIntermediate;
}
public Location getLocationFromRouteDirection(RouteDirectionInfo i){
if(i != null && locations != null && i.routePointOffset < locations.size()){
return locations.get(i.routePointOffset);
}
return null;
}
/*public */NextDirectionInfo getNextRouteDirectionInfo(NextDirectionInfo info, Location fromLoc, boolean toSpeak) {
int dirInfo = currentDirectionInfo;
if (dirInfo < directions.size()) {
// Locate next direction of interest
int nextInd = dirInfo + 1;
if (toSpeak) {
while (nextInd < directions.size()) {
RouteDirectionInfo i = directions.get(nextInd);
if (i.getTurnType() != null && !i.getTurnType().isSkipToSpeak()) {
break;
}
nextInd++;
}
}
int dist = listDistance[currentRoute];
if (fromLoc != null) {
dist += fromLoc.distanceTo(locations.get(currentRoute));
}
if (nextInd < directions.size()) {
info.directionInfo = directions.get(nextInd);
if (directions.get(nextInd).routePointOffset <= currentRoute
&& currentRoute <= directions.get(nextInd).routeEndPointOffset)
// We are not into a puntual direction.
dist -= listDistance[directions.get(nextInd).routeEndPointOffset];
else
dist -= listDistance[directions.get(nextInd).routePointOffset];
}
if(intermediatePoints != null && nextIntermediate < intermediatePoints.length) {
info.intermediatePoint = intermediatePoints[nextIntermediate] == nextInd;
}
info.directionInfoInd = nextInd;
info.distanceTo = dist;
return info;
}
info.directionInfoInd = -1;
info.distanceTo = -1;
info.directionInfo = null;
return null;
}
/*public */NextDirectionInfo getNextRouteDirectionInfoAfter(NextDirectionInfo prev, NextDirectionInfo next, boolean toSpeak) {
int dirInfo = prev.directionInfoInd;
if (dirInfo < directions.size() && prev.directionInfo != null) {
int dist = listDistance[prev.directionInfo.routePointOffset];
int nextInd = dirInfo + 1;
if (toSpeak) {
while (nextInd < directions.size()) {
RouteDirectionInfo i = directions.get(nextInd);
if (i.getTurnType() != null && !i.getTurnType().isSkipToSpeak()) {
break;
}
nextInd++;
}
}
if (nextInd < directions.size()) {
next.directionInfo = directions.get(nextInd);
dist -= listDistance[directions.get(nextInd).routePointOffset];
}
if(intermediatePoints != null && nextIntermediate < intermediatePoints.length) {
next.intermediatePoint = intermediatePoints[nextIntermediate] == nextInd;
}
next.distanceTo = dist;
next.directionInfoInd = nextInd;
return next;
}
next.directionInfoInd = -1;
next.distanceTo = -1;
next.directionInfo = null;
return null;
}
public List<RouteDirectionInfo> getRouteDirections() {
if(currentDirectionInfo < directions.size() - 1){
if(cacheCurrentTextDirectionInfo != currentDirectionInfo) {
cacheCurrentTextDirectionInfo = currentDirectionInfo;
List<RouteDirectionInfo> list = currentDirectionInfo == 0 ? directions :
directions.subList(currentDirectionInfo + 1, directions.size());
cacheAgreggatedDirections = new ArrayList<RouteDirectionInfo>();
RouteDirectionInfo p = null;
for(RouteDirectionInfo i : list) {
// if(p == null || !i.getTurnType().isSkipToSpeak() ||
// (!Algorithms.objectEquals(p.getRef(), i.getRef()) &&
// !Algorithms.objectEquals(p.getStreetName(), i.getStreetName()))) {
if(p == null ||
(i.getTurnType() != null && !i.getTurnType().isSkipToSpeak())) {
p = new RouteDirectionInfo(i.getAverageSpeed(), i.getTurnType());
p.routePointOffset = i.routePointOffset;
p.routeEndPointOffset = i.routeEndPointOffset;
p.setDestinationName(i.getDestinationName());
p.setRef(i.getRef());
p.setStreetName(i.getStreetName());
p.setDescriptionRoute(i.getDescriptionRoutePart());
cacheAgreggatedDirections.add(p);
}
float time = i.getExpectedTime() + p.getExpectedTime();
p.distance += i.distance;
p.setAverageSpeed(p.distance / time);
p.afterLeftTime = i.afterLeftTime;
}
}
return cacheAgreggatedDirections;
}
return Collections.emptyList();
}
public Location getNextRouteLocation() {
if(currentRoute < locations.size()) {
return locations.get(currentRoute);
}
return null;
}
public Location getNextRouteLocation(int after) {
if(currentRoute + after < locations.size()) {
return locations.get(currentRoute + after);
}
return null;
}
public boolean directionsAvailable(){
return currentDirectionInfo < directions.size();
}
public int getDistanceToPoint(int locationIndex) {
if(listDistance != null && currentRoute < listDistance.length && locationIndex < listDistance.length &&
locationIndex > currentRoute){
return listDistance[currentRoute] - listDistance[locationIndex];
}
return 0;
}
public int getDistanceToFinish(Location fromLoc) {
if(listDistance != null && currentRoute < listDistance.length){
int dist = listDistance[currentRoute];
Location l = locations.get(currentRoute);
if(fromLoc != null){
dist += fromLoc.distanceTo(l);
}
return dist;
}
return 0;
}
public int getDistanceToNextIntermediate(Location fromLoc) {
if(listDistance != null && currentRoute < listDistance.length){
int dist = listDistance[currentRoute];
Location l = locations.get(currentRoute);
if(fromLoc != null){
dist += fromLoc.distanceTo(l);
}
if(nextIntermediate >= intermediatePoints.length ){
return 0;
} else {
int directionInd = intermediatePoints[nextIntermediate];
return dist - listDistance[directions.get(directionInd).routePointOffset];
}
}
return 0;
}
public int getIndexOfIntermediate(int countFromLast) {
final int j = intermediatePoints.length - countFromLast - 1;
if(j < intermediatePoints.length && j >= 0) {
int i = intermediatePoints[j];
return directions.get(i).routePointOffset;
}
return -1;
}
public int getIntermediatePointsToPass(){
if(nextIntermediate >= intermediatePoints.length) {
return 0;
}
return intermediatePoints.length - nextIntermediate;
}
public int getLeftTime(Location fromLoc){
int time = 0;
if(currentDirectionInfo < directions.size()) {
RouteDirectionInfo current = directions.get(currentDirectionInfo);
time = current.afterLeftTime;
int distanceToNextTurn = listDistance[currentRoute];
if(currentDirectionInfo + 1 < directions.size()) {
distanceToNextTurn -= listDistance[directions.get(currentDirectionInfo + 1).routePointOffset];
}
Location l = locations.get(currentRoute);
if(fromLoc != null){
distanceToNextTurn += fromLoc.distanceTo(l);
}
time += distanceToNextTurn / current.getAverageSpeed();
}
return time;
}
public static class NextDirectionInfo {
public RouteDirectionInfo directionInfo;
public int distanceTo;
public boolean intermediatePoint;
public String pointName;
public int imminent;
private int directionInfoInd;
}
}