Google Maps / Yahoo! Traffic mash-up Tutorial

Here is a quick tutorial showing how to display markers on a Google map with the YM4R/GM plugin. We will also see how to use the Traffic functionnality of the YM4R gem, which connects to the Yahoo! Map Traffic API, to display some traffic data on the map.

Setting up the environment

The first thing is to create a Rails project:

rails traffic

Then you need to install the YM4R gem to get the Yahoo! Traffic ruby helpers :

gem install ym4r

Then you need to install the YM4R/GM plugin:

ruby script/plugin install svn://rubyforge.org/var/svn/ym4r/Plugins/GM/trunk/ym4r_gm

You will need to configure the Google Maps API key to use in the file RAILS_ROOT/config/gmaps_api_key.yml. If you use the WEBRick default settings, the key for the development and test environments should work all right (host localhost:3000). If you have other settings, visit this page and apply for a key for your host.

Since we are not going to use any database, there is no need to configure it.

Initialization of the map

Create a Traffic controller with an index action:

ruby script/generate controller Traffic index

In this action we are going to take care of the initialization of a Google map. Here is the code for the whole Traffic controller, followed by an explanation of what is happening:

require "ym4r/yahoo_maps/building_block/traffic"
include Ym4r::YahooMaps::BuildingBlock
	
class TrafficController < ApplicationController
  def index
    @map = GMap.new("map_div")
    @map.control_init(:large_map => true,:map_type => true)
    @map.center_zoom_init([38.134557,-95.537109],4)
    @map.icon_global_init(GIcon.new(:image => "/images/icon_incident.png", :icon_size => GSize.new(15,15),:icon_anchor => GPoint.new(7,7),:info_window_anchor => GPoint.new(9,2)),"icon_incident")
    @map.icon_global_init(GIcon.new(:image => "/images/icon_construction.png", :icon_size => GSize.new(15,15),:icon_anchor => GPoint.new(7,7),:info_window_anchor => GPoint.new(9,2)),"icon_construction")
  end
end

The first 2 lines are to load the part of the YM4R gem related to the Yahoo! Maps Traffic API. In the index method, the first line initializes the Google map. The argument in the GMap constructor is the id of the DIV element that will contain the map, in this case "map_div". The next 2 lines initialize the controls and the initial view of the map: We saw that yesterday. The last 2 lines initialize 2 icons with names icon_incident and icon_construction and make them available globally, so they can be referenced later. You can download them here and put them in the /public/images folder of your rails project.

We then need to make a template for the index action:

<html>
<head>
  <title>Google Maps / Yahoo! Traffic Mashup</title>
  <%= javascript_include_tag :defaults %>
  <%= stylesheet_link_tag "traffic" %>
  <%= GMap.header(:with_vml => false) %>
  <%= @map.to_html %>
</head>
<body>
  <div id="main">
    <%= form_remote_tag(:url => {:action => :find}) %>
      <div id="form" >
	<label for="address">Address:</label>
	<%= text_field_tag "address" %>
	<%= submit_tag "Find" %>
      </div>
    <%= end_form_tag %>
    <%= @map.div %>
    <div id="notice" style="display:none;"></div>
  </div>
  </body>
</html>

We first include the default JavaScript libraries, since we are going to need Prototype to perform Ajax requests and Scriptaculous for some effects on the error message. Next is the inclusion of the stylesheet. Download it and put it in your public/stylesheet folder. Among other things, inside this stylesheet is set the dimension of the Google map. Then the header: It includes the JavaScript files from Google needed to use the Maps API. The false argument is to order not to output the style for VML elements, that we don't need here (although it would not have hurt much). Finally, the initialization with default parameters of the Map itself. Then the document body, which is pretty standard stuff.

We are now ready to test the initialization of the map. Start your WEBRick server and direct your browser to localhost:3000/traffic. You should see a map centered on the USA, like this:

Ajax request to update the map

The user can enter an adress. If there is some traffic info found for it, we will center the map and display all the traffic reports for the place, taken from the Yahoo! Maps Traffic API. We need an action to respond to these requests. Here is the find method of the Traffic controller, along with 2 helper methods:

def find
  begin
    results = Traffic.get(:location => @params[:address])
    unless results.empty?
      @map = Variable.new("map")
      icon_incident = Variable.new("icon_incident")
      icon_construction = Variable.new("icon_construction")
      @traffic_markers = []
      results.each do |result|
        icon = result.type == "construction" ? icon_construction : icon_incident
        marker = GMarker.new(result.latlon,:icon => icon, :info_window => info_window_from_result(result), :title => result.title)
        @traffic_markers << marker
      end
      @center = GLatLng.new(bounding_box_center(@traffic_markers))
    else
      @message = "No Traffic information found for #{@params[:address]}"
    end
  rescue Exception => exception
    @message = "Service momentarily unavailable"
  end
end
	
private
def bounding_box_center(markers)
  maxlat, maxlng, minlat, minlng = -Float::MAX, -Float::MAX, Float::MAX, Float::MAX
  markers.each do |marker|
    coord = marker.point
    maxlat = coord.lat if coord.lat > maxlat
    minlat = coord.lat if coord.lat < minlat
    maxlng = coord.lng if coord.lng > maxlng
    minlng = coord.lng if coord.lng < minlng
  end
  return [(maxlat + minlat)/2,(maxlng + minlng)/2]
end
	
def info_window_from_result(result)
  return "<div style=\"font-size: 14px;width:200px;background-color:#D2F9F8; \"><strong>#{result.title}</strong></div>
<div style=\"font-size: 10px;width:200px;background-color:#E9FFFE;\"><div><strong>Severity:</strong> #{result.severity}</div>
<div><strong>Description:</strong>#{result.description}</div><div><strong>End:</strong>#{result.end_date}</div></div>"
end

The first line is to query the Yahoo! Maps Traffic service. It returns an array of Traffic::Result objects, each containing information about the severity, description, end date or location of a single traffic report. Then we bind to Ruby variables 3 global JavaScript variables: The map and the 2 icons we defined at initialization time. Then for each traffic report, we build a marker, with a location and an options hash containing a reference to the icon corresponding to its type (construction or incident), a tooltip (with the :title key) and an info window that will be displayed when the marker is clicked on by the user. The HTML code for this window is returned by info_window_from_result. I have included the style along with the data, but it should probably be possible to put this into the CSS. Next we calculate the center of the bounding box of the markers in order to center the map. Then we render the update through RJS.

Update of the map through RJS

Here is the content of the find.rjs code:

unless @message
  page << @map.clear_overlays
  @traffic_markers.each { |marker| page << @map.add_overlay(marker)}
  page << @map.set_center(@center,12)
  page.visual_effect :fade, "notice", :duration => 0.5
else
  page.replace_html "notice", @message
  page.hide "notice"
  page.visual_effect :appear, "notice", :duration => 0.5
end

When there is no error, we first clear all the overlays from the map. Next we add all the markers in turn and center the map. That's it!

The finished app

The app is complete. Therefore we are going to test it. Again get your browser to localhost:3000/traffic. If you enter an address, for example "New York", the map is centered on Manhattan, with a bunch of icons scattered around. You get a tooltip if you leave your mouse for a few seconds on an icon. When you click, you get some details about the report (description, severity and scheduled end date).