Google maps api v3 calculate mileage by state
Asked Answered
P

1

11

I'm searching for a way to calculate mileage by US State based on an origin, waypoints and destination of a route using Google Maps API v3.

I have tried using Googles Distance Matrix API but this it is calculating the distance between 2 points, which is good, but I need the break down for miles traveled for each State. For taxes purposes (IFTA reports for transportation).

I've done a lot of googling and looked through the documentation but I'm not seeing anything that calculate the mileage per State.

I know how to use Google maps and I know this is possible since I saw it on one video. There is no code I can show because I have no idea how to do it. Any thoughts?

Useful links I have found:

How to Draw Routes and Calculate Route Time and Distance on the Fly Using Google Map API V3 http://www.c-sharpcorner.com/UploadFile/8911c4/how-to-draw-routes-and-calculate-route-time-and-distance-on/

How to Build a Distance Finder with Google Maps API http://www.1stwebdesigner.com/distance-finder-google-maps-api/

Precise answered 1/12, 2015 at 19:48 Comment(13)
Similar question has already been asked and answered. #17661177. Hope this helps, if not edit your question and distinguish it from the above one :)Hillary
Thanks for your answer. I saw this before. Unfortunately it is not answering the question. Just assuming some things and suggest to use the text "Entering " from directions text which is not reliable at all.Precise
No, not the "Entering..." part, check the accepted answer. If you use the directions API, you can get back a route, which has multiple steps, each with a distance. Each step also has start/end locations, which you can use to figure out when the route crosses a state border. Now that you have the route, broken down by states, simply sum up the distances.Camisole
What format are is the trip information in currently? How do you want to input the data? How many requests total or how frequently do you need to run this?Harrelson
@Locke125 It is not really matters. The trip is constructed from 3 drop down lists that are selected on the same page. From id of the select I have an address to geocode (if not on the database already). Take the lat/long and draw the map. The things are more complicated because of the OL that you can sort and the map will show a new route. BUT, THIS IS NOT WHAT I WANT. I have done this already.Precise
I NEED, from a trip from New York(NY) to Dallas(TX) to know how many miles for each state. You travel through NY, NJ, PA, TN, AR, TX. It is quite often since I run it every new order. So, take a trip and calculate the state mileage. The rest is done.Precise
How do you determine the route? Does the route just go through the state and it doesn't matter what roads? Different roads will result in different distances. Are the points in the middle of the trip saved as addresses, lat/lng, etc..?Harrelson
@Locke125 It is the shortest route for now. optimizeWaypoints: true option on directionsService request. Yes it is possible to have multiple stops but this doesn't matters because I need to calculate total route distance from first point to the last point and the state mileage between these 2(first and last points on the route). All stops between doesn't matter for state mileage.Precise
I need bascally the same thing like here ifueltax.com/helpPrecise
I think @approxiblue's comment is a reasonable solution. You can get state outlines here: google.com/fusiontables/…. Intersect each segment which makes up the route with each segment which makes up a state polygon to find where there is an intersection. Of course, you should be smart about it so the performance is good (for example, do bounding box checks before calculating intersections).Sukkoth
Source1 or Source2Bowshot
You can use Truck Road truckroad.us. It's free and it calculate miles on each state separate.Telescopium
@Telescopium truckroad.us doesn't work anymoreColdshoulder
M
5

Below is a fully functional implementation that uses the Google Maps Javascript API. All you need to add is your own Maps API Key. As noted in the posts referenced above, Google Maps throttles requests at an asymptotic rate, and thus, the longer the route, the longer it will take to calculate. To give a ballpark, a route from New Haven CT to the NJ/PA border takes approximately 5 minutes. A trip from New Haven CT to Los Angeles takes 45 minutes to index. One other note: There are a few state borders that run through bodies of water. Google considers these to be not located in any state, and so reports undefined as the state name. These sections are obviously only a few tenths of a mile in most cases, but I felt I should mention it just to clarify what is going on when that happens.

UPDATED:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>

<script src="https://maps.googleapis.com/maps/api/js?key=<YOUR-KEY-HERE>"></script>
<div id="map" style="height:400px"></div>
<div id="status"></div>
<div id="results" style="height:400px"><b>Results:</b></div>

<script>

var directionsRequest = {
  origin: "New York, NY", //default
  destination: "Los Angeles, LA", //default
  optimizeWaypoints: true,
  provideRouteAlternatives: false,
  travelMode: google.maps.TravelMode.DRIVING,
  drivingOptions: {
    departureTime: new Date(),
    trafficModel: google.maps.TrafficModel.PESSIMISTIC
  }
};

