Geo programming with python and javascript
As mentioned in a previous post, I built a widget using Google maps that tracks a friends journey using GPS logs. This post is about how to build such a widget.
I have given Andi the GPS CS1KASP tracker I had laying around.
Contents
Below you see the widget for reference.
Download
You can download the code for this howto.
Logfile format
Andi sent me his logfiles via email
ls -l *.log
10002 2010-08-26 10:46 WG20100815025810.log
1424810 2010-08-26 10:46 WG20100815162202.log
779561 2010-08-26 10:46 WG20100816162336.log
700241 2010-08-26 10:47 WG20100817202507.log
84433 2010-08-26 10:47 WG20100818033625.log
1004726 2010-08-26 10:47 WG20100818180009.log
1800 2010-08-26 10:47 WG20100819204401.log
789287 2010-08-26 10:47 WG20100819204533.log
795328 2010-08-26 10:47 WG20100820193232.log
And they look like this inside
@Sonygps/ver1.0/wgs-84 $GPGGA,025810,6018.7672,N,13415.5247,W,1,03,02 $GPGSA,A,2,10,30,31,,,,,,,,,,02.3,02.0,01.0*01 $GPGSV,2,1,07,02,46,071,50,05,27,126,00,30,60, $GPGSV,2,2,07,31,33,286,31,04,,,28,10,34,081,4 $GPRMC,025810,A,6018.7672,N,13415.5247,W,000.0 $GPVTG,279.2,T,,M,000.0,N,000.0,K,A*03 $GPGGA,025835,6019.1549,N,13416.0216,W,1,03,08
Since google maps can't deal with this directly, we'll have to convert it to KML (short for Keyhole Markup Language).
GPSBabel
GPSBabel is up to that job nicely. First you need to install it
sudo apt-get install gps-babel
I am picking one of the larger logs and convert it to KML
gpsbabel -i nmea -f WG20100815162202.log -o kml -F path.kml
In order to test if it worked, you can upload it to Google maps
Go to My Maps and click on "Create New Map"

Click on "Import"

Pick a file

The result looks like this:

So it basically worked, but it's not very nice. Adding a few options to GPSBabel the result can be improved.
simplify="-x simplify,count=100,crosstrack" infile="-i nmea -f WG20100815162202.log" kmlopts="line_color=ff2a7fff,lines=1,trackdirection=1" outfile="-o kml,$kmlopts -F path.kml" gpsbabel $infile $simplify $outfile

That's better, but way to many arrows. So let's get rid of some of them.
XML parsing and cleanup
The following function (adapted from geolocator) calculates the distance between a pair latitude/longitude coordinates into kilometers.
from math import pi, sin, cos, sqrt, atan2 earthradius = 6371.0 def distance(loc1, loc2): '''Haversine formula give coordinates as (lat_decimal,lon_decimal) tuples ''' lat1, lon1 = loc1 lat2, lon2 = loc2 # convert to radians lon1 = lon1 * pi / 180.0 lon2 = lon2 * pi / 180.0 lat1 = lat1 * pi / 180.0 lat2 = lat2 * pi / 180.0 # haversine formula dlon = lon2 - lon1 dlat = lat2 - lat1 a = ( (sin(dlat/2))**2 + cos(lat1) * cos(lat2) * (sin(dlon/2.0))**2 ) c = 2.0 * atan2(sqrt(a), sqrt(1.0-a)) km = earthradius * c return km
On to parsing the KML file. The module xml.etree from the pythons stdlib is good for such jobs. There's also a couple of helpers that come in handy:
from xml.etree import ElementTree as et import sys #some helpers def search(element, parent=None): yield element, parent for child in element: for node in search(child, element): yield node def search_tag(element, name): for node, parent in search(element): if node.tag == name: yield node, parent def search_folder(element, name): for node, parent in search_tag(element, 'Folder'): name_node = node.find('name') if name_node is not None and name_node.text == name: yield node
The document needs to be opened and cleaned up because ElementTree leaves useless namespace information on tags, and GPSBabel outputs a lot of descriptions that only make the file larger:
def open_document(name): doc = et.parse(name) root = doc.getroot() # get rid of the namespaces for node, parent in search(root): node.tag = node.tag.split('}')[-1] #delete descriptions for node, parent in search_tag(root, 'description'): parent.remove(node) return doc, root
Then we remove all arrow placemarks that are closer then 50km to any of the placemarks we have let trough
if __name__ == '__main__': doc, root = open_document(sys.argv[1]) # remove all waypoints close to previous # waypoints for folder in search_folder(root, 'Points'): places = [] for placemark in folder.findall('Placemark'): placemark.find('name').text = '' coords = placemark.find('Point/coordinates') latlng = map(float, coords.text.split(',')[:-1]) for previous in places: if distance(previous, latlng) < 50: #km folder.remove(placemark) break else: places.append(latlng) # output the file print '<?xml version="1.0" encoding="UTF-8"?>' root.attrib['xmlns'] = "http://earth.google.com/kml/2.1" doc.write(sys.stdout)
Running this program over the first file and putting it into google maps
python cleanup.py path.kml > path_final.kml
And how it looks

I collate all the tracking files into one now, for this GPSBabel supports batch files:
ls *.log > batch.txt
batch.txt now looks like this
WG20100815025810.log WG20100815162202.log WG20100816162336.log WG20100817202507.log WG20100818033625.log WG20100818180009.log WG20100819204401.log WG20100819204533.log WG20100820193232.log
Quickly inserting the appropriate options with the vim command :%s^-i nmea\r-f /g
-i nmea -f WG20100815025810.log -i nmea -f WG20100815162202.log -i nmea -f WG20100816162336.log -i nmea -f WG20100817202507.log -i nmea -f WG20100818033625.log -i nmea -f WG20100818180009.log -i nmea -f WG20100819204401.log -i nmea -f WG20100819204533.log -i nmea -f WG20100820193232.log
And run it with
simplify="-x simplify,count=600,crosstrack" kmlopts="line_color=ff2a7fff,lines=1,trackdirection=1" outfile="-o kml,$kmlopts -F path.kml" gpsbabel -b batch.txt $simplify $outfile python cleanup.py path.kml > final_path.kml
Google Maps Widget
The first thing you need for a maps element on your page is a div that can contain it
<div id="map" style="width:500px; height:500px"></div>
Then you need to include the Google maps API
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"> </script>
Finally the code that makes the map and points to the KML file has to be added
<script type="text/javascript"> $(document).ready(function(){ var map = new google.maps.Map($('#map')[0],{ mapTypeId : google.maps.MapTypeId.SATELLITE }); var path = new google.maps.KmlLayer( 'http://the.path.to.the.kml' ); path.setMap(map); }); </script>
The jQuery method $(document).ready is executed when the document has loaded. $('#map') is also jQuery syntax to fetch the map element. The path points to the KML file we generated.
That is all you need for an easy and useful little google maps embeding. I hope the explanations where useful, now go and make some mad maps!



