Google Maps

Add Google maps with the ym4r plugin

Google Maps API

Terms of use:
  • available for any web site that is free to consumers
  • API key for a specific domain/directory
  • API key signup
  • no limit on the number of page views per day
  • 50,000 geocode requests per day per Maps API key

Install the ym4r plugin


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

Signup for API key and configure the plugin

  • get a key for http://wierzba.wzks.uj.edu.pl at the API key signup
  • open the file config/gmaps_api_key.yml
  • in the 'development' section:
    • prepend localhost: to the left of the default key
    • add a new line: wierzba.wzks.uj.edu.pl: your_api_key
    (mind the space after the colon) gmaps_api_key.yml

Include GMap API in the layout

  1. Open the layout file app/views/layouts/coffeehouse.rhtml
  2. Add this snippet to the HEAD section:
    
    <% if @map %>
    <%= GMap.header(:host => @request.host) %>
    <%= javascript_include_tag 'ym4r-gm' %>
    <%= javascript_include_tag 'application' %>
    <%= @map.to_html %>
    <% end %>
    
  3. what this does is as follows: if the map is set
    • GMap.header: output the header with Google Map API code
    • @map.to html: initialize the map
CAUTION: make sure the application renders with layout. If the html source has no headers -- the layout is missing. Easiest way to fix this -- add the layout command to ApplicationController.

Display coffee house on map -- controller

app/controllers/coffee_house_controller.rb

        def show
          @coffee_house = CoffeeHouse.find(params[:id])
          
          @map = GMap.new("map_div")#name of the html element that will hold the map
          @map.control_init(:large_map => true,:map_type => true)
          @map.center_zoom_init([@coffee_house.latitude,@coffee_house.longitude],@coffee_house.zoom)
          @map.overlay_init(GMarker.new([@coffee_house.latitude,@coffee_house.longitude],:title => @coffee_house.name))
          
        end

Display coffee house on map -- view

app/views/coffee_house/show.rhtml add this snippet where you want the map displayed:

<%= @map.div(:width => 600, :height => 400) %>

Place pointer on map -- controller

  1. app/controllers/coffee_house_controller.rb add this method at the end
    
    private
    def set_map_for_editing
      @map = GMap.new("map_div")
      loc = [@coffee_house.latitude,@coffee_house.longitude] 
      @map.control_init(:large_map => true,:map_type => true)
      @map.center_zoom_init(loc, @coffee_house.zoom)
      marker = GMarker.new(loc,:draggable => true)
      @map.overlay_global_init(marker,"marker") 
      @map.record_init marker.on_dragend("gmap_update_position")
      @map.record_init @map.on_zoomend("gmap_update_zoom")
    end
    
  2. call set_map_for_editing where we want to edit the map app/controllers/coffee_house_controller.rb
    
    def new
        @coffee_house = CoffeeHouse.new
        @cities = City.find(:all)
        @companies = Company.find(:all)
        set_map_for_editing
      end
    
      def create
        @coffee_house = CoffeeHouse.new(params[:coffee_house])
        if @coffee_house.save
          flash[:notice] = 'CoffeeHouse was successfully created.'
          redirect_to :action => 'list'
        else
          @cities = City.find(:all)
          @companies = Company.find(:all)
          set_map_for_editing
          render :action => 'new'
        end
      end
    
      def edit
        @coffee_house = CoffeeHouse.find(params[:id])
        @cities = City.find(:all)
        @companies = Company.find(:all)
        set_map_for_editing
      end
    
      def update
        @coffee_house = CoffeeHouse.find(params[:id])
        if @coffee_house.update_attributes(params[:coffee_house])
          flash[:notice] = 'CoffeeHouse was successfully updated.'
          redirect_to :action => 'show', :id => @coffee_house
        else
          @cities = City.find(:all)
          @companies = Company.find(:all)
          set_map_for_editing
          render :action => 'edit'
        end
      end
    
    

Place pointer on map -- view

  1. public/javascripts/application.js
    
      function gmap_update_position(){
        lat = document.getElementById('coffee_house_latitude');
        lon = document.getElementById('coffee_house_longitude');
        lat.value = ""+ marker.getPoint().lat();
        lon.value = ""+ marker.getPoint().lng();
      }
      function gmap_update_zoom(){
        zoom = document.getElementById('coffee_house_zoom');
        zoom.value = ""+ map.getZoom();
      }
    
  2. app/views/coffee_house/_form.rhtml add this line:
    
    <%= @map.div(:width => 600, :height => 400) %>
    
  3. in app/views/coffee_house/_form.rhtml -- uncomment the latitude, longitude and zoom input fields to see how they are updated with javascript

Map of all coffee houses in a given city

  1. app/models/city.rb add association with coffee_houses;
    
    has_many :coffee_houses
    
  2. app/controllers/city_controller add 'show' action:
    
     def show
        @city = City.find_by_id(params[:id])
        redirect_to :action => 'list' and return unless @city
        @map = GMap.new("map_div")
        @map.control_init(:large_map => true, :map_type => true)
        @coffee_houses = @city.coffee_houses
        @coffee_houses.each do |ch|
          tabs = []
          t1 = GInfoWindowTab.new
          t1.tab = 'Address'
          t1.content = "<b>#{ch.name}</b><br>#{ch.street} #{ch.building_number}"
          t1.content += '<br><a href="'+url_for(:controller=>'coffee_house',:action=>'show',:id=>ch)+'">more</a>'
          tabs<< t1
          if ch.photos.size > 0
            t2 = GInfoWindowTab.new
            t2.tab = 'Photo'
            photo = ch.photos.first.thumbnails.find_by_thumbnail('thumb')
            t2.content = '<img src="'+url_for(:controller=>photo.public_filename)
            t2.content +='" width="'+photo.width.to_s+'" height="'+photo.height.to_s+'">'
            tabs<< t2
          end
          @map.overlay_init(GMarker.new([ch.latitude,ch.longitude], :title => ch.name,:info_window_tabs => tabs))
        end
    
        if @coffee_houses.length > 0
          @selected = @coffee_houses.first
          @map.center_zoom_init([@selected.latitude,@selected.longitude],@selected.zoom)
        end
      end
    
    
  3. create the view -- app/views/city/show.rhtml
    
    <h1><%= @city.name %></h1>
    
    <%= @map.div(:width => 800, :height => 600) %>
    
    
  4. click on the marker to see the info bubbles

City geocoding (trivial example)

Instead of centering the map of all coffee houses on the first one, center it on the city centre using the Google geocoder. Replace the old centering code in app/controllers/city_controller.rb with:

    geo_results = Geocoding::get(@city.name+' Poland',{:host=>@request.host})
    if geo_results.status == Geocoding::GEO_SUCCESS
      coords = geo_results[0].latlon
      @map.center_zoom_init(coords,13)
    end
(it's at the end of the show action -- delete/comment out code from "if @coffee_houses.length > 0" on and replace with the above snippet)

Tasks

  1. add a 'updated_at' column to the coffee_houses table and use it to display a man of recently updated coffee houses on the main page
  2. add latitude and longitude columns to cities, and store the geocoded coordinates there instead of querying the geocoder each time