directionsRequest.origin = prompt("Enter your starting address");
directionsRequest.destination = prompt("Enter your destination address");
var starttime = new Date();

var geocoder  = new google.maps.Geocoder();
var startState;
var currentState;
var routeData;
var index = 0;
var stateChangeSteps = [];
var borderLatLngs = {};
var startLatLng;
var endLatLng;

directionsService = new google.maps.DirectionsService();
directionsService.route(directionsRequest, init);

function init(data){
    routeData = data;
    displayRoute();
    startLatLng = data.routes[0].legs[0].start_location;
    endLatLng = data.routes[0].legs[0].end_location;
    geocoder.geocode({location:data.routes[0].legs[0].start_location}, assignInitialState)

}

function assignInitialState(data){
    startState = getState(data);
    currentState = startState;
    compileStates(routeData);
}

function getState(data){
    for (var i = 0; i < data.length; i++) {
        if (data[i].types[0] === "administrative_area_level_1") {
            var state = data[i].address_components[0].short_name;
        }
    }
    return state;
}

function compileStates(data, this_index){
    if(typeof(this_index) == "undefined"){
        index = 1;
        geocoder.geocode({location:data.routes[0].legs[0].steps[0].start_location}, compileStatesReceiver);
    }else{
        if(index >= data.routes[0].legs[0].steps.length){
            console.log(stateChangeSteps);
            index = 0;
            startBinarySearch();
            return;
        }
        setTimeout(function(){ 
                geocoder.geocode({location:data.routes[0].legs[0].steps[index].start_location}, compileStatesReceiver);
                $("#status").html("Indexing Step "+index+"...  ("+data.routes[0].legs[0].steps.length+" Steps Total)");
            }, 3000)
    }

}

function compileStatesReceiver(response){
      state = getState(response);
      console.log(state);
      if(state != currentState){
            currentState = state;
            stateChangeSteps.push(index-1);
      }
      index++; 
      compileStates(routeData, index);

    }



var stepIndex = 0;
var stepStates = [];
var binaryCurrentState = "";
var stepNextState;
var stepEndState;
var step;

var myLatLng = {lat:39.8282, lng:-98.5795};
var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 4,
    center: myLatLng
  });

function displayRoute() {
  directionsDisplay = new google.maps.DirectionsRenderer();
  directionsDisplay.setMap(map);
  directionsDisplay.setDirections(routeData);
}

var orderedLatLngs = [];
function startBinarySearch(iterating){
    if(stepIndex >= stateChangeSteps.length){
        for(step in borderLatLngs){
            for(state in borderLatLngs[step]){
                for(statename in borderLatLngs[step][state]){
                   $("#results").append("<br>Cross into "+statename+" at "+JSON.stringify(borderLatLngs[step][state][statename], null, 4));
                   orderedLatLngs.push([borderLatLngs[step][state][statename], statename]); 
                }
            }
        }
        compileMiles(true);
        return;

    }
    step = routeData.routes[0].legs[0].steps[stateChangeSteps[stepIndex]];
    console.log("Looking at step "+stateChangeSteps[stepIndex]);
    borderLatLngs[stepIndex] = {};
    if(!iterating){
        binaryCurrentState = startState;
    }
    geocoder.geocode({location:step.end_location}, 
        function(data){
            if(data === null){
                setTimeout(function(){startBinarySearch(true);}, 6000);
            }else{
                stepNextState = getState(data);
                stepEndState = stepNextState;
                binaryStage2(true);
            }
        });
}

var minIndex;
var maxIndex;
var currentIndex;
function binaryStage2(init){
    if (typeof(init) != "undefined"){   
        minIndex = 0;
        maxIndex  = step.path.length - 1;    
    }
    if((maxIndex-minIndex)<2){
        borderLatLngs[stepIndex][maxIndex]={};
        borderLatLngs[stepIndex][maxIndex][stepNextState]=step.path[maxIndex];
        var marker = new google.maps.Marker({
            position: borderLatLngs[stepIndex][maxIndex][stepNextState],
            map: map,
        });
        if(stepNextState != stepEndState){
            minIndex = maxIndex;
            maxIndex = step.path.length - 1;
            binaryCurrentState = stepNextState;
            stepNextState = stepEndState;

        }else{
            stepIndex++;
            binaryCurrentState = stepNextState;
            startBinarySearch(true);
            return;
        }
    }
    console.log("Index starts: "+minIndex+" "+maxIndex);
    console.log("current state is "+binaryCurrentState);
    console.log("next state is "+ stepNextState);
    console.log("end state is "+ stepEndState);

    currentIndex = Math.floor((minIndex+maxIndex)/2);
    setTimeout(function(){
                geocoder.geocode({location:step.path[currentIndex]}, binaryStage2Reciever);
                $("#status").html("Searching for division between "+binaryCurrentState+" and "+stepNextState+" between indexes "+minIndex+" and "+maxIndex+"...") 
            }, 3000);


}

