Ticker

6/recent/ticker-posts

Google Map API Fun

Introduction

Our exercise crazy friend, Tim, bought a really crazy watch by Garmin called the Forerunner 201. It's basically a sweat-proof GPS unit for your wrist to help you keep excellent records of your workout activities. Best part: you can hook it up to your computer and download an XML file of the latitudes, longitudes, altitudes, times, calories burned, and some other goodies for each workout. A few days ago, Tim mentioned that he was disappointed with the services offered for visualizing the data from his Forerunner. And so in an effort to help out both Tim and other web developers working on Google Map API implementations, we created this tutorial based off a practical application.

See it in Action

The gMap Workout Tracker!

Our implementation loads the XML file and draws a polyline between each point on a small delay to give the animated effect and route taken. We thought it would be cool to spice it up and added some other calculations like total time, total distance, pace, markers at different colored intervals, a loading bar, and stats.

Note: This demo draws out just a small segment of one of Tim's bike rides (the one he gave us was for a 96 mile behemoth!). Because the device spits out data points every few feet, we figure it'll be more useful to developers to start with something a bit more managable.

Bugs/Concerns

  • The demo map has ~140 points being drawn. After about 200 points, the script starts to chug. If you're going to map 96 miles worth of data, it might be a good idea to grab some food. We're pretty sure it's because only a certain amount of markers/polylines can be drawn at optimal speed. Eh.

  • The distance measurement is not super accurate. The math might be off. This is why some of the markers have weird spacing issues. Because speed is calculated from distance and time, that might be off too. This was mentioned to us by Tim, who thinks they must be because he's pretty sure he rides MUCH faster than the stats indicate. Fair enough.

  • Altitude is not taken into consideration in the distance.

  • The map just doesn't work in Safari. It'll just close your browser. Working on it.

  • The half mile markers are set at the nearest point after a half a mile and not at the half a mile point itself.

How it Works

XML FILE

Let's start by looking at an excerpt of the XML data given to us by the Forerunner device. Because the Forerunner combines all 7 or so different rides/runs in one xml file, we recommend breaking up each run into it's own file.

<Run>
<Notes></Notes>
<Lap>
<StartTime>2004-12-30T22:36:58Z</StartTime>

<Duration>PT1418.230S</Duration>
<Length>5144.500</Length>
<Calories>367</Calories>
</Lap>
<Track>
<Trackpoint>
<Position>

<Latitude>28.09127</Latitude>
<Longitude>-82.42928</Longitude>
<Altitude>12.154</Altitude>
</Position>
<Time>2004-12-30T22:36:58Z</Time>
</Trackpoint>
<Trackpoint>

<Position>
<Latitude>28.09125</Latitude>
<Longitude>-82.42928</Longitude>
<Altitude>8.789</Altitude>
</Position>
<Time>2004-12-30T22:37:00Z</Time>
</Trackpoint>

</Track>
</Run>

In Forerunner, you can track individual rides/runs by the '' element. There are multiple 'laps' in the 'Run' separated by minutes rather than seconds and do not give a great idea of your route. A 'Run' also contains 'Trackpoints' taken every couple of seconds and give a much more reliable picture. Every other or third 'Trackpoint' can actually give a fairly realistic representation of the ride.

In the demo, we only used the 'Trackpoints' to map our data. You should be able to load an entire run or runs into the script with no problems.

FUNCTIONS

'onLoad()' is where all the fun begins and the map is created, displayed, and centered. The map is first created using the 'GMap()' constructor.

