com.spoiledmilk.ibikecph.navigation.routing_engine.SMRoute.java Source code

Java tutorial

Introduction

Here is the source code for com.spoiledmilk.ibikecph.navigation.routing_engine.SMRoute.java

Source

// Copyright (C) 2013 City of Copenhagen.
//
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at 
// http://mozilla.org/MPL/2.0/.
package com.spoiledmilk.ibikecph.navigation.routing_engine;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import android.location.Location;
import android.os.Handler;

import com.fasterxml.jackson.databind.JsonNode;
import com.spoiledmilk.ibikecph.IbikeApplication;
import com.spoiledmilk.ibikecph.map.SMHttpRequest;
import com.spoiledmilk.ibikecph.map.SMHttpRequest.RouteInfo;
import com.spoiledmilk.ibikecph.map.SMHttpRequestListener;
import com.spoiledmilk.ibikecph.navigation.routing_engine.SMTurnInstruction.TurnDirection;
import com.spoiledmilk.ibikecph.util.LOG;
import com.spoiledmilk.ibikecph.util.Util;

public class SMRoute implements SMHttpRequestListener {

    public static final int MAX_DISTANCE_FROM_PATH = 20;
    public static final int MIN_DISTANCE_FOR_RECALCULATION = 20;
    public static final int TO_START_STATION = 0;
    public static final int TO_END_STATION = 1;
    public static final int TO_DESTINATION = 2;
    public static final double DISTANCE_TO_REMOVE_TURN = 20d;

    public int routePhase = TO_START_STATION;
    public boolean approachingTurn;
    public double distanceFromStart;
    SMRouteListener listener;
    public List<Location> waypoints;
    public List<SMTurnInstruction> allTurnInstructions;
    public List<SMTurnInstruction> pastTurnInstructions; // turn instructions
                                                         // from
                                                         // first to the last passed
                                                         // turn
    public ArrayList<SMTurnInstruction> turnInstructions; // turn instruction
                                                          // from next
                                                          // to the last
    public List<Location> visitedLocations;
    float distanceLeft;
    float tripDistance;
    float averageSpeed;
    float caloriesBurned;
    public Location locationStart;
    public Location locationEnd;
    public boolean recalculationInProgress;
    float estimatedArrivalTime, arrivalTime;
    int estimatedRouteDistance = -1;
    String routeChecksum;
    String destinationHint;
    public Location lastCorrectedLocation;
    public double lastCorrectedHeading;
    public int lastVisitedWaypointIndex;
    public float distanceFromRoute;
    private String viaStreets;
    private Location lastRecalcLocation;
    public Location startStation, endStation;
    public String startStationName, endStationName;
    public int stationIcon;
    public boolean isRouteBroken = false;
    public SMTurnInstruction station1, station2;
    public JsonNode jsonRoot, jsonRoot2;
    public int startStatIndex = 0, endStatIndex = 0;
    public boolean reachedDestination = false;
    public int waypointStation1 = -1, waypointStation2 = -1;

    public SMRoute() {
        init();
    }

    public void init() {
        distanceLeft = -1;
        tripDistance = -1;
        caloriesBurned = -1;
        averageSpeed = -1;
        approachingTurn = false;
        lastVisitedWaypointIndex = -1;
        recalculationInProgress = false;
        lastRecalcLocation = Util.locationFromCoordinates(0, 0);
        allTurnInstructions = new ArrayList<SMTurnInstruction>();
        reachedDestination = false;
        waypointStation1 = -1;
        waypointStation2 = -1;
    }

    public void init(Location start, Location end, SMRouteListener listener, JsonNode routeJSON) {
        init();

        locationStart = start;
        locationEnd = end;
        setListener(listener);
        if (routeJSON == null) {
            LOG.d("SMRoute init() jsonRoot is null, trying to get route from here...");
            new SMHttpRequest().getRoute(start, end, null, this);
        } else {
            setupRoute(routeJSON);
        }
    }

    public void init(Location start, Location end, Location startStation, Location endStation,
            SMRouteListener listener, JsonNode routeJSON) {
        init();
        locationStart = start;
        locationEnd = end;
        setListener(listener);
        setupBrokenRoute(routeJSON);
    }

    public void setListener(SMRouteListener listener) {
        this.listener = listener;
    }

    // SMRequestOSRMListener callback

