package net.osmand.plus.routing;
import net.osmand.Location;
import net.osmand.PlatformUtil;
import net.osmand.ValueHolder;
import net.osmand.data.LatLon;
import net.osmand.plus.ApplicationMode;
import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.NavigationService;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.TargetPointsHelper;
import net.osmand.plus.TargetPointsHelper.TargetPoint;
import net.osmand.plus.notifications.OsmandNotification.NotificationType;
import net.osmand.plus.routing.RouteCalculationResult.NextDirectionInfo;
import net.osmand.plus.routing.RouteProvider.GPXRouteParamsBuilder;
import net.osmand.plus.routing.RouteProvider.RouteService;
import net.osmand.router.RouteCalculationProgress;
import net.osmand.router.RouteSegmentResult;
import net.osmand.router.TurnType;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import static net.osmand.plus.notifications.OsmandNotification.NotificationType.NAVIGATION;
public class RoutingHelper {
private static final org.apache.commons.logging.Log log = PlatformUtil.getLog(RoutingHelper.class);
public interface IRouteInformationListener {
void newRouteIsCalculated(boolean newRoute, ValueHolder<Boolean> showToast);
void routeWasCancelled();
void routeWasFinished();
}
private static final float POSITION_TOLERANCE = 60;
private List<WeakReference<IRouteInformationListener>> listeners = new LinkedList<WeakReference<IRouteInformationListener>>();
private OsmandApplication app;
private boolean isFollowingMode = false;
private boolean isRoutePlanningMode = false;
private boolean isPauseNavigation = false;
private GPXRouteParamsBuilder currentGPXRoute = null;
private RouteCalculationResult route = new RouteCalculationResult("");
private LatLon finalLocation;
private List<LatLon> intermediatePoints;
private Location lastProjection;
private Location lastFixedLocation;
private static final int RECALCULATE_THRESHOLD_COUNT_CAUSING_FULL_RECALCULATE = 3;
private static final int RECALCULATE_THRESHOLD_CAUSING_FULL_RECALCULATE_INTERVAL = 2*60*1000;
private Thread currentRunningJob;
private long lastTimeEvaluatedRoute = 0;
private String lastRouteCalcError;
private String lastRouteCalcErrorShort;
private long recalculateCountInInterval = 0;
private int evalWaitInterval = 0;
private ApplicationMode mode;
private OsmandSettings settings;
private RouteProvider provider;
private VoiceRouter voiceRouter;
private static boolean isDeviatedFromRoute = false;
private long deviateFromRouteDetected = 0;
//private long wrongMovementDetected = 0;
private boolean voiceRouterStopped = false;
private RouteCalculationProgressCallback progressRoute;
// private ProgressBar progress;
// private Handler progressHandler;
public boolean isDeviatedFromRoute() {
return isDeviatedFromRoute;
}
public RoutingHelper(OsmandApplication context){
this.app = context;
settings = context.getSettings();
voiceRouter = new VoiceRouter(this, settings);
provider = new RouteProvider();
setAppMode(settings.APPLICATION_MODE.get());
}
public boolean isFollowingMode() {
return isFollowingMode;
}
public OsmandApplication getApplication() {
return app;
}
public String getLastRouteCalcError() {
return lastRouteCalcError;
}
public String getLastRouteCalcErrorShort() {
return lastRouteCalcErrorShort;
}
public void setPauseNaviation(boolean b) {
this.isPauseNavigation = b;
if (b) {
if (app.getNavigationService() != null) {
app.getNavigationService().stopIfNeeded(app, NavigationService.USED_BY_NAVIGATION);
} else {
app.getNotificationHelper().updateTopNotification();
app.getNotificationHelper().refreshNotifications();
}
} else {
app.startNavigationService(NavigationService.USED_BY_NAVIGATION, 0);
}
}
public boolean isPauseNavigation() {
return isPauseNavigation;
}
public void setFollowingMode(boolean follow) {
isFollowingMode = follow;
isPauseNavigation = false;
if (!follow) {
if (app.getNavigationService() != null) {
app.getNavigationService().stopIfNeeded(app, NavigationService.USED_BY_NAVIGATION);
} else {
app.getNotificationHelper().updateTopNotification();
app.getNotificationHelper().refreshNotifications();
}
} else {
app.startNavigationService(NavigationService.USED_BY_NAVIGATION, 0);
}
}
public boolean isRoutePlanningMode() {
return isRoutePlanningMode;
}
public void setRoutePlanningMode(boolean isRoutePlanningMode) {
this.isRoutePlanningMode = isRoutePlanningMode;
}
public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, List<LatLon> intermediatePoints, Location currentLocation){
RouteCalculationResult previousRoute = route;
clearCurrentRoute(finalLocation, intermediatePoints);
// to update route
setCurrentLocation(currentLocation, false, previousRoute, true);
}
public synchronized void clearCurrentRoute(LatLon newFinalLocation, List<LatLon> newIntermediatePoints) {
route = new RouteCalculationResult("");
isDeviatedFromRoute = false;
evalWaitInterval = 0;
app.getWaypointHelper().setNewRoute(route);
app.runInUIThread(new Runnable() {
@Override
public void run() {
Iterator<WeakReference<IRouteInformationListener>> it = listeners.iterator();
while(it.hasNext()) {
WeakReference<IRouteInformationListener> ref = it.next();
IRouteInformationListener l = ref.get();
if(l == null) {
it.remove();
} else {
l.routeWasCancelled();
}
}
}
});
this.finalLocation = newFinalLocation;
this.intermediatePoints = newIntermediatePoints;
if(currentRunningJob instanceof RouteRecalculationThread) {
((RouteRecalculationThread) currentRunningJob).stopCalculation();
}
if (newFinalLocation == null) {
settings.FOLLOW_THE_ROUTE.set(false);
settings.FOLLOW_THE_GPX_ROUTE.set(null);
// clear last fixed location
this.lastProjection = null;
setFollowingMode(false);
}
}
private synchronized void finishCurrentRoute() {
app.runInUIThread(new Runnable() {
@Override
public void run() {
Iterator<WeakReference<IRouteInformationListener>> it = listeners.iterator();
while(it.hasNext()) {
WeakReference<IRouteInformationListener> ref = it.next();
IRouteInformationListener l = ref.get();
if(l == null) {
it.remove();
} else {
l.routeWasFinished();
}
}
}
});
}
public GPXRouteParamsBuilder getCurrentGPXRoute() {
return currentGPXRoute;
}
public void setGpxParams(GPXRouteParamsBuilder params) {
currentGPXRoute = params;
}
public List<Location> getCurrentCalculatedRoute() {
return route.getImmutableAllLocations();
}
public void setAppMode(ApplicationMode mode){
this.mode = mode;
voiceRouter.updateAppMode();
}
public ApplicationMode getAppMode() {
return mode;
}
public LatLon getFinalLocation() {
return finalLocation;
}
public List<LatLon> getIntermediatePoints() {
return intermediatePoints;
}
public boolean isRouteCalculated(){
return route.isCalculated();
}
public VoiceRouter getVoiceRouter() {
return voiceRouter;
}
public Location getLastProjection(){
return lastProjection;
}
public void addListener(IRouteInformationListener l){
listeners.add(new WeakReference<RoutingHelper.IRouteInformationListener>(l));
}
public boolean removeListener(IRouteInformationListener lt){
Iterator<WeakReference<IRouteInformationListener>> it = listeners.iterator();
while(it.hasNext()) {
WeakReference<IRouteInformationListener> ref = it.next();
IRouteInformationListener l = ref.get();
if(l == null || lt == l) {
it.remove();
return true;
}
}
return false;
}
public void updateLocation(Location currentLocation) {
if(isFollowingMode() || (settings.getPointToStart() == null && isRoutePlanningMode) ||
app.getLocationProvider().getLocationSimulation().isRouteAnimating()) {
setCurrentLocation(currentLocation, false);
}
}
public Location setCurrentLocation(Location currentLocation, boolean returnUpdatedLocation) {
return setCurrentLocation(currentLocation, returnUpdatedLocation, route, false);
}
public double getRouteDeviation(){
if (route == null ||
route.getImmutableAllDirections().size() < 2 ||
route.currentRoute == 0){
return 0;
}
List<Location> routeNodes = route.getImmutableAllLocations();
return getOrthogonalDistance(lastFixedLocation, routeNodes.get(route.currentRoute -1), routeNodes.get(route.currentRoute));
}
private Location setCurrentLocation(Location currentLocation, boolean returnUpdatedLocation,
RouteCalculationResult previousRoute, boolean targetPointsChanged) {
Location locationProjection = currentLocation;
if (finalLocation == null || currentLocation == null) {
isDeviatedFromRoute = false;
return locationProjection;
}
float posTolerance = POSITION_TOLERANCE;
if(currentLocation.hasAccuracy()) {
posTolerance = POSITION_TOLERANCE / 2 + currentLocation.getAccuracy();
}
boolean calculateRoute = false;
synchronized (this) {
isDeviatedFromRoute = false;
double distOrth = 0;
// 0. Route empty or needs to be extended? Then re-calculate route.
if(route.isEmpty()) {
calculateRoute = true;
} else {
// 1. Update current route position status according to latest received location
boolean finished = updateCurrentRouteStatus(currentLocation, posTolerance);
if (finished) {
return null;
}
List<Location> routeNodes = route.getImmutableAllLocations();
int currentRoute = route.currentRoute;
// 2. Analyze if we need to recalculate route
// >100m off current route (sideways)
if (currentRoute > 0) {
distOrth = getOrthogonalDistance(currentLocation, routeNodes.get(currentRoute - 1), routeNodes.get(currentRoute));
if ((!settings.DISABLE_OFFROUTE_RECALC.get()) && (distOrth > (1.7 * posTolerance))) {
log.info("Recalculate route, because correlation : " + distOrth); //$NON-NLS-1$
isDeviatedFromRoute = true;
calculateRoute = true;
}
}
// 3. Identify wrong movement direction
Location next = route.getNextRouteLocation();
boolean wrongMovementDirection = checkWrongMovementDirection(currentLocation, next);
if ((!settings.DISABLE_WRONG_DIRECTION_RECALC.get()) && wrongMovementDirection && (currentLocation.distanceTo(routeNodes.get(currentRoute)) > (2 * posTolerance))) {
log.info("Recalculate route, because wrong movement direction: " + currentLocation.distanceTo(routeNodes.get(currentRoute))); //$NON-NLS-1$
isDeviatedFromRoute = true;
calculateRoute = true;
}
// 4. Identify if UTurn is needed
if (identifyUTurnIsNeeded(currentLocation, posTolerance)) {
isDeviatedFromRoute = true;
}
// 5. Update Voice router
// Do not update in route planning mode
if (isFollowingMode) {
boolean inRecalc = calculateRoute || isRouteBeingCalculated();
if (!inRecalc && !wrongMovementDirection) {
voiceRouter.updateStatus(currentLocation, false);
voiceRouterStopped = false;
} else if (isDeviatedFromRoute && !voiceRouterStopped) {
voiceRouter.interruptRouteCommands();
voiceRouterStopped = true; // Prevents excessive execution of stop() code
}
if (distOrth > 350) {
voiceRouter.announceOffRoute(distOrth);
}
}
// calculate projection of current location
if (currentRoute > 0) {
locationProjection = new Location(currentLocation);
Location nextLocation = routeNodes.get(currentRoute);
LatLon project = getProject(currentLocation, routeNodes.get(currentRoute - 1), routeNodes.get(currentRoute));
locationProjection.setLatitude(project.getLatitude());
locationProjection.setLongitude(project.getLongitude());
// we need to update bearing too
float bearingTo = locationProjection.bearingTo(nextLocation);
locationProjection.setBearing(bearingTo);
}
}
lastFixedLocation = currentLocation;
lastProjection = locationProjection;
}
if (calculateRoute) {
recalculateRouteInBackground(currentLocation, finalLocation, intermediatePoints, currentGPXRoute,
previousRoute.isCalculated() ? previousRoute : null, false, !targetPointsChanged);
} else {
Thread job = currentRunningJob;
if(job instanceof RouteRecalculationThread) {
RouteRecalculationThread thread = (RouteRecalculationThread) job;
if(!thread.isParamsChanged()) {
thread.stopCalculation();
}
if (isFollowingMode){
voiceRouter.announceBackOnRoute();
}
}
}
double projectDist = mode != null && mode.hasFastSpeed() ? posTolerance : posTolerance / 2;
if(returnUpdatedLocation && locationProjection != null && currentLocation.distanceTo(locationProjection) < projectDist) {
return locationProjection;
} else {
return currentLocation;
}
}
private static double getOrthogonalDistance(Location loc, Location from, Location to) {
return MapUtils.getOrthogonalDistance(loc.getLatitude(),
loc.getLongitude(), from.getLatitude(), from.getLongitude(),
to.getLatitude(), to.getLongitude());
}
private static LatLon getProject(Location loc, Location from, Location to) {
return MapUtils.getProjection(loc.getLatitude(),
loc.getLongitude(), from.getLatitude(), from.getLongitude(),
to.getLatitude(), to.getLongitude());
}
private static int lookAheadFindMinOrthogonalDistance(Location currentLocation, List<Location> routeNodes, int currentRoute, int iterations) {
double newDist;
double dist = Double.POSITIVE_INFINITY;
int index = currentRoute;
while (iterations > 0 && currentRoute + 1 < routeNodes.size()) {
newDist = getOrthogonalDistance(currentLocation, routeNodes.get(currentRoute), routeNodes.get(currentRoute + 1));
if (newDist < dist) {
index = currentRoute;
dist = newDist;
}
currentRoute++;
iterations--;
}
return index;
}
private boolean updateCurrentRouteStatus(Location currentLocation, float posTolerance) {
List<Location> routeNodes = route.getImmutableAllLocations();
int currentRoute = route.currentRoute;
// 1. Try to proceed to next point using orthogonal distance (finding minimum orthogonal dist)
while (currentRoute + 1 < routeNodes.size()) {
double dist = currentLocation.distanceTo(routeNodes.get(currentRoute));
if(currentRoute > 0) {
dist = getOrthogonalDistance(currentLocation, routeNodes.get(currentRoute - 1),
routeNodes.get(currentRoute));
}
boolean processed = false;
// if we are still too far try to proceed many points
// if not then look ahead only 3 in order to catch sharp turns
boolean longDistance = dist >= 250;
int newCurrentRoute = lookAheadFindMinOrthogonalDistance(currentLocation, routeNodes, currentRoute, longDistance ? 15 : 8);
double newDist = getOrthogonalDistance(currentLocation, routeNodes.get(newCurrentRoute),
routeNodes.get(newCurrentRoute + 1));
if(longDistance) {
if(newDist < dist) {
if (log.isDebugEnabled()) {
log.debug("Processed by distance : (new) " + newDist + " (old) " + dist); //$NON-NLS-1$//$NON-NLS-2$
}
processed = true;
}
} else if (newDist < dist || newDist < 10) {
// newDist < 10 (avoid distance 0 till next turn)
if (dist > posTolerance) {
processed = true;
if (log.isDebugEnabled()) {
log.debug("Processed by distance : " + newDist + " " + dist); //$NON-NLS-1$//$NON-NLS-2$
}
} else {
// case if you are getting close to the next point after turn
// but you have not yet turned (could be checked bearing)
if (currentLocation.hasBearing() || lastFixedLocation != null) {
float bearingToRoute = currentLocation.bearingTo(routeNodes.get(currentRoute));
float bearingRouteNext = routeNodes.get(newCurrentRoute).bearingTo(routeNodes.get(newCurrentRoute + 1));
float bearingMotion = currentLocation.hasBearing() ? currentLocation.getBearing() : lastFixedLocation
.bearingTo(currentLocation);
double diff = Math.abs(MapUtils.degreesDiff(bearingMotion, bearingToRoute));
double diffToNext = Math.abs(MapUtils.degreesDiff(bearingMotion, bearingRouteNext));
if (diff > diffToNext) {
if (log.isDebugEnabled()) {
log.debug("Processed point bearing deltas : " + diff + " " + diffToNext);
}
processed = true;
}
}
}
}
if (processed) {
// that node already passed
route.updateCurrentRoute(newCurrentRoute + 1);
currentRoute = newCurrentRoute + 1;
app.getNotificationHelper().refreshNotification(NotificationType.NAVIGATION);
} else {
break;
}
}
// 2. check if intermediate found
if(route.getIntermediatePointsToPass() > 0
&& route.getDistanceToNextIntermediate(lastFixedLocation) < getArrivalDistance() * 2f && !isRoutePlanningMode) {
showMessage(app.getString(R.string.arrived_at_intermediate_point));
route.passIntermediatePoint();
TargetPointsHelper targets = app.getTargetPointsHelper();
String name = "";
if(intermediatePoints != null && !intermediatePoints.isEmpty()) {
LatLon rm = intermediatePoints.remove(0);
List<TargetPoint> ll = targets.getIntermediatePointsNavigation();
int ind = -1;
for(int i = 0; i < ll.size(); i++) {
if(ll.get(i).point != null && MapUtils.getDistance(ll.get(i).point, rm) < 5) {
name = ll.get(i).getOnlyName();
ind = i;
break;
}
}
if(ind >= 0) {
targets.removeWayPoint(false, ind);
}
}
if(isFollowingMode) {
voiceRouter.arrivedIntermediatePoint(name);
}
// double check
while(intermediatePoints != null && route.getIntermediatePointsToPass() < intermediatePoints.size()) {
intermediatePoints.remove(0);
}
}
// 3. check if destination found
Location lastPoint = routeNodes.get(routeNodes.size() - 1);
if (currentRoute > routeNodes.size() - 3
&& currentLocation.distanceTo(lastPoint) < getArrivalDistance()
&& !isRoutePlanningMode) {
//showMessage(app.getString(R.string.arrived_at_destination));
TargetPointsHelper targets = app.getTargetPointsHelper();
TargetPoint tp = targets.getPointToNavigate();
String description = tp == null ? "" : tp.getOnlyName();
if(isFollowingMode) {
voiceRouter.arrivedDestinationPoint(description);
}
boolean onDestinationReached = OsmandPlugin.onDestinationReached();
onDestinationReached &= app.getAppCustomization().onDestinationReached();
if (onDestinationReached) {
clearCurrentRoute(null, null);
setRoutePlanningMode(false);
app.runInUIThread(new Runnable() {
@Override
public void run() {
settings.LAST_ROUTING_APPLICATION_MODE = settings.APPLICATION_MODE.get();
settings.APPLICATION_MODE.set(settings.DEFAULT_APPLICATION_MODE.get());
}
});
finishCurrentRoute();
// targets.clearPointToNavigate(false);
return true;
}
}
return false;
}
private float getArrivalDistance() {
return ((float)settings.getApplicationMode().getArrivalDistance()) * settings.ARRIVAL_DISTANCE_FACTOR.get();
}
private boolean identifyUTurnIsNeeded(Location currentLocation, float posTolerance) {
if (finalLocation == null || currentLocation == null || !route.isCalculated()) {
return false;
}
boolean isOffRoute = false;
if (currentLocation.hasBearing()) {
float bearingMotion = currentLocation.getBearing() ;
Location nextRoutePosition = route.getNextRouteLocation();
float bearingToRoute = currentLocation.bearingTo(nextRoutePosition);
double diff = MapUtils.degreesDiff(bearingMotion, bearingToRoute);
// 7. Check if you left the route and an unscheduled U-turn would bring you back (also Issue 863)
// This prompt is an interim advice and does only sound if a new route in forward direction could not be found in x seconds
if (Math.abs(diff) > 135f) {
float d = currentLocation.distanceTo(nextRoutePosition);
// 60m tolerance to allow for GPS inaccuracy
if (d > posTolerance) {
// require x sec continuous since first detection
if (deviateFromRouteDetected == 0) {
deviateFromRouteDetected = System.currentTimeMillis();
} else if ((System.currentTimeMillis() - deviateFromRouteDetected > 10000)) {
isOffRoute = true;
//log.info("bearingMotion is opposite to bearingRoute"); //$NON-NLS-1$
}
}
} else {
deviateFromRouteDetected = 0;
}
}
return isOffRoute;
}
/**
* Wrong movement direction is considered when between
* current location bearing (determines by 2 last fixed position or provided)
* and bearing from currentLocation to next (current) point
* the difference is more than 60 degrees
*/
public boolean checkWrongMovementDirection(Location currentLocation, Location nextRouteLocation) {
// measuring without bearing could be really error prone (with last fixed location)
// this code has an effect on route recalculation which should be detected without mistakes
if (currentLocation.hasBearing() && nextRouteLocation != null) {
float bearingMotion = currentLocation.getBearing();
float bearingToRoute = currentLocation.bearingTo(nextRouteLocation);
double diff = MapUtils.degreesDiff(bearingMotion, bearingToRoute);
if (Math.abs(diff) > 60f) {
// require delay interval since first detection, to avoid false positive
//but leave out for now, as late detection is worse than false positive (it may reset voice router then cause bogus turn and u-turn prompting)
//if (wrongMovementDetected == 0) {
// wrongMovementDetected = System.currentTimeMillis();
//} else if ((System.currentTimeMillis() - wrongMovementDetected > 500)) {
return true;
//}
} else {
//wrongMovementDetected = 0;
return false;
}
}
//wrongMovementDetected = 0;
return false;
}
private void setNewRoute(RouteCalculationResult prevRoute, final RouteCalculationResult res, Location start){
final boolean newRoute = !prevRoute.isCalculated();
if (isFollowingMode) {
if(lastFixedLocation != null) {
start = lastFixedLocation;
}
// try remove false route-recalculated prompts by checking direction to second route node
boolean wrongMovementDirection = false;
List<Location> routeNodes = res.getImmutableAllLocations();
if (routeNodes != null && !routeNodes.isEmpty()) {
int newCurrentRoute = lookAheadFindMinOrthogonalDistance(start, routeNodes, res.currentRoute, 15);
if (newCurrentRoute + 1 < routeNodes.size()) {
// This check is valid for Online/GPX services (offline routing is aware of route direction)
wrongMovementDirection = checkWrongMovementDirection(start, routeNodes.get(newCurrentRoute + 1));
// set/reset evalWaitInterval only if new route is in forward direction
if (wrongMovementDirection) {
evalWaitInterval = 3000;
} else {
evalWaitInterval = Math.max(3000, evalWaitInterval * 3 / 2);
evalWaitInterval = Math.min(evalWaitInterval, 120000);
}
}
}
// trigger voice prompt only if new route is in forward direction
// If route is in wrong direction after one more setLocation it will be recalculated
if (!wrongMovementDirection || newRoute) {
voiceRouter.newRouteIsCalculated(newRoute);
}
}
app.getWaypointHelper().setNewRoute(res);
app.runInUIThread(new Runnable() {
@Override
public void run() {
ValueHolder<Boolean> showToast = new ValueHolder<Boolean>();
showToast.value = true;
Iterator<WeakReference<IRouteInformationListener>> it = listeners.iterator();
while (it.hasNext()) {
WeakReference<IRouteInformationListener> ref = it.next();
IRouteInformationListener l = ref.get();
if (l == null) {
it.remove();
} else {
l.newRouteIsCalculated(newRoute, showToast);
}
}
if (showToast.value) {
String msg = app.getString(R.string.new_route_calculated_dist) + ": "
+ OsmAndFormatter.getFormattedDistance(res.getWholeDistance(), app);
if (OsmandPlugin.isDevelopment() && res.getRoutingTime() != 0f) {
msg += " (" + Algorithms.formatDuration((int) res.getRoutingTime(), app.accessibilityEnabled()) + ")";
}
app.showToastMessage(msg);
}
}
});
}
public int getLeftDistance(){
return route.getDistanceToFinish(lastFixedLocation);
}
public int getLeftDistanceNextIntermediate() {
return route.getDistanceToNextIntermediate(lastFixedLocation);
}
public int getLeftTime() {
return route.getLeftTime(lastFixedLocation);
}
public OsmandSettings getSettings() {
return settings;
}
public String getGeneralRouteInformation(){
int dist = getLeftDistance();
int hours = getLeftTime() / (60 * 60);
int minutes = (getLeftTime() / 60) % 60;
return app.getString(R.string.route_general_information, OsmAndFormatter.getFormattedDistance(dist, app),
hours, minutes);
}
public Location getLocationFromRouteDirection(RouteDirectionInfo i){
return route.getLocationFromRouteDirection(i);
}
public synchronized NextDirectionInfo getNextRouteDirectionInfo(NextDirectionInfo info, boolean toSpeak){
NextDirectionInfo i = route.getNextRouteDirectionInfo(info, lastProjection, toSpeak);
if(i != null) {
i.imminent = voiceRouter.calculateImminent(i.distanceTo, lastProjection);
}
return i;
}
public synchronized float getCurrentMaxSpeed() {
return route.getCurrentMaxSpeed();
}
public static String formatStreetName(String name, String ref, String destination, String towards) {
//Hardy, 2016-08-05:
//Now returns: (ref) + ((" ")+name) + ((" ")+"toward "+dest) or ""
String formattedStreetName = "";
if (ref != null && ref.length() > 0) {
formattedStreetName = ref;
}
if (name != null && name.length() > 0) {
if (formattedStreetName.length() > 0) {
formattedStreetName = formattedStreetName + " ";
}
formattedStreetName = formattedStreetName + name;
}
if (destination != null && destination.length() > 0) {
if (formattedStreetName.length() > 0) {
formattedStreetName = formattedStreetName + " ";
}
formattedStreetName = formattedStreetName + towards + " " + destination;
}
return formattedStreetName.replace(";", ", ");
}
// protected boolean isDistanceLess(float currentSpeed, double dist, double etalon, float defSpeed){
// if(dist < etalon || ((dist / currentSpeed) < (etalon / defSpeed))){
// return true;
// }
// return false;
// }
public synchronized String getCurrentName(TurnType[] next){
NextDirectionInfo n = getNextRouteDirectionInfo(new NextDirectionInfo(), true);
Location l = lastFixedLocation;
float speed = 0;
if(l != null && l.hasSpeed()) {
speed = l.getSpeed();
}
if(n.distanceTo > 0 && n.directionInfo != null && !n.directionInfo.getTurnType().isSkipToSpeak() &&
voiceRouter.isDistanceLess(speed, n.distanceTo, voiceRouter.PREPARE_DISTANCE * 0.75f, 0f)) {
String nm = n.directionInfo.getStreetName();
String rf = n.directionInfo.getRef();
String dn = n.directionInfo.getDestinationName();
if(next != null) {
next[0] = n.directionInfo.getTurnType();
}
return formatStreetName(nm, rf, dn, "»");
}
RouteSegmentResult rs = getCurrentSegmentResult();
if(rs != null) {
String nm = rs.getObject().getName(settings.MAP_PREFERRED_LOCALE.get(), settings.MAP_TRANSLITERATE_NAMES.get());
String rf = rs.getObject().getRef(settings.MAP_PREFERRED_LOCALE.get(), settings.MAP_TRANSLITERATE_NAMES.get(), rs.isForwardDirection());
String dn = rs.getObject().getDestinationName(settings.MAP_PREFERRED_LOCALE.get(),
settings.MAP_TRANSLITERATE_NAMES.get(), rs.isForwardDirection());
return formatStreetName(nm, rf, dn, "»");
}
return null;
}
public RouteSegmentResult getCurrentSegmentResult() {
return route.getCurrentSegmentResult();
}
public List<RouteSegmentResult> getUpcomingTunnel(float distToStart) {
return route.getUpcomingTunnel(distToStart);
}
public synchronized NextDirectionInfo getNextRouteDirectionInfoAfter(NextDirectionInfo previous, NextDirectionInfo to, boolean toSpeak){
NextDirectionInfo i = route.getNextRouteDirectionInfoAfter(previous, to, toSpeak);
if(i != null) {
i.imminent = voiceRouter.calculateImminent(i.distanceTo, null);
}
return i;
}
public List<RouteDirectionInfo> getRouteDirections(){
return route.getRouteDirections();
}
private class RouteRecalculationThread extends Thread {
private final RouteCalculationParams params;
private boolean paramsChanged;
private Thread prevRunningJob;
public RouteRecalculationThread(String name, RouteCalculationParams params, boolean paramsChanged) {
super(name);
this.params = params;
this.paramsChanged = paramsChanged;
if(params.calculationProgress == null) {
params.calculationProgress = new RouteCalculationProgress();
}
}
public boolean isParamsChanged() {
return paramsChanged;
}
public void stopCalculation(){
params.calculationProgress.isCancelled = true;
}
@Override
public void run() {
synchronized (RoutingHelper.this) {
currentRunningJob = this;
}
if(prevRunningJob != null) {
while(prevRunningJob.isAlive()){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (RoutingHelper.this) {
currentRunningJob = this;
}
}
lastRouteCalcError = null;
lastRouteCalcErrorShort = null;
RouteCalculationResult res = provider.calculateRouteImpl(params);
if (params.calculationProgress.isCancelled) {
synchronized (RoutingHelper.this) {
currentRunningJob = null;
}
return;
}
final boolean onlineSourceWithoutInternet = !res.isCalculated() && params.type.isOnline() && !settings.isInternetConnectionAvailable();
if (onlineSourceWithoutInternet && settings.GPX_ROUTE_CALC_OSMAND_PARTS.get()) {
if (params.previousToRecalculate != null && params.previousToRecalculate.isCalculated()) {
res = provider.recalculatePartOfflineRoute(res, params);
}
}
RouteCalculationResult prev = route;
synchronized (RoutingHelper.this) {
if (res.isCalculated()) {
route = res;
} else {
evalWaitInterval = evalWaitInterval * 3 / 2;
evalWaitInterval = Math.min(evalWaitInterval, 120000);
}
currentRunningJob = null;
}
if(res.isCalculated()){
setNewRoute(prev, res, params.start);
} else if (onlineSourceWithoutInternet) {
lastRouteCalcError = app.getString(R.string.error_calculating_route)
+ ":\n" + app.getString(R.string.internet_connection_required_for_online_route);
lastRouteCalcErrorShort = app.getString(R.string.error_calculating_route);
showMessage(lastRouteCalcError); //$NON-NLS-1$
} else {
if (res.getErrorMessage() != null) {
lastRouteCalcError = app.getString(R.string.error_calculating_route) + ":\n" + res.getErrorMessage();
lastRouteCalcErrorShort = app.getString(R.string.error_calculating_route);
showMessage(lastRouteCalcError); //$NON-NLS-1$
} else {
lastRouteCalcError = app.getString(R.string.empty_route_calculated);
lastRouteCalcErrorShort = app.getString(R.string.empty_route_calculated);
showMessage(lastRouteCalcError);
}
}
app.getNotificationHelper().refreshNotification(NAVIGATION);
lastTimeEvaluatedRoute = System.currentTimeMillis();
}
public void setWaitPrevJob(Thread prevRunningJob) {
this.prevRunningJob = prevRunningJob;
}
}
public void recalculateRouteDueToSettingsChange() {
clearCurrentRoute(finalLocation, intermediatePoints);
recalculateRouteInBackground(lastFixedLocation, finalLocation, intermediatePoints, currentGPXRoute, route, true, false);
}
private void recalculateRouteInBackground(final Location start, final LatLon end, final List<LatLon> intermediates,
final GPXRouteParamsBuilder gpxRoute, final RouteCalculationResult previousRoute, boolean paramsChanged, boolean onlyStartPointChanged){
if (start == null || end == null) {
return;
}
// do not evaluate very often
if ((currentRunningJob == null && System.currentTimeMillis() - lastTimeEvaluatedRoute > evalWaitInterval)
|| paramsChanged || !onlyStartPointChanged) {
if(System.currentTimeMillis() - lastTimeEvaluatedRoute < RECALCULATE_THRESHOLD_CAUSING_FULL_RECALCULATE_INTERVAL) {
recalculateCountInInterval ++;
}
RouteCalculationParams params = new RouteCalculationParams();
params.start = start;
params.end = end;
params.intermediates = intermediates;
params.gpxRoute = gpxRoute == null ? null : gpxRoute.build(start, settings);
params.onlyStartPointChanged = onlyStartPointChanged;
if(recalculateCountInInterval < RECALCULATE_THRESHOLD_COUNT_CAUSING_FULL_RECALCULATE) {
params.previousToRecalculate = previousRoute;
} else {
recalculateCountInInterval = 0;
}
params.leftSide = settings.DRIVING_REGION.get().leftHandDriving;
params.fast = settings.FAST_ROUTE_MODE.getModeValue(mode);
params.type = settings.ROUTER_SERVICE.getModeValue(mode);
params.mode = mode;
params.ctx = app;
if (params.type == RouteService.OSMAND) {
params.calculationProgress = new RouteCalculationProgress();
updateProgress(params);
}
synchronized (this) {
final Thread prevRunningJob = currentRunningJob;
RouteRecalculationThread newThread = new RouteRecalculationThread(
"Calculating route", params, paramsChanged); //$NON-NLS-1$
currentRunningJob = newThread;
if (prevRunningJob != null) {
newThread.setWaitPrevJob(prevRunningJob);
}
currentRunningJob.start();
}
}
}
private void updateProgress(final RouteCalculationParams params) {
if(progressRoute != null ) {
app.runInUIThread(new Runnable() {
@Override
public void run() {
RouteCalculationProgress calculationProgress = params.calculationProgress;
if (isRouteBeingCalculated()) {
float p = Math.max(calculationProgress.distanceFromBegin, calculationProgress.distanceFromEnd);
float all = calculationProgress.totalEstimatedDistance * 1.25f;
if (all > 0) {
int t = (int) Math.min(p * p / (all * all) * 100f, 99);
progressRoute.updateProgress(t);
}
Thread t = currentRunningJob;
if(t instanceof RouteRecalculationThread && ((RouteRecalculationThread) t).params != params) {
// different calculation started
return;
} else {
if (calculationProgress.requestPrivateAccessRouting) {
progressRoute.requestPrivateAccessRouting();
}
updateProgress(params);
}
} else {
if (calculationProgress.requestPrivateAccessRouting) {
progressRoute.requestPrivateAccessRouting();
}
progressRoute.finish();
}
}
}, 300);
}
}
public void setProgressBar(RouteCalculationProgressCallback progressRoute) {
this.progressRoute = progressRoute;
}
public interface RouteCalculationProgressCallback {
// set visibility
public void updateProgress(int progress);
public void requestPrivateAccessRouting();
public void finish();
}
public boolean isRouteBeingCalculated(){
return currentRunningJob instanceof RouteRecalculationThread;
}
private void showMessage(final String msg){
app.runInUIThread(new Runnable() {
@Override
public void run() {
app.showToastMessage(msg);
}
});
}
// NEVER returns null
public RouteCalculationResult getRoute() {
return route;
}
public GPXFile generateGPXFileWithRoute(){
return provider.createOsmandRouterGPX(route, app);
}
public void notifyIfRouteIsCalculated() {
if(route.isCalculated()) {
voiceRouter.newRouteIsCalculated(true) ;
}
}
}