Saturday, February 25, 2012

Google Maps API part 3 Elevation Profiles

As a big cyclist I live and die by elevation profiles. In part 2 of my Google Maps API blog I showed how to get recommended cycling routes from Google Maps. In this blog I will show how to get the elevation profile for that route and how to display it. We need to display the profile in a new DIV element. To make room for the element we will reduce the height of the map DIV to 80% and display the profile DIV below it. You need to have completed parts 1 and 2 of this tutorial. Find the map_canvas DIV and change the height to 80%. Then add the following HTML below. Do not use the "/>" shortcut because of a bug in IE.
<div id="elevation_chart" style="height:20%; width:100%"></div>

We will get the elevation profile after we have displayed the route. As you might expect by now this will be an asynchronous call. We start by declaring an elevationService object and a chart object to display the elevation profile in. Because the chart object will be bound to the elevation_profile DIV it can't be initialized at this point. Add the following declarations before the initialize function.
var elevationService = new google.maps.ElevationService();
var chart;

Now we need to make the call to the elevationService. We also need to store the total distance of the route. Create a global variable to store the distance by adding the following line above the initialize function.
var totalDistance = 0;

Add the following lines of code after the line "directionsRenderer.setMap(map);" in displaydirections. It tells the elevationService to calculate the elevation at 100 evenly spaced points along the path returned by the directionsService and pass the results to a function called plotElevation. It also stores the route distance to totalDistance.
var elevationOptions = {'path': result.routes[0].overview_path, 'samples': 100};
elevationService.getElevationAlongPath(elevationOptions, plotElevation);
totalDistance = result.routes[0].legs[0].distance.value;

The last thing to do is to write the plotElevation function. We will start off simple and then add more functionality later. We need to populate a datatable, pass it to the chart control, and then render the chart control. To do visualization we need to reference a new library and load a new package. Add the new library by adding the following script tag after the <head> tag.
<script type="text/javascript" src="http://www.google.com/jsapi"></script>

And then load the visualization package by adding the following line before the initialize function.
google.load("visualization", "1", { packages: ["linechart"] });

Now we can write our new function to render the results returned by the elevationService call.
function plotElevation(results, status)
{ if (status == google.maps.ElevationStatus.OK)
  { var data = new google.visualization.DataTable();
    var distance;
    data.addColumn('string', 'Distance');
    data.addColumn('number', 'Elevation');
    for (var i=0; i < results.length; i++)
    { distance = Math.round(totalDistance * i / results.length);
      data.addRow([distance.toString(), results[i].elevation]);
    }
    var chartOptions = {legend:'none',
        titleX:'Distance (m)',
        titleY:'Elevation (m)'};
    var chartDiv = document.getElementById("elevation_chart");
    chart = new google.visualization.LineChart(chartDiv);
    chart.draw(data, chartOptions);
  }
  else
    alert("Plotting elevation" + status);
}

Try selecting a route now. About 10 seconds after the route is displayed you will see the elevation profile charted below the map. It's a bit slow.

Now the most important number for any route is the total climbing ;-) Let's add some javascript to calculate and display the total distance, total ascending, and total descending. We already know the total distance and we can calculate the others while we populate the data table. In plotElevation find the line "var distance;" and insert the following lines below it.
var totalAscending = 0;
var totalDescending = 0;

Now in the same function, after the line "data.addRow..." add the following code.
if (i > 0)
{ if (results[i-1].elevation < results[i].elevation)
    totalAscending += results[i].elevation - results[i-1].elevation;
  else
    totalDescending += results[i-1].elevation - results[i].elevation;
}

And finally we will show the user this information by inserting the following code after the line "chart.draw..."
window.status='Total Distance: ' + Math.round(totalDistance) + 'm, ' +
      'Total Ascending: ' + Math.round(totalAscending) + 'm, ' +
      'Total Descending: ' + Math.round(totalDescending) + 'm';

No comments:

Post a Comment