    @Override
    public void onResponseReceived(int requestType, Object response) {
        switch (requestType) {
        case SMHttpRequest.REQUEST_GET_ROUTE:
            JsonNode jsonRoot = ((RouteInfo) response).jsonRoot;
            if (jsonRoot == null || jsonRoot.path("status").asInt(-1) != 0) {
                if (listener != null)
                    listener.routeNotFound();
            } else {
                setupRoute(jsonRoot);
                if (listener != null)
                    listener.startRoute();
            }
            break;
        case SMHttpRequest.REQUEST_GET_RECALCULATED_ROUTE:
            final JsonNode jRoot = ((RouteInfo) response).jsonRoot;

            if (jRoot == null || jRoot.path("status").asInt() != 0) {
                if (listener != null) {
                    listener.serverError();
                }
                recalculationInProgress = false;
                return;
            }

            final Handler h = new Handler();
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {

                    boolean ok = parseFromJson(jRoot, null, isRouteBroken);

                    //                        logWaypoints();

                    if (ok) {
                        approachingTurn = false;
                        h.post(new Runnable() {
                            @Override
                            public void run() {
                                if (lastLocation != null) {
                                    visitLocation(lastLocation);
                                }
                                if (visitedLocations != null && visitedLocations.size() > 0) {
                                    // visitLocation(visitedLocations.get(visitedLocations.size() - 1));
                                }
                                if (SMLocationManager.getInstance().hasValidLocation()) {
                                    updateDistances(SMLocationManager.getInstance().getLastValidLocation());
                                }
                                if (listener != null) {
                                    listener.routeRecalculationDone();
                                    listener.updateRoute();
                                }
                                recalculationInProgress = false;
                            }
                        });
                    } else {
                        h.post(new Runnable() {
                            @Override
                            public void run() {
                                if (listener != null)
                                    listener.serverError();
                                recalculationInProgress = false;
                            }
                        });
                    }

                }
            });
            t.setPriority(Thread.MIN_PRIORITY);
            t.start();
        }
    }

    public Location getStartLocation() {
        if (waypoints != null && waypoints.size() > 0)
            return waypoints.get(0);
        return null;
    }

    public Location getEndLocation() {
        if (waypoints != null && waypoints.size() > 0)
            return waypoints.get(waypoints.size() - 1);
        return null;
    }

    public List<Location> getWaypoints() {
        return waypoints;
    }

    public ArrayList<SMTurnInstruction> getTurnInstructions() {
        return turnInstructions;
    }

    private void setupRoute(JsonNode jsonRoot) {
        LOG.d("SMRoute setupRoute()");

        boolean ok = parseFromJson(jsonRoot, null, false);
        if (ok) {
            approachingTurn = false;
            tripDistance = 0.0f;

            if (SMLocationManager.getInstance().hasValidLocation()) {
                updateDistances(SMLocationManager.getInstance().getLastValidLocation());
            }
        }
    }

    private void setupBrokenRoute(JsonNode jsonRoot) {
        boolean ok = parseFromJson(jsonRoot, null, true);
        if (ok) {
            approachingTurn = false;
            tripDistance = 0.0f;
            if (SMLocationManager.getInstance().hasValidLocation()) {
                updateDistances(SMLocationManager.getInstance().getLastValidLocation());
            }
        }
    }

    boolean parseFromJson(JsonNode jsonRoot, SMRouteListener listener, boolean isBrokenRoute) {
        LOG.d("SMRoute parseFromJson() json = " + jsonRoot);
        synchronized (this) {
            if (jsonRoot == null) {
                return false;
            }
            waypoints = decodePolyline(jsonRoot.path("route_geometry").textValue());

            if (waypoints.size() < 2) {
                return false;
            }

            turnInstructions = new ArrayList<SMTurnInstruction>();
            pastTurnInstructions = new LinkedList<SMTurnInstruction>();
            visitedLocations = new ArrayList<Location>();
            estimatedArrivalTime = jsonRoot.path("route_summary").path("total_time").asInt();
            arrivalTime = estimatedArrivalTime;
            distancePassed = 0d;
            if (estimatedRouteDistance < 0)
                estimatedRouteDistance = jsonRoot.path("route_summary").path("total_distance").asInt();

            routeChecksum = null;
            destinationHint = null;

            if (!jsonRoot.path("hint_data").path("checksum").isMissingNode()) {
                routeChecksum = jsonRoot.path("hint_data").path("checksum").asText();
            }

            JsonNode hint_locations = jsonRoot.path("hint_data").path("locations");
            if (hint_locations != null && !hint_locations.isMissingNode() && hint_locations.size() > 0) {
                destinationHint = jsonRoot.path("hint_data").path("locations").get(hint_locations.size() - 1)
                        .asText();
            }

            JsonNode routeInstructionsArr = jsonRoot.path("route_instructions");
            if (routeInstructionsArr != null && routeInstructionsArr.size() > 0) {
                int prevlengthInMeters = 0;
                String prevlengthWithUnit = "";
                boolean isFirst = true;
                for (JsonNode instructionNode : routeInstructionsArr) {
                    SMTurnInstruction instruction = new SMTurnInstruction();

                    String[] arr = instructionNode.get(0).asText().split("-");
                    if (arr.length < 1)
                        continue;
                    int pos = Integer.valueOf(arr[0]);
                    if (pos <= 17) {
                        instruction.drivingDirection = TurnDirection.values()[pos];
                        if (arr.length > 1 && arr[1] != null) {
                            instruction.ordinalDirection = arr[1];
                        } else {
                            instruction.ordinalDirection = "";
                        }

                        instruction.wayName = instructionNode.get(1).asText();
                        if (instruction.wayName.matches("\\{.+\\:.+\\}"))
                            instruction.wayName = IbikeApplication.getString(instruction.wayName);
                        instruction.wayName = instruction.wayName.replaceAll("&#39;", "'");
                        instruction.lengthInMeters = prevlengthInMeters;
                        prevlengthInMeters = instructionNode.get(2).asInt();
                        instruction.timeInSeconds = instructionNode.get(4).asInt();
                        instruction.lengthWithUnit = prevlengthWithUnit;
                        if (instructionNode.size() > 8) {
                            instruction.vehicle = instructionNode.get(8).asInt();
                        }
                        /**
                         * Save length to next turn with units so we don't have to generate it each time It's formatted just the way we like it
                         */
                        instruction.fixedLengthWithUnit = Util.formatDistance(prevlengthInMeters);
                        prevlengthWithUnit = instructionNode.get(5).asText();
                        instruction.directionAbrevation = instructionNode.get(6).asText();
                        instruction.azimuth = (float) instructionNode.get(7).asDouble();

                        if (isFirst) {
                            instruction.generateStartDescriptionString();
                            isFirst = false;
                        } else {
                            instruction.generateDescriptionString();
                        }
                        instruction.generateFullDescriptionString();
                        int position = instructionNode.get(3).asInt();
                        instruction.waypointsIndex = position;
                        if (waypoints != null && position >= 0 && position < waypoints.size())
                            instruction.loc = waypoints.get(position);
                        turnInstructions.add(instruction);
                    }
                }
            }
            if (isRouteBroken && turnInstructions != null) {

                double dStat1 = Double.MAX_VALUE, dStat2 = Double.MAX_VALUE;
                for (int i = 0; i < waypoints.size(); i++) {
                    if (startStation.distanceTo(waypoints.get(i)) < dStat1) {
                        dStat1 = startStation.distanceTo(waypoints.get(i));
                        waypointStation1 = i;
                    }
                    if (endStation.distanceTo(waypoints.get(i)) < dStat2) {
                        dStat2 = endStation.distanceTo(waypoints.get(i));
                        waypointStation2 = i;
                    }
                }

                Iterator<SMTurnInstruction> it2 = turnInstructions.iterator();
                float distToStart = Float.MAX_VALUE, distToEnd = Float.MAX_VALUE;
                while (it2.hasNext()) {
                    SMTurnInstruction smt = it2.next();
                    if (smt.loc.distanceTo(startStation) < distToStart && smt.waypointsIndex <= waypointStation1) {
                        distToStart = smt.loc.distanceTo(startStation);
                        station1 = smt;
                    }
                    if (smt.loc.distanceTo(endStation) < distToEnd && smt.waypointsIndex <= waypointStation2) {
                        distToEnd = smt.loc.distanceTo(endStation);
                        station2 = smt;
                    }
                }

                station1.convertToStation(startStationName, stationIcon);
                station2.convertToStation(endStationName, stationIcon);
                int startIndex = turnInstructions.indexOf(station1);
                int endIndex = turnInstructions.indexOf(station2);
                while (startIndex < endIndex - 1) {
                    turnInstructions.remove(startIndex + 1);
                    startIndex = turnInstructions.indexOf(station1);
                    endIndex = turnInstructions.indexOf(station2);
                }
            }

            int longestStreet = 0;
            viaStreets = "";

            int n = jsonRoot.path("route_name").size();
            if (n > 0) {
                int i = 0;
                for (JsonNode streetNode : jsonRoot.path("route_name")) {
                    i++;
                    viaStreets += streetNode.asText() + (i == n ? "" : ", ");
                }
            }
            if (viaStreets == null || viaStreets.trim().equals("")) {
                for (int i = 1; i < turnInstructions.size() - 1; i++) {
                    SMTurnInstruction inst = turnInstructions.get(i);
                    if (inst.lengthInMeters > longestStreet) {
                        longestStreet = inst.lengthInMeters;
                        viaStreets = turnInstructions.get(i - 1).wayName;
                    }
                }
            }

            lastVisitedWaypointIndex = 0;
        }
        return true;
    }

    /*
     * Decoder for the Encoded Polyline Algorithm Format https://developers.google .com/maps/documentation/utilities/polylinealgorithm
     */
    List<Location> decodePolyline(String encodedString) {
        if (encodedString == null)
            return null;

        byte[] bytes;
        try {
            bytes = encodedString.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            LOG.e("decodePolyline() UnsupportedEncodingException", e);
            return null;
        }

        int len = encodedString.length();

        int lat = 0, lng = 0;

        List<Location> locations = new ArrayList<Location>();
        for (int i = 0; i < len;) {
            for (int k = 0; k < 2; k++) {

                int delta = 0;
                int shift = 0;

                byte c;
                do {
                    c = (byte) (bytes[i++] - 63);
                    delta |= (c & 0x1f) << shift;
                    shift += 5;
                } while ((c & 0x20) != 0);

                delta = ((delta & 0x1) != 0) ? ((~delta >> 1) | 0x80000000) : (delta >> 1);
                if (k == 0)
                    lat += delta;
                else
                    lng += delta;
            }
            // LOG.d("decodePolyline(): (" + lat + ", " + lng + ")");
            Location loc = Util.locationFromCoordinates((double) lat / 1e5, (double) lng / 1e5);
            locations.add(loc);
        }

        return locations;
    }

    public float getEstimatedArrivalTime() {
        return arrivalTime;
    }

    public int getEstimatedDistance() {
        return estimatedRouteDistance;
    }

    ArrayList<Double> speedData = new ArrayList<Double>();
    final int SPEEDS_COUNT = 20;
    int speedIndex = 0;
    double distancePassed = 0;
    Location lastLocation;

    // Turn by Turn
    public void visitLocation(Location loc) {

        if (lastLocation != null && loc != null) {
            distancePassed += loc.distanceTo(lastLocation);
        }

        lastLocation = loc;

        visitedLocations.add(loc);

        if (turnInstructions.size() <= 0)
            return;

        if (recalculationInProgress) {
            return;
        }

        // Check if we are finishing:
        double distanceToFinish = loc.distanceTo(getEndLocation());

        arrivalTime = distanceLeft * estimatedArrivalTime / estimatedRouteDistance;

        // Calculate the average speed and update the ETA
        double speed = loc.getSpeed() > 0 ? loc.getSpeed() : 5;

        int timeToFinish = 100;
        if (speed > 0) {
            timeToFinish = (int) (distanceToFinish / speed);
        }

        // are we close to the finish (< 10m or 3s left)?
        if (distanceToFinish < 10.0 || timeToFinish <= 3) {
            LOG.d("finishing in " + distanceToFinish + " m and " + timeToFinish + " s");
            if (turnInstructions.size() == 1) {
                // if there was only one instruction left go through usual
                // channels
                approachingTurn = false;
                // removeTurn();
                if (listener != null) {
                    reachedDestination = true;
                    listener.reachedDestination();
                }
                return;
            } else {
                // we have somehow skipped most of the route (going through a
                // park or unknown street)
                if (listener != null) {
                    reachedDestination = true;
                    listener.reachedDestination();
                }
                return;
            }
        }

        // Check if we went too far from the calculated route and, if so,
        // recalculate route
        // max allowed distance depends on location's accuracy
        int maxD = loc.getAccuracy() > 0.0 ? (int) (loc.getAccuracy() / 3 + 20) : MAX_DISTANCE_FROM_PATH;
        if (!approachingFinish() && listener != null && isTooFarFromRoute(loc, maxD)) {
            approachingTurn = false;
            recalculateRoute(loc, false);
            return;
        }

        int closestWaypointIndex = -1;
        double minD = Double.MAX_VALUE;
        Location projectedLoc = null;
        if (turnInstructions != null && turnInstructions.size() > 0) {
            // find the projected location on the route segment
            // check only a couple of the first instructions
            // for (int i = 0; i < turnInstructions.size() - 1 && i < 3; i++) {
            // double d = SMGPSUtil.distanceFromLineInMeters(loc, turnInstructions.get(i).loc, turnInstructions.get(i +
            // 1).loc);
            // if (d < minD && d < 20) {
            // projectedLoc = SMGPSUtil.closestCoordinate(loc, turnInstructions.get(i).loc, turnInstructions.get(i +
            // 1).loc);
            // }
            // }
            // minD = Double.MAX_VALUE;

            // if (lastCorrectedLocation != null && lastCorrectedLocation.distanceTo(loc) < 30) {
            // projectedLoc = lastCorrectedLocation;
            // }

            projectedLoc = loc;

            if (projectedLoc != null) {
                if (waypoints != null && waypoints.size() > 0) {
                    // find the closest waypoint
                    for (int i = lastVisitedWaypointIndex; i < waypoints.size(); i++) {
                        Location waypoint = waypoints.get(i);
                        if (projectedLoc.distanceTo(waypoint) < minD) {
                            minD = projectedLoc.distanceTo(waypoint);
                            closestWaypointIndex = i;
                        }
                    }
                }
            }
        }

        try {
            LOG.d("routing debug closestWaypointIndex = " + closestWaypointIndex);
            LOG.d("routing debug loc = " + Util.locationString(loc));
            LOG.d("routing debug instructions[0] = " + turnInstructions.get(0).fullDescriptionString + " loc = "
                    + Util.locationString(turnInstructions.get(0).loc) + " waypoint index = "
                    + turnInstructions.get(0).waypointsIndex);
        } catch (Exception e) {

        }

        if (closestWaypointIndex > -1 && !recalculationInProgress) {
            synchronized (SMRoute.this) {

                Iterator<SMTurnInstruction> it = turnInstructions.iterator();
                // int i = 0;
                while (it.hasNext()) {
                    SMTurnInstruction instruction = it.next();
                    double d = loc.distanceTo(instruction.loc);
                    if (closestWaypointIndex < instruction.waypointsIndex) {
                        // future instruction, stop the loop
                        instruction.lastD = loc.distanceTo(instruction.loc);
                        break;
                    } else if (closestWaypointIndex > instruction.waypointsIndex) {
                        // we have definetly passed the instruction
                        it.remove();
                        pastTurnInstructions.add(instruction);
                        allTurnInstructions.add(instruction);
                    } else if (d < 10d && (!instruction.plannedForRemoving || d > instruction.lastD)) {
                        // we are approaching the instruction
                        LOG.d("routing debug instruction planned for removing = "
                                + instruction.fullDescriptionString + " d = " + loc.distanceTo(instruction.loc));
                        instruction.plannedForRemoving = true;
                    } else {
                        if (d >= 10d && (instruction.plannedForRemoving || d > instruction.lastD)) {
                            // remove the instruction
                            LOG.d("routing debug removing the instruction " + instruction.fullDescriptionString);
                            it.remove();
                            pastTurnInstructions.add(instruction);
                            allTurnInstructions.add(instruction);
                        }
                    }
                    instruction.lastD = loc.distanceTo(instruction.loc);

                }
            }

        }

        updateDistances(loc);

    }

    public void recalculateRoute(Location loc, boolean isBicycleTypeChanged) {
        if (recalculationInProgress) {
            return;
        }
        float distance = loc.distanceTo(lastRecalcLocation);
        if (distance < MIN_DISTANCE_FOR_RECALCULATION && !isBicycleTypeChanged) {
            return;
        }
        LOG.d("Recalculating route, distance: " + distance);
        lastRecalcLocation = loc;
        recalculationInProgress = true;
        if (listener != null) {
            listener.routeRecalculationStarted();
        }
        Location end = getEndLocation();
        if (loc == null || end == null)
            return;

        // Uncomment code below if previous part of the route needs to be
        // displayed.
        // NSMutableArray *viaPoints = [NSMutableArray array];
        // for (SMTurnInstruction *turn in self.pastTurnInstructions)
        // [viaPoints addObject:turn.loc];
        // [viaPoints addObject:loc];
        // [r getRouteFrom:((CLLocation *)[self.waypoints
        // objectAtIndex:0]).coordinate to:end.coordinate via:viaPoints];

        if (!isRouteBroken
                || (isRouteBroken && (((pastTurnInstructions != null && pastTurnInstructions.contains(station1))
                        || loc.distanceTo(endStation) < loc.distanceTo(startStation))))) {
            routePhase = TO_DESTINATION;
            isRouteBroken = false;
            new SMHttpRequest().getRecalculatedRoute(loc, end, null, routeChecksum, null, destinationHint, this);
        } else if (isRouteBroken) {
            List<Location> viaList = new LinkedList<Location>();
            viaList.add(startStation);
            viaList.add(endStation);
            new SMHttpRequest().getRecalculatedRoute(loc, end, viaList, null, null, null, this);
        }
    }

    public boolean getRouteForNewBicycleType(Location loc) {
        if (recalculationInProgress) {
            return false;
        }
        lastRecalcLocation = loc;
        recalculationInProgress = true;
        Location end = getEndLocation();
        if (loc == null || end == null) {
            return false;
        }
        new SMHttpRequest().getRecalculatedRoute(loc, end, null, null, null, null, this);
        return true;
    }

    boolean checkLocation(Location loc, float maxDistance) {
        SMTurnInstruction currentTurn = turnInstructions.get(0);
        SMTurnInstruction nextTurn = turnInstructions.get(Math.min(turnInstructions.size() - 1, 2));
        if (nextTurn != null) {
            if (!isTooFarFromRouteSegment(loc, null, nextTurn, maxDistance)) {
                if (lastVisitedWaypointIndex > currentTurn.waypointsIndex) {
                    // removeTurn();
                    approachingTurn = true;
                }

                return false;
            }
        }
        return true;
    }

    public boolean isTooFarFromRoute(Location loc, int maxDistance) {
        if (turnInstructions.size() > 0) {
            SMTurnInstruction currentTurn = turnInstructions.get(0);
            lastCorrectedLocation = new Location(loc);

            if (pastTurnInstructions.size() < 0) {
                // lastCorrectedHeading = SMGPSUtil.bearingBetween(loc,
                // currentTurn.loc);
                // We have passed no turns. Check if we have managed to get on
                // the route somehow.
                if (currentTurn != null) {
                    double currentDistanceFromStart = loc.distanceTo(currentTurn.loc);
                    LOG.d("Current distance from start: " + currentDistanceFromStart);
                    if (currentDistanceFromStart > maxDistance) {
                        return checkLocation(loc, maxDistance);
                    }
                }
                return false;
            }

            distanceFromRoute = Float.MAX_VALUE;
            return checkLocation(loc, maxDistance);
        }
        return false;
    }

    private boolean isTooFarFromRouteSegment(Location loc, SMTurnInstruction turnA, SMTurnInstruction turnB,
            double maxDistance) {
        double min = Float.MAX_VALUE;

        for (int i = lastVisitedWaypointIndex; i < turnB.waypointsIndex; i++) {
            try {
                Location a = waypoints.get(i);
                Location b = waypoints.get(i + 1);
                double d = SMGPSUtil.distanceFromLineInMeters(loc, a, b);
                if (d < 0.0)
                    continue;
                if (d <= min) {
                    min = d;
                    lastVisitedWaypointIndex = i;
                }
                if (min < 2) {
                    // Close enough :)
                    break;
                }
            } catch (Exception e) {
                continue;
            }
        }

        if (min <= maxDistance && min < distanceFromRoute) {
            distanceFromRoute = (float) min;

            Location a = waypoints.get(lastVisitedWaypointIndex);
            Location b = waypoints.get(lastVisitedWaypointIndex + 1);
            Location coord = SMGPSUtil.closestCoordinate(loc, a, b);
            if (a.distanceTo(b) > 0.0f) {
                lastCorrectedHeading = SMGPSUtil.bearingBetween(a, b);
            }

            if (visitedLocations != null && visitedLocations.size() > 0) {
                lastCorrectedLocation = new Location(loc);
                lastCorrectedLocation.setLatitude(coord.getLatitude());
                lastCorrectedLocation.setLongitude(coord.getLongitude());
            }
        }

        return min > maxDistance;
    }

    private boolean approachingFinish() {
        boolean isNear = false;
        if (locationEnd != null && lastLocation != null) {
            isNear = locationEnd.distanceTo(lastLocation) <= 20;
        }
        return isNear && turnInstructions.size() == 1;
    }

    // private void removeTurn() {
    // if (listener == null) {
    // return;
    // }
    //
    // if (turnInstructions.size() > 0) {
    // pastTurnInstructions.add(turnInstructions.get(0));
    // allTurnInstructions.add(turnInstructions.get(0));
    // turnInstructions.remove(0);
    // listener.updateTurn(true);
    // if (isRouteBroken && station1 != null && station2 != null) {
    // if (pastTurnInstructions.contains(station2)) {
    // isRouteBroken = false;
    // routePhase = TO_DESTINATION;
    // } else if (pastTurnInstructions.contains(station1))
    // routePhase = TO_END_STATION;
    // }
    // if (turnInstructions.size() == 0) {
    // reachedDestination = true;
    // listener.reachedDestination();
    // }
    // }
    // }

    private void updateDistances(Location loc) {
        if (tripDistance < 0.0) {
            tripDistance = 0.0f;
        }
        if (visitedLocations.size() > 0) {
            tripDistance += loc.distanceTo(visitedLocations.get(visitedLocations.size() - 1));
        }

        if (distanceLeft < 0.0) {
            distanceLeft = estimatedRouteDistance;
        } else if (turnInstructions.size() > 0) {
            // calculate distance from location to the next turn
            SMTurnInstruction nextTurn = turnInstructions.get(0);
            nextTurn.lengthInMeters = (int) calculateDistanceToNextTurn(loc);
            if (nextTurn.plannedForRemoving && nextTurn.lengthInMeters < 10) {
                nextTurn.lengthInMeters = 0;
            }
            // LOG.d("turn " + nextTurn.descriptionString + " distance = " +
            // nextTurn.lengthInMeters);
            nextTurn.lengthWithUnit = Util.formatDistance(nextTurn.lengthInMeters);
            // turnInstructions.set(0, nextTurn);
            distanceLeft = nextTurn.lengthInMeters;

            // calculate distance from next turn to the end of the route
            for (int i = 1; i < turnInstructions.size(); i++) {
                distanceLeft += turnInstructions.get(i).lengthInMeters;
            }
        }
        if (listener != null) {
            listener.updateRoute();
        }
    }

    private float calculateDistanceToNextTurn(Location loc) {
        if (turnInstructions.size() == 0)
            return 0.0f;

        SMTurnInstruction nextTurn = turnInstructions.get(0);

        // If first turn still hasn't been reached, return linear distance to
        // it.
        if (pastTurnInstructions.size() == 0)
            return loc.distanceTo(nextTurn.loc);

        int firstIndex = lastVisitedWaypointIndex >= 0 ? lastVisitedWaypointIndex + 1 : 0;
        float distance = 0.0f;
        if (firstIndex < waypoints.size()) {
            distance = loc.distanceTo(waypoints.get(firstIndex));
            if (nextTurn.waypointsIndex <= waypoints.size()) {
                for (int i = firstIndex; i < nextTurn.waypointsIndex; i++) {
                    double d = waypoints.get(i).distanceTo(waypoints.get(i + 1));
                    distance += d;
                }
            }
        }

        return distance;
    }

    public String getViaStreets() {
        return viaStreets;
    }

    // public void logWaypoints() {
    // Iterator<Location> it = waypoints.iterator();
    // while (it.hasNext()) {
    // Location loc = it.next();
    // LOG.d("waypoint = " + loc.getLatitude() + " , " + loc.getLongitude() + "\n");
    // }
    // LOG.d("///////////////////////////////////////////");
    // }

}