Add Markers to a Google Map With Ruby on Rails and JSON
September 25th, 2008 By: Wes BangerterThis tutorial will guide you through creating a map using the Google Maps API that will be dynamically populated with markers as the user zooms or scrolls around the map.
For this example, we’re going to create and use a generic Location model.
Geocoding Your Addresses
Geocoding will translate an address into its approximate latitude and longitude.
We’ll use GeoKit, a Ruby on Rails plugin to geocode our addresses. Install it with:
script/plugin install svn://rubyforge.org/var/svn/geokit/trunk
Follow the instructions to obtain and install your own Google API key.
Generate the model, controller and views for our map locations:
script/generate scaffold location name:string address:string city:string \ state:string zip:string
We also need to edit the migration file for this model and add fields for the location’s latitude and longitude:
t.decimal :lat, :precision => 15, :scale => 12 t.decimal :lng, :precision => 15, :scale => 12
Replace the code in app/models/location.rb with:
class Location < ActiveRecord::Base acts_as_mappable validates_presence_of :name, :address, :city, :state, :zip, :lat, :lng before_validation_on_create :geocode_address private def geocode_address geo=GeoKit::Geocoders::MultiGeocoder.geocode("#{address} #{city} #{state} #{zip}") errors.add(:address, "Could not Geocode address") if !geo.success self.lat, self.lng = geo.lat,geo.lng if geo.success end end
That’s all there is to geocoding, now any time we create a Location it will automatically be assigned a latitude and longitude.
Adding the Google Map
In app/views/locations/index.html.erb add:
<div id="map" style="width: 890px; height: 600px;"></div>
And in app/views/controllers/locations_controller.rb, change the index action to:
# GET /locations # GET /locations.xml # GET /locations.js def index respond_to do |format| format.html do @locations = Location.find(:all) end format.xml { render :xml => @locations } format.js do ne = params[:ne].split(',').collect{|e|e.to_f} sw = params[:sw].split(',').collect{|e|e.to_f} @locations = Location.find(:all, :limit => 100, :bounds => [sw, ne]) render :json => @locations.to_json end end end
The index action will now respond to javascript requests with a JSON object containing the first 100 Locations inside of the map boundaries.
In your layout file, add this code inside of the <head> tag:
<% unless @locations.blank? %> <script src="http://maps.google.com/maps?file=api&v=2.x&key=<%= GeoKit::Geocoders::google -%>" type="text/javascript"></script> <%= javascript_include_tag 'prototype', 'maps' %> <% end %>
And finally, create a public/javascripts/maps.js file with this code:
window.onunload = GUnload; var map; var markers = new Array(); Event.observe(window, 'load', function() { if (GBrowserIsCompatible()) { map = new GMap2(document.getElementById("map")); // Center the map on the US map.setCenter(new GLatLng(37.731145,-97.326092),4); GEvent.addListener(map,"moveend",function(){updateMap();}); map.addControl(new GLargeMapControl()); map.addControl(new GMapTypeControl()); updateMap(); } }); function updateMap() { var bounds = map.getBounds(); var southWest = bounds.getSouthWest(); var northEast = bounds.getNorthEast(); // Send an AJAX request for our locations new Ajax.Request('/locations.js', { method:'get', parameters: {sw: southWest.toUrlValue(), ne: northEast.toUrlValue()}, onSuccess: function(transport){ // Remove markers outside of our maps boundaries. if(markers.length > 0){ removeMarkersOutsideOfMapBounds(); } // Add our new markers to the map (unless they are already on the map.) var json = transport.responseText.evalJSON(); json.each(function(i) { id = i.location.id; if(!markers[id] || markers[id] == null){ // Marker doesnt exist, add it. markers[id] = createMarker(i.location); map.addOverlay(markers[id]); } }); } }); } function createMarkerClickHandler(marker, location) { return function() { marker.openInfoWindowHtml( '<div><strong>' + location.name + '</strong><br/> ' + location.address + '<br/>' + location.city + ', ' + location.state + ' ' + location.zip + '</div>' ); return false; }; } function createMarker(location) { var latlng = new GLatLng(location.lat, location.lng); var marker = new GMarker(latlng); var handler = createMarkerClickHandler(marker, location); GEvent.addListener(marker, "click", handler); return marker; } function removeMarkersOutsideOfMapBounds() { for(i in markers) { if(i > 0 && markers[i] && !map.getBounds().containsLatLng(markers[i].getLatLng())) { map.removeOverlay(markers[i]); markers[i] = null; } } }
The updateMap() function is run after the page initially loads and each time the user moves or zooms the map. It sends an AJAX request to the server with the maps boundaries, and the server returns a JSON object of the locations within those boundaries. After it receives the JSON object, it will add new locations to the map (it skips locations that have already been mapped) and removes locations that are no longer within the visible map boundaries.
A sample app containing all of the code can be downloaded here: map-sample-code.zip

Moki Systems is seeking a full-time Ruby on Rails developer. The person should be a self starter, willing and able to figure things out on their own. Applicant should have experience with Ruby on Rails, MVC programming concepts, MySQL and/or PostgreSQL experience and the ability to learn new technologies. Any additional...
December 11th, 2008 at 3:14 pm
Maybe I’m missing something … but I cant find the locations file in your sample code?
December 11th, 2008 at 4:11 pm
I’m not sure which location file you are referring to. I double checked and I’m pretty sure everything is in the sample code.
The model, controller and views are in app/models/location.rb, app/controllers/locations_controller.rb and app/views/locations/ respectively.
December 11th, 2008 at 5:16 pm
locations.js?
December 11th, 2008 at 5:27 pm
location.js is handled dynamically by Rails. When you access /locations.js it is ending up in app/controllers/locations_controller.rb and rendering the code in the format.js block on line 11.
Basically, /location.js just returns a JSON string with all of the locations that should be displayed on the map, there isn’t any real code in it.
December 12th, 2008 at 3:17 am
I get it now. Thank you for your replies and your great tutorial!
April 15th, 2009 at 4:15 pm
Thanks for this! Every other article seems to suggest clearing and reloading ALL the markers which seems like overkill to me. This looks much simpler.
September 29th, 2009 at 2:09 pm
Hi,
I m not ROR guru, but doing work in simple php. and want to create boundries around markers as
http://www.redfin.com/search#search_location=90061
http://www.maplandia.com/pakistan/punjab/
Can anybody show me the sample result of above code that how it’s looking.