Friday, April 5, 2013

Add Markers to a Google Map With Ruby on Rails and JSON


September 25th, 2008 By: Wes

This 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

7 Responses to “Add Markers to a Google Map With Ruby on Rails and JSON”

  1. David H Says:
    Maybe I’m missing something … but I cant find the locations file in your sample code?
  2. Wes Bangerter Says:
    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.
  3. David H Says:
    locations.js?
  4. Wes Bangerter Says:
    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.
  5. David H Says:
    I get it now. Thank you for your replies and your great tutorial!
  6. James Says:
    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.
  7. Nauman Says:
    Hi,
    I m not ROR guru, but doing work in simple php. and want to create boundries around markers as
    Can anybody show me the sample result of above code that how it’s looking.

Search Engine Optimization Class


September 29th, 2008 By: brian

Recently Moki Systems was invited to teach, at Dixie State College, the first class in a series as part of the SEED Dixie program. (http://www.seedutah.com/)

I’ve uploaded the slides (in Microsoft PowerPoint format) that were used during the class in case any class members want to refer to them. Feel free to comment with any questions you have.
Link to slides: SEO Presentation

Rails Utility Methods


October 20th, 2008 By: Daniel

Browsing the Rails API I found a few methods I wish I’d known about earlier.
current_page? in ActionView::Helpers::UrlHelper returns “true if the current request URI was generated by the given options.”
  current_page?(:action => 'process')
  # => false
 
  current_page?(:action => 'checkout')
  # => true
reverse_merge! in ActiveSupport::CoreExtensions::Hash::ReverseMerge “allows for reverse merging where its the keys in the calling hash that wins over those in the other_hash.”
So now instead of
  def setup(options = {})
    options = {:income => 0, :expenses => 0}.merge(options)
  end
I can do
  def setup(options = {})
    options.reverse_merge! :income => 0, :expenses => 0
  end
And with ActiveSupport::CoreExtensions::String::Conversions I can stop using the PHP feeling
  Date.parse("10/20/2008")
to the more rubyish
  "10/20/2008".to_date

SSH Host Aliases


November 18th, 2008 By: Wes

If you use SSH from the command line you can save yourself some typing by aliasing the servers hostname to something shorter. There’s a bunch of different ways to get similar results, but I prefer to use the built in SSH functionality for this. Create a ~/.ssh/config file. The syntax is:
Host server
HostName server.example.com
User username
 
Host another
HostName another.example.com
User username
Now you can just ssh server instead of ssh username@example.server.com. SCP also works great with these aliases, just scp file.txt server:/path
You can leave the User part out of the config file if your local and remote usernames are the same.

Zebra Striping with jQuery


November 18th, 2008 By: Wes

Zebra Striping a table with jQuery is ridiculously easy. This code will add even and odd classes to every row in all tables:
$(document).ready(function(){
  // Zebra stripe our tables
  $("table tr:odd").addClass("odd");
  $("table tr:even").addClass("even");
});
You can make it more selective by changing the table part in $("table tr:odd") to something like table.stripe, so it will only apply to tables with a stripe class.
Of course you’ll have to throw in some CSS so the even and odd classes actually do something:
table tr.odd {
  background-color: #fff;
}
 
table tr.even {
  background-color: #f3f3f3;
}

Interesting Google Technique – CSS Sprites


November 21st, 2008 By: brian

As I was browsing today, I noticed an interesting technique in use on the Google search results page. The innocent and plain logo at the top left is actually a single image file made up of multiple images which then are made visible by the CSS for each placement:
This is presumably for optimization. Every little bit counts when you serve up 20 bazillion hits a minute.

Using Rails’ New I18n Support in Real Life: Part the First


December 11th, 2008 By: Daniel

Well, there are plenty of nice introductions and demos to Rails’ slick new I18n features out there but I haven’t seen much on using it on a real decent-sized app. So I’ll share some of my thoughts on the subject.
Now a real application has more text that just hello_world and a couple paragraphs, so the question is how to organize it all in a way that makes sense. My first thought was to use controllers and actions, but that doesn’t work because sometimes the action doesn’t match the page you are rendering (for example when a create fails validation you probably render the new page even though params[:action] is still ‘create’). So instead I set my namespaces up to mimic my directory/filename structure as much as possible. And in order to make that a lot shorter and cleaner, I wrote a helper method (and put it in application_helper.rb) to help me out:
UPDATE: Rails 2.3 added “lazy” lookup so instead of the ugly __FILE__ stuff you can just prepend a dot. e.g. “.title” I don’t think it woks in controllers though.
  # Translate from file (I18n namespace set to file path)
  def tf(file, key, options = {})
    t("#{file.gsub(%r|#{RAILS_ROOT}/app/\w*/|, '').sub(/\.(.*)$/, '').gsub('/', '.')}.#{key}", options)
  end
So now any page that starts with a title looks like this:
  <h1><%= tf __FILE__, "title" %></h1>
And I can also use it in my controllers (as long as I include ApplicationHelper):
  def create
    @agent = Agent.new(params[:agent])
    if @agent.save
      flash[:notice] = tf(__FILE__, 'create_s')
      redirect_to documents_url
    else
      flash[:notice] = tf(__FILE__, 'create_f')
      render :action => 'new'
    end
  end
And my config/locales/en-US.yml might look like this:
en-US:
  agent_interface:
    agents_controller:
      create_s: "Agent was successfully created."
      create_f: "Error creating agent."
    agents:
      new:
        title: "Create a new Agent"
      show:
        title: "Showing Agent"
      _form:
        legend: "Agent Information"
  layouts:
    agent_interface:
      agent_interface:
        login: "Log In"
        logout: "Log Out"
I like this method because not only does it work for the simple cases shown above but it also works great with layouts and partials and helpers.
I think it also helps to set some conventions for yourself. At first I started naming things with numbers (p1, p2, etc) but then I realized that makes views very cryptic to understand. So instead I tried to pick translation keys that explained the text a little better. And then tried to reuse those names when applicable. For instance, quite a few pages have a title, intro, legend, submit, etc. For flash messages I used the action name with an _s appended for successes and _f for failures. In general I tried to be as consistent as possible.
In future posts I’ll talk about how I handled images, some I18n customizations and how I checked for translation coverage.
P.S. I think it’s pretty ugly to have all those redundant __FILE__s every time I call the helper, but I couldn’t find a good way to get the filename of the file where the method was first called. I tried playing with the stack trace from caller but it got confused on partials and layouts, and I didn’t have the time to find a better way.