A Tech Blog

Make Graticule Failover to other Geocoding Services

The Graticule Ruby gem makes it super easy to geocode addresses using multiple services. One thing I’ve found in developing Unthirsty is that sometimes a geocoding service either fails to be reached or the address couldn’t be found. So as a precaution, I’ve set up a way for Graticule to failover to another service if a lookup fails. Here’s how.

First, set up a list of your preferred geocoding services. Graticule expects a service name and optionally an access key. Some geocoding services like Ontok don’t require a key. However Google does, and it’s probably the one you’ll want to use over the others. Order your geocoders so your preferred geocoders are listed first.

  PREFERRED_GEOCODERS = [
    {:name => :google, :key => '[your key here]'},
    {:name => :yahoo, :key => 'My Service'}, # yahoo will take any string for a key
    {:name => :geocoder_us, :key => 'My Service'} # same with geocoder.us
  ]

Take your pick of geocoding services. Graticule supports quite a few:

  • Google (Free)
  • Host IP (Free, get geocode info by IP)
  • Yahoo (Free)
  • Geocoder.ca (Free but throttled, or paid service, Canadian locations)
  • Geocoder.us (Free for non-commercial use)
  • Local Search Maps (Free)
  • Meta Carta
  • Postcode Anywhere (Paid, locations)

 

Now it’s time to put those geocoders to use. This method loops through your PREFERRED_GEOCODERS array and sets up a new Graticule instance with the service name and key you provided. It will then try to locate it with a lookup to that service. Calling the locate method on a service instance will raise an exception if an error occurs, so it’s possible to rescue all the different errors you can get while doing a lookup.

Note that this was used in a Rails project, so I’m using logger, a built in Rails logging facility. You may want to substitute your own logging method if you’re not using Rails.

 

  def geocode(query)
    PREFERRED_GEOCODERS.each{|service|
      begin
        geocoder = Graticule.service(service[:name]).new(service[:key])
        result = geocoder.locate(query)
        logger.info("Found address [#{query}] with service [#{service[:name]}]")
        return result unless result.precision == :country # probably not worth keeping
      rescue Graticule::AddressError => detail
        logger.error("Nothing found [#{query}] with service [#{service[:name]}] (#{detail})")
      rescue Graticule::Error => detail
        logger.error("Nothing found [#{query}] with service [#{service[:name]}] (#{detail})")
      rescue Graticule::CredentialsError => detail
        logger.error("Invalid credentials [#{service[:name]}] with [#{service[:key]}] (#{detail})")
      rescue Errno::ECONNREFUSED
        logger.error("Connection refused to #{service}")
      end
    }
    raise Graticule::AddressError.new("Couldn't find address [#{query}] with any service")
  end

The failover works because since we are in a begin..rescue block, a raised exception will cause it to skip over the return result line, log the exception, then move on to the next service. But if a lookup succeeds, it will return a new Graticule::Location instance that contains the results, and not go any further. If no lookups are successful, it will raise a new Graticule::AddressError exception that you can trap while calling the method in your code.

Also notice I don’t return a result if the :precision attribute is :country There’s pretty much no use for it if you’re going for specific addresses. You may want to adjust it based on your needs. Maybe you only want results that have :address precision.