I decided that this project is going to be about having fun and nothing more, and as such decided to write the UI first, because all of my work these days is server side, Java, so 'fun' for me involves more dynamic languages, i.e. JavaScript and Ruby.
I wanted to display my gps data as a route on a map, which meant getting up to speed on the Google Maps API, and writing a quick dummy server that could dump out some route data for me.
I decided to use the latest Google Maps API v3 , and of course JQuery for the front end work, and mocked up a quick backend server using Sinatra. I can always redo that backend in something more robust once I want to actually deploy, but for now getting the data to the page is more important than how fast that data is retrieved, or machine resources consumed by serving that data.
Part 1: Displaying The Map
I needed to include the google maps api v3 js:
http://maps.google.com/maps/api/js?sensor=false
and the latest JQuery:
http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js
Then in the ready function, I created a map by pointing it to a div and passing in my options.
$(document).ready(function() {
var myLatlng = new google.maps.LatLng(47.5615, -122.2168);
var myOptions = {
zoom: 12,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
var map = new google.maps.Map($("#map_canvas")[0], myOptions);
Note above that I'm assigning the map to a div with id = "map_canvas".Part 2: Parsing the GPS data
Eventually I'd like to upload data directly from my device, but for now I'm going to skip that part and 'pretend' I've already done it. GPS data is exported in the TCX format, which is Garmin-proprietary, but is the easiest to use right now. My current desktop program has the ability to export 1 to N days worth of data into tcx.At some point parsing tcx using a DOM based parser was going to start hurting, so I decided to use a SAX based parser from the start. My usual choice for quick n dirty XML/HTML parsing, hpricot, option was therefore not an option. I investigated nokogiri, but eventually settled on libxml, mostly because the rdoc on sax parsing was very clear, and it was much faster for sax parsing.
I mainly wanted to parse lat-long data out of the tcx file and dump the coordinates into another file in JSON format. Here is my 5 minute hacked together code:
class PostCallbacks
include XML::SaxParser::Callbacks
def initialize(write_file)
@state="unset"
@write_file = File.open(write_file,"w")
@buffer = "{\"data\" : ["
end
def on_start_element_ns(element, attributes, prefix, uri, namespaces)
if element == 'LatitudeDegrees'
@state = "in_lat"
elsif element == 'LongitudeDegrees'
@state = "in_long"
end
end
def on_characters(chars)
if(@state=="in_lat")
@buffer += "{\"lat\": #{chars}"
elsif(@state == "in_long")
@buffer += ", \"long\": #{chars}},"
end
end
def on_end_element_ns(element,prefix,uri)
@state="unset"
end
def on_end_document()
@buffer = @buffer.slice(0,@buffer.length-1)
@buffer += ("]}")
@write_file.puts(@buffer)
@write_file.close()
end
end
parser = XML::SaxParser.file(ARGV[0])
parser.callbacks = PostCallbacks.new(ARGV[1])
parser.parse
Part 3: Serving The GPS Data Up
My goal here was to basically dump that generated file as a response to an AJAX request. Sinatra is perfect for delivering quick services like this. I use Sinatra's DSL to handle a GET request as follows:
require 'rubygems'
require 'sinatra'
require 'open-uri'
require 'json'
get '/sample_path.json' do
content_type :json
File.open("../output/out.json") do | file |
file.gets
end
end
The ../output/out.json is where I parsed the lat-long data from the tcx file into.
I ended up spending a lot of time debugging my get method (http://localhost:7000/sample_path.json). In FF I was unable to get a response back from my GET request. I googled around and apparently there are some compatibility issues with firefox 3.6.2 and JQuery. I was however able get the code to work in Safari, and I'm considering downgrading to FF 3.5 because I haven't seen those kind of problems with that browser, and Firebug is an essential part of my debugging library.
Part 4: Drawing The Data On the Map
The JS code that made the request loads the results into google.maps.MVCArray, which it then uses to create a polyline superimposed on the map:
var url = "http://localhost:4567/sample_path.json";
$.ajax({
type: "GET",
url: url,
beforeSend: function(x) {
if(x && x.overrideMimeType) {
x.overrideMimeType("application/json;charset=UTF-8");
}
},
dataType: "json",
success: function(data,success){
var latLongArr = data['data'];
var pathCoordinates = new google.maps.MVCArray();
for(i = 0; i < latLongArr.length; i++) {
// each coordinate is put into a LatLng.
var latlng = new google.maps.LatLng(latLongArr[i]['lat'],
latLongArr[i]['long']);
pathCoordinates.insertAt(i,latlng);
}
// and this is where we actually draw it.
var polyOptions = {
path: pathCoordinates,
strokeColor: '#ff0000',
strokeOpacity: 1.0,
strokeWeight: 1
};
poly = new google.maps.Polyline(polyOptions); poly.setMap(map);
}
});
Note to self: the issues with JQuery and FF were not. I was making a query on the service from a static web page (not hosted in the same domain), and FF was enforcing standard cross site restrictions. There is an explicit workaround: http://ejohn.org/blog/cross-site-xmlhttprequest/, but for my purposes (standard web app) it is unnecessary.
ReplyDeleteHi ,
ReplyDeletei am not able to use "var myLatlng = new google.maps.LatLng(47.5615, -122.2168);"
i am getting var is can not be resolved..i am not getting where i am wrong..please guide me on this..
My mai Id : abhilashpujari@gmail.com
Is there any way I can see what you JSON lookes like?
ReplyDeletemy email is christopherross1182@gmail.com
Delete