Using the Google Maps API with Ruby on Rails and Coffeescript

In the last tutorial we parsed a XML file with GPS data. In this tutorial I like to show you how to display the GPS data on a map. For this part we use the Google Maps API.

I know there are some gems for using the Google Maps API with Ruby on Rails but in this tutorial we are going to do it from scratch. We will do this with three steps:

  • Install the Polyline Gem
  • Build a small JSON API
  • Call this API using Coffeescript

In my opinion Coffeescript makes the development with Javascript easier. You can also write all the functions with Javascript but it would take more code. Before we can start we need an additional gem.

Install the Polyline Gem

For displaying the track on Google Maps we are going to use the polyline API function. For this function I recommend to use a encoded polyline. It speeds up the function for loading and displaying the track. Using an array of 2.5K single coordinates is slower.

The polyline gem allows us to encode the polyline on server side. Add to the gemfile

1
gem 'polylines'

and run a

1
bundle install

to install it. Now we can build an encoded polyline in the Rails application.

Build an Encoded Polyline

Before we can build a JSON API to provide an encoded polyline we have to build it first. The polyline function of the gem needs as input an array like this:

1
[[latitude1,longitude1],[latitude2,longitude2],....]

To provide this kind of array we start with adding to models/point.rb the following function:

1
2
3
def latlng
[self.latitude,self.longitude]
end

This function returns only the coordinates of the point. In our models/track.rb model we can use this function together with ruby syntax candy to build the needed array.

1
2
3
def polyline_points
self.points.map(&:latlng)
end

This function returns the array we need for the function of the polyline gem. By adding the following function to the tracks.rb model

1
2
3
def polyline
Polylines::Encoder.encode_points(self.polyline_points)
end

we have finished the encoding of our polyline. Now we can start to build our JSON API.

Build a small JSON API

In our last tutorial we used a scaffold generator to build the Tracks controller. The show method should look like this:

1
2
def show
@track = Track.find(params[:id])

respond_to do |format|
format.html # show.html.erb
format.json { render :json => @track}
end
end

As you can see the controller can already handle JSON requests but we need to modify it to fit our needs.

1
2
3
format.json {
render :json => @track.to_json(:methods => [:polyline],:only => [:name])
}

With the to_json method we can control the output of the JSON response. The methods parameter allows us to call a method of the Track model. We use the polyline method of the Track model we prepared earlier. It adds the encoded polyline to our JSON response. The only parameter restricts the attributes only to the name of the track. It’s for reducing the amount of data of the JSON response. You can add any other attribute of the Track model if you need it.

We can test our smal JSON API by calling

1
http://localhost:3000/tracks/<any_track_id>.json

The response should look like this (polyline is shortened):