function binaryStage2Reciever(response){
    if(response === null){
        setTimeout(binaryStage2, 6000);
    }else{
        state = getState(response)
        if(state == binaryCurrentState){
            minIndex = currentIndex +1; 
        }else{
            maxIndex = currentIndex - 1
            if(state != stepNextState){
                stepNextState = state;
            }
        }
        binaryStage2();
    }
}

var currentStartPoint;
var compileMilesIndex = 0;
var stateMiles = {};
var trueState;
function compileMiles(init){
        if(typeof(init)!= "undefined"){
            currentStartPoint = startLatLng;
            trueState = startState;    
        }
        if(compileMilesIndex == orderedLatLngs.length){
            directionsRequest.destination = endLatLng;
        }else{
            directionsRequest.destination = orderedLatLngs[compileMilesIndex][0];
        }
        directionsRequest.origin = currentStartPoint;
        currentStartPoint = directionsRequest.destination;
        directionsService.route(directionsRequest, compileMilesReciever)

}

function compileMilesReciever(data){
    if(data===null){
        setTimeout(compileMiles, 6000);
    }else{
        if(compileMilesIndex == orderedLatLngs.length){
            stateMiles[stepEndState]=data.routes[0].legs[0].distance["text"];
            $("#results").append("<br><br><b>Distances Traveled</b>");
            for(state in stateMiles){
                $("#results").append("<br>"+state+": "+stateMiles[state]);
            }
            var endtime = new Date();
            totaltime = endtime - starttime;
            $("#results").append("<br><br>Operation took "+Math.floor(totaltime/60000)+" minute(s) and "+(totaltime%60000)/1000+" second(s) to run.");
            return;
        }else{
            stateMiles[trueState]=data.routes[0].legs[0].distance["text"];
        }
        trueState = orderedLatLngs[compileMilesIndex][1];
        compileMilesIndex++;
        setTimeout(compileMiles, 3000);
    }
}




</script>

</script>
Mercier answered 16/12, 2015 at 13:32 Comment(8)
@IsaacRay is there any other way to get only miles traveled each state? Because this method get long time to generate this.Condottiere
@JoyChowdhury not that I know of if you are limited to using the google maps API.Mercier
@AdrianP. were you able to figure out this problem without it taking 45 minutes to complete? I am trying to solve a similar problem and considering setting up a map of the state polygons to determine when a path crosses a state line instead which would not require the geocodingColdshoulder
@MikeHarrison Nope. If you'll find other solution please post it. It's still of interest for me.Precise
@AdrianP. here is what I am using now: gist. If the directions text ever changes I will have to update my code but I can't imagine them changing much more than the text output which can be updated by changing the regex searching. I am working on another function to get the miles traveled per state and can post as well. Hope this helps!Coldshoulder
@MikeHarrison Yes, thanks. I'm using this tool but there are some JS and PHP backend involved. trulos.com/truckload/mileage-by-state.phpPrecise
@MikeHarrison I worry about you posting your solution here. Using a regex that depends on Google's labeling of the state boundaries with "Passing through" or "Entering" is SOO fragile, and also definitely could be incorrect in some cases. My solution guarantees an accurate output. I know its annoying that it takes a long time, but thats Google's fault. I encourage anyone who is interested in a guaranteed accurate number to use the posted answer as a solution.Mercier
@isaacray I agree with you that it is fragile. I would not recommend someone using it if they are going to use it as a method to calculate a payment calculation for example, but your code is very length and was hard to follow along with what the code was doing. It would be helpful to see some online comments but then that makes your answer even longer. And yes it is Google's fault lol but if someone wants a quick and dirty solution that does not need to be 100% dependable, then they can just use the simpler form of a regex search which also doesn't require using jQuery. I don't have jQueryColdshoulder

© 2022 - 2024 — McMap. All rights reserved.