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>