1
{"name":"Test GPX","polyline":"cd~kJava_@???????@?@?A?????C?E???????A?@?? ...}

With this JSON response we can begin to integrate the Google Maps API.

Integrate the Google Maps API v3

At first we have to add the Google Maps API libraries. Because we need the libraries only in the show method we start by creating a new layout file view/layouts/tracks.html.erb. As template we take the content from view/layouts/application.html.erb. Our tracks layout file should now look like follow:

 

Tutorial Track
<%= stylesheet_link_tag “application”, :media => “all” %>
<%= javascript_include_tag “application” %>
<%= csrf_meta_tags %>

<%= yield %>

In this file we have to insert the Google Maps API libraries. To keep the file clean lets write some helper functions. We open the helper/tracks_helper.rb file and add following functions:

1
2
3
def google_maps_api_key
"AIzaSyD5vuFoJirfgRNu5y5HmlNmnb7dIDKjkp4"
end

This function returns the google maps developer key. If you don’t have one you can get it from Google.

1
2
3
def google_api_url
"http://maps.googleapis.com/maps/api/js"
end

This function returns the API url from the Google Maps API v3.

1
2
3
def google_api_access
"#{google_api_url}?key=#{google_maps_api_key}&amp;libraries=geometry&amp;sensor=false"
end

The access function puts all together and returns the string to get access to the API. Take a note that we have added libraries=geometry. We need the geometry lib to decode the encoded polyline on the client side.

1
2
3
4
def google_maps_api
content_tag(:script,:type =&gt; "text/javascript",:src =&gt; google_api_access) do
end
end

This function builds the script tag with the google_api_access string. This helper function we can add to our layout file in views/layout/tracks.html.erb. The head area of this file should look like this now:

1
2
3
...
Tutorial Track&lt;%= stylesheet_link_tag "application", :media =&gt; "all" %&gt; &lt;%= google_maps_api %&gt; &lt;%= javascript_include_tag "application" %&gt; &lt;%= csrf_meta_tags %&gt;
...

Please take care to insert the google_maps_api helper function before the javascript include tag. After the libray is included we have to initialize it. We do this inside the assets/javascript/tracks.js.coffee file:

1
2
3
4
5
gm_init = -&gt;
gm_center = new google.maps.LatLng(54, 12)
gm_map_type = google.maps.MapTypeId.ROADMAP
map_options = {center: gm_center, zoom: 8, mapTypeId: gm_map_type}
new google.maps.Map(@map_canvas,map_options);

This function returns a new Google Map. At the end of tracks.js.coffee add following code:

1
2
$ -&gt;
map = gm_init()

To see the map we need to add the map_canvas div to our views/tracks/show.html.erb. Simply add:

 

 

to the place inside show.html.erb you like to show the map with the track. Now we can call show view of our test track and see the following:

map_init

All we have to do is to add the track to this map. At first we need a helper function to get our track id from Rails to Javascript.

1
2
3
4
5
def track_id_to_js(id)
content_tag(:script, :type =&gt; "text/javascript") do
"var js_track_id = "+id.to_s;
end
end

Here we create a javascript variable and store the id of the track in it. By adding this function to our tracks layout file

1
2
3
...
Tutorial Track&lt;%= stylesheet_link_tag "application", :media =&gt; "all" %&gt; &lt;%= google_maps_api %&gt; &lt;%= track_id_to_js(@track.id) if @track %&gt; &lt;%= javascript_include_tag "application" %&gt; &lt;%= csrf_meta_tags %&gt;
...

we get the id to Javascript easily. Take care to call this function only if a @track exists.

Now we can return to assets/javascript/tracks.js.coffee and add the following function:

1
2
3
load_track = (id,map) -&gt;
callback = (data) -&gt; display_on_map(data,map)
$.get '/tracks/'+id+'.json', {}, callback, 'json'

In line 1 we define the function load_track with the parameters id and map. In line 2 we define the callback function. This function will be called if the AJAX request is successfully. The parameter data contains the JSON response. In line 3 we execute a JSON request. The first argument is the URL. We build it with the id inside. The empty brackets can contain additional parameters for the request. The third parameter is the callback function. The last parameter defines that the function should be a JSON request.

In the function display_on_map from line 2 we use the JSON response to display the track on the map. The function contains:

1
2
3
4
5
6
display_on_map = (data,map) -&gt;
decoded_path = google.maps.geometry.encoding.decodePath(data.polyline)
path_options = { path: decoded_path, strokeColor: "#FF0000",strokeOpacity: 0.5, strokeWeight: 5}
track_path = new google.maps.Polyline(path_options)
track_path.setMap(map)
map.fitBounds(calc_bounds(track_path));

The first line contains the function definition.

In the second line the decode the encoded polyline with the help of the Google Maps Geometry Library. The third line sets the options of the polyline. The options are mainly for display purposes.

In the 4th line we create a new polyline with the path options from line 3. The 5th line displays the polyline on the map.

In the 6th line we positioning the map viewport around the polyline. For this we create the calc_bounds function:

1
2
3
4
5
6
7
8
calc_bounds = (track_path) -&gt;
b = new google.maps.LatLngBounds()
gm_path = track_path.getPath()
path_length = gm_path.getLength()
i = [0,(path_length/3).toFixed(0),(path_length/3).toFixed(0)*2]
b.extend(gm_path.getAt(i[0]))
b.extend(gm_path.getAt(i[1]))
b.extend(gm_path.getAt(i[2]))

This function is a workaround for the Google Maps Polyline. It has no function to get the bounds of the polyline. So we take the first point, the point after 1/3 of the track and one point after 2/3 of the track. So we get a good approximation for the bounds.

In line 2 we create a new LatLngBounds Google Maps object. In line 3 and 4 we extract the path of the polyline and the length of the path. In line 5 we extract the indexes of the three points and expand the bounds in lines 6 to 8.

At last we add the load_track function after our gm_init function. It should look like this:

1
2
3
$ -&gt;
map = gm_init()
load_track(js_track_id,map)

If we now call the show function we should see:

map_view

As we can see the track displays correctly.

Summary

In this tutorial we learned how to created an encoded polyline with Ruby on Rails to display the track on a Google Map. We built a small JSON API to get the data from Rails to Javascript and used Coffeescript to implement the client side functions.

If you like this tutorial please share it on your social networks. If you have tips for improvement or any other information please leave a comment.

20 Responses to “Using the Google Maps API with Ruby on Rails and Coffeescript”

    • Lars

      Hi aripoya, it doesn’t use PostGIS or any specific database extensions.It’s a simple sqlite db I use. This tutorial is part 2 of the first tutorial about parsing XML data. In the first part I stored only latitude, longitude and elevation as float datatypes in the database. In the second part I use these data together with the Google Maps API. The API functions do all the calculations for displaying it on the map.

      Reply
      • ari poya

        i need to know how to use postGIS in rails app. but some how i don’t understand how to use postGIS on ruby on rails

        Reply
        • Lars

          Hi Aripoya, simply install the PostGIS extension to your Postgres installation and add template: template_postgis and postgis_extension: true inside the database.yml to development, test and production settings. Then you can use specific PostGIS datatypes in your migrations.

          In one of the larger Rails projects I’m involved a co-developer uses PostGIS with Rails via find_by_sql statements.

          Reply
          • ari poya

            hi lars
            after add template: template_postgis and postgis_extension: true inside the database.yml at my database.yml
            then rake db:create i got this error
            rake aborted!
            (): mapping values are not allowed in this context at line 23 column 21
            i don’t now way then i change my adapter with postgis the result is same …
            i want to make t.point :lonlat, :geographic => true at my database
            thanks

  1. Mahmoodi

    hi
    when i use polylines i get this error:
    uninitialized constant Track::Polylines

    Reply
    • Lars

      Hi Mahmoodi, the polyline gem is missing in our sourcecode. Please read the paragraph ‘Install the Polyline Gem’ and add the polylines gem to your Gemfile.

      Reply
  2. mahmoodi

    Hi
    I developed an application from your tutorial and this is very useful for me.
    Already I want to display multi polyline. Based on your tutorial I have a table that my tracks saved in that table. I want to send more than one track id to coffeescript and display its polyline on the map.
    How can I do that? And what is your solution?

    Reply
    • Lars

      Hi mahmoodi, for more complex javascript functions I would expand the JSON API. For example, you can add a json format to the tracks index method and use javascript/coffeescript requests to call the method and read it out. Then you are able to display multiple tracks on one map.

      Reply
  3. Justin

    Hi Lars,

    Thanks for posting such a thorough tutorial!
    I was wandering how you would plot individual points instead of the polyline?

    Reply
  4. Dan

    Lars, Thank you! Here’s a suggestion for folks…
    If you want your center point to be the first coordinate, then put this in your view:

    … then put this in your coffee:
    gm_center = new google.maps.LatLng($(‘#map_canvas’).data(‘track’).latitude, $(‘#map_canvas’).data(‘track’).longitude)

    Reply
  5. Ben

    Hi Lars,
    great tutorial thanks.

    I have two questions:
    1) How would you store additional information per point? For example the timestamp of the track. The encoded polyline probably stores only lat/lon.

    2) Encoding polylines is great for performance. Do you have any ideas about the limits of this approach? For example, polylines with all together 100.000 points might be filtered already on serverside.

    Reply
    • Lars

      Hi Ben,

      For additional information per point I would use (semi-)transparent markers with a marker manager. But this approach has limitations.
      At first you can only click single points on a certain zoom level. At second, if you have a high point density, the performance will go down, even with a marker manager. Maybe I would load the points at a certain zoom level with an ajax request and use the viewport coordinates to limit the points server side.

      I have not tried encoding 100K point yet, so I can’t say something about the limitations.

      Reply

Leave a Reply

  • (will not be published)

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>