function onLoad() {
map = new GMap(document.getElementById("map"));

After that the map controls which allow us to pan/zoom and switch between Map and Satellite mode are added.

control = new GLargeMapControl();

map.addControl(control);
map.addControl(new GMapTypeControl());

Next, the map is centered at the proper geographical position and the zoom level set.

map.centerAndZoom(new GPoint(-82.45126, 27.94351), 4);

Once the map is in place, let's load the info to be displayed.

loadInfo();

'function loadInfo()' should look familiar to those of you with any AJAX experience. The XML file is loaded in using the 'request.open()' function and once loaded in is stored as the variable 'xmlDoc'.

var request = GXmlHttp.create();

request.open("GET", "bike1.xml", true);
request.onreadystatechange = function() {
if (request.readyState == 4) {

var xmlDoc = request.responseXML;

After the file is loaded we create an array of 'markers' consisting of the 'Trackpoints' found in the XML file.

 markers =xmlDoc.documentElement.getElementsByTagName("Trackpoint");

Once we know where all the markers are, we need to do something with them and head over to the 'plotPoint()' function.

window.setTimeout(plotPoint,timeOut);

The 'plotPoint()' function loops through the markers array and extracts the latitude and longitude from each 'Trackpoint'.

var Lat = markers[i].getElementsByTagName("Latitude");
var Lng = markers[i].getElementsByTagName("Longitude");

Lat = Lat[0].firstChild.nodeValue;
Lng = Lng[0].firstChild.nodeValue;

A 'GPoint' containing these latitude and longitude values is then created and passed to the 'createMarker()' function in order to generate the actual marker icon.

var point = new GPoint(Lng, Lat);
marker = createMarker(point,i,markers.length);

We have a point but now need to draw the line or add a 'polyline'. To do this we extract the latitude and longitude from the next marker. This point is then stored as 'point1'.

if (i < markers.length-2){
var Lat1 = markers[i+1].getElementsByTagName("Latitude");
var Lng1 = markers[i+1].getElementsByTagName("Longitude");

Lat1 = Lat1[0].firstChild.nodeValue;
Lng1 = Lng1[0].firstChild.nodeValue;

var point1 = new GPoint(Lng1, Lat1);

After the next point is created and stored as 'point1', create an array containing the current point and next point.

var points=[point, point1];

With the position of the two points held inside of the variable 'points', we can create a polyline between the two points and overlay it onto the map using 'map.addOverlay()' and 'new GPolyline()'. The color of the polyline changes after the XML file's hits its halfway point.

if (i < markers.length/2){
map.addOverlay(new GPolyline(points, "#336633",2,1));
}else{
map.addOverlay(new GPolyline(points, "#000099",2,1));
}

Inside of the 'plotPoint()' function are calls to the calculateDistance, loadingPercentage, and calculateTime functions. Hopefully, these are pretty self explanatory and if not shoot me an email or post a comment for an explanation.

calculateDistance(Lng, Lat, Lng1, Lat1)

loadingPercentage(i);

calculateTime(i);

'function createMarker()' creates and displays the icons/balloons you see on the map. The begin, end, and checkpoint icons are created using the 'GIcon()' constructor.

var icon = new GIcon();
icon.shadow = "images/mm_20_shadow.png";
icon.iconSize = new GSize(12, 20);
icon.shadowSize = new GSize(22, 20);
icon.iconAnchor = new GPoint(6, 18);
icon.infoWindowAnchor = new GPoint(9, 5);

The icons are then placed on the map if the point passed in happens to be a beginning, finishing, or checkpoint point. To do this we create a marker using the point passed into the function and the 'new GMarker()' constructor.

var marker = new GMarker(point);

After the marker is created, it is placed on top of the map using 'map.addOverlay()'.

map.addOverlay(marker);

The little bubble and text that appears after a marker is clicked is registered using 'GEvent.addListener()'.

GEvent.addListener(marker, "click", function() {
marker.openInfoWindowHtml("Finish");});

Conclusion

We had a blast getting this working. For more information definitely check out Google's API documentation. The Google Map API makes it very easy to create a map, generate points, and overlay those points on a map. They have tons of code examples. Definitely have to thank them for making it easy.

Obviously, this implementation has a ton of potential outisde of just excercise and workout tracking. It wouldn't be difficult to adapt the functions to parse any kind of XML file with latititude, longitude and time data. Road trips. Scavenger hunts. Surveying. Truck routes. Package tracking. Whatever you want. Once you get the hang of it, anything's possible.


Post a Comment

0 Comments