Advanced Geocoding with Ruby on Rails

Geocoding with Ruby on Rails and the Google Maps API is normally an easy task. But sometimes you have do more than simple geocoding. For real estate webpages you need additional information about state, county or suburb for a given address.

This tutorial I will show you how to build your own server-side geocoding with address information completing and usage of caching data.

This tutorial has following parts:

  1. Geocode an address
  2. Get extented information to this address

We start with generating a sample application.

1
rails new tutorial

and create an address model in it.

1
2
3
rails g model Address country:string state:string
county:string city:string suburb:string zipcode:string street:string
streetno:string longitude:float latitude:float error_code:string
1
rake db:migrate

Open the app/models/address.rb with your favorite editor. It should look like this:

1
2
3
4
5
6
7
8
9
class Address < ActiveRecord::Base

  require 'open-uri'
  require 'cgi'
  require 'hpricot'

  attr_accessible :city, :country, :county, :error_code, :latitude, :longitude, :state, :street, :streetno, :suburb, :zipcode

end

You have to add the hpricot gem to your Gemfile.

The process explained

The process is quite simple. I’m going to hook the geocoding method on the before_save callback. Every saving action will call the geocoding. I assume that only exists minimal address informations like street number, street name, city and zip_code. I send these informations to the google geocoding api and parse the response in order to get more detailed location data like suburb and county.

One short notice to the geocoding api. The process I show works only with the standard geocoding api from google. If you have a business api account then you have to sign your api query before each request. But this is another topic.

We start with some preparation functions.

1
2
3
  def minimal_clean_address
    [streetno,street,city,zip_code,country].to_a.compact.join(",")
  end

The minimal_clean_address method returns a clean address string for geocoding. It takes care that a missing address component won’t kill your process.

1
2
3
  def api_url
    "http://maps.googleapis.com/maps/api/geocode/xml?sensor=false"
  end

The api_url method wraps the api url from the google geocoding api documentation.

1
2
3
  def api_query
    "#{api_url}&address=#{minimal_clean_address}"
  end

The api_query method returns the whole query for the google geocoding api. This is the query for the developer api. The business api has another query. This query we send to the google api with following method.

1
2
3
4
5
6
7
  def geocode
    open(api_query) do |file|
      @body = file.read
      doc = Hpricot(@body)
      parse_response(doc)
    end
  end

In our api_url the set the output format to XML. With the following function we parse the XML-response.

1
2
3
4
5
6
7
  def parse_response(doc)
    self.error_code = (doc/:status).first.inner_html
    if error_code.eql? "OK"
      set_coordinates(doc)
      complete_address(doc)
    end
  end

At first we check the returned status code and assign it to the error_code field. If the error code is OK then we go further with parsing the geodata and the address information with following functions.

1
2
3
4
  def set_coordinates(doc)
    self.latitude = (doc/:geometry/:location/:lat).first.inner_html
    self.longitude = (doc/:geometry/:location/:lng).first.inner_html
  end

The set_coordinates function parses the geometry part of the xml response and the next function uses the extended address information from google to complete our minimal address.

1
2
3
4
5
6
7
8
9
10
11
12
13
  def complete_address(doc)
    (doc/:result/:address_component).each do |ac|
      if (ac/:type).first.inner_html == "sublocality"
        self.suburb = (ac/:long_name).first.inner_html
      end
      if (ac/:type).first.inner_html == "administrative_area_level_3"
        self.county = (ac/:long_name).first.inner_html
      end
      if (ac/:type).first.inner_html == "administrative_area_level_1"
        self.state = (ac/:long_name).first.inner_html
      end
    end
  end

The complete_address function goes through the xml address parts and checks the type of each component. If the type you are looking for matches then we assign it to our address fields.

In this tutorial we use only sublocality, admin lv 3 and admin lv 1 which represents suburbs, county and state information. If you need to save more information then take a look into the Google Maps API docs.

Caching

Lets start with the caching part. This part is quite simple. If we enter the same address again then we don’t have to use the API. As you know there is a query limit on the geocoding api so it’s a good idea not to exceed it.

The next function replaces our geocode function we use in the before_save callback.

1
2
3
4
5
6
7
8
  def geocode_with_cache
    c_address = address_lookup
    if c_address
      copy_cached_data(c_address)
    else
      geocode
    end
  end

In the first line of this function we check for an existing address in the database. If the address_lookup function returns an address then we use the c_address data to complete our new address. Otherwise we use our geocode function.

In the following lines I will explain the used functions in detail.

1
2
3
  def address_lookup
    Address.where(cache_query).last
  end

It’s an one-liner looking for the last address that matches. The match criteria is simple as you see in the next function.

1
2
3
  def cache_query
    ["streetno = ? AND street = ? AND city = ? and zipcode = ?",streetno,street,city,zipcode]
  end

The query asks for an address with the same minimal address data as the entered address. If the addresses matching we can use the existing address to complete the new address with the next function. It simply copies the needed data to the new address.

1
2
3
4
5
6
7
  def copy_cached_data(ca)
    self.latitude = ca.latitude
    self.longitude = ca.longitude
    self.suburb = ca.suburb
    self.county = ca.county
    self.state = ca.state
  end

Afte implementation of all caching functions don’t forget to change the before_save callback.

1
  before_save :geocode_with_cache

Summary

In this tutorial I showed you server-side geocoding with Ruby on Rails with address completion and caching. It’s a simple caching solution for the geocoding api, but since we use the before_save callback it saves you a lot of api requests.

If you like this tutorial please share it in your social network. If you have suggestions for improvement please leave a comment.

4 Responses to “Advanced Geocoding with Ruby on Rails”

    • Lars

      This tutorial about geocoding is standalone. The tutorial about building a real estate listing service has two parts.

      Reply
  1. ari poya

    hi Lars
    how to create view of this tutorial
    can you give me more clear like part 2 of this tutorial
    thanks for great tutorial

    Reply
    • Lars

      Hi aripoya, I hardly understand your question but I try my best to answer. This tutorial only covers the server side api of Google Maps. It makes no sense to use a view here. In my other tutorial I cover parts of the client side API of Google Maps. You can use the server side functions to geocode an address and transfer the geocoordinates – using the way I showed in the second tutorial – to javascript. Here you can use the Google Maps API to display the coordinates on the map. Regarding to the question I cant answer more precisely and please have understanding that I cant do development work in a blog comment.

      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>