Introduction to Geo-Coding in Rails

Introduction

Geocoding typically refers to the transformation process of addresses and places to coordinates.

# Example
Geocoder.coordinates("25 Main St, Cooperstown, NY")
 => [42.700149, -74.922767]

It is sometimes called forward geocoding whereas Reverse geocoding uses geographic coordinates to find a description of the location, most typically a postal address or place name.

In Rails we are going to use Geocoder gem.

Geocoder [Reference Inspired from Original Doc]

Geocoder is a complete geocoding solution for Ruby. With Rails it adds geocoding (by street or IP address), reverse geocoding (finding street address based on given coordinates), and distance queries. It’s as simple as calling geocode on your objects, and then using a scope like Venue.near("Billings, MT").

For geocoding your model must provide a method that returns an address. This can be a single attribute, but it can also be a method that returns a string assembled from different attributes (eg: city, state, and country).

Next, your model must tell Geocoder which method returns your object’s geocodable address:

geocoded_by :full_street_address   # can also be an IP address
after_validation :geocode          # auto-fetch coordinates

For reverse geocoding, tell Geocoder which attributes store latitude and longitude:

reverse_geocoded_by :latitude, :longitude
after_validation :reverse_geocode  # auto-fetch address

Request Geocoding by Zip-Code

geocoded_by :zip_code

reverse_geocoded_by :latitude, :longitude do |obj, results|
  if geo = results.first
    obj.state = geo.state
    obj.city = geo.city
  end
end

 

Request Geocoding by IP Address

Geocoder adds location and safe_location methods to the standard Rack::Request object so you can easily look up the location of any HTTP request by IP address. For example, in a Rails controller or a Sinatra app:

# returns Geocoder::Result object
result = request.location

The location method is vulnerable to trivial IP address spoofing via HTTP headers. If that’s a problem for your application, use safe_location instead.

Location-Aware Database Queries

For geocoded objects you can do things like this:

if obj.geocoded?
  obj.nearbys(30)                      # other objects within 30 miles
  obj.distance_from([40.714,-100.234]) # distance from arbitrary point to object
  obj.bearing_to("Paris, France")      # direction from object to arbitrary point
end

 

At class level

Venue.near('Omaha, NE, US', 20)    # venues within 20 miles of Omaha
Venue.near([40.71, -100.23], 20)    # venues within 20 miles of a point
Venue.near([40.71, -100.23], 20, :units => :km)
                                   # venues within 20 kilometres of a point
Venue.geocoded                     # venues with coordinates
Venue.not_geocoded                 # venues without coordinates

Here Venue is a model in which geocoder is initialized.

Distance and Bearing

When you run a location-aware query the returned objects have two attributes added to them (only w/ ActiveRecord):

  • obj.distance – number of miles from the search point to this object
  • obj.bearing – direction from the search point to this object

Bearing is given as a number of clockwise degrees from due north, for example:

  • 0 – due north
  • 180 – due south
  • 90 – due east
  • 270 – due west
  • compass-silver-direction-north-south-east-west-34574249230.1 – southwest
  • 359.9 – almost due north

 

 

 

 

 

You can convert these numbers to compass point names by using the utility method provided:

Geocoder::Calculations.compass_point(355) # => "N"
Geocoder::Calculations.compass_point(45)  # => "NE"
Geocoder::Calculations.compass_point(208) # => "SW"

when using SQLite distance and bearing values are provided for interface consistency only. They are not very accurate.

To calculate accurate distance and bearing with SQLite or MongoDB:

obj.distance_to([43.9,-98.6])  # distance from obj to point
obj.bearing_to([43.9,-98.6])   # bearing from obj to point
obj.bearing_from(obj2)         # bearing from obj2 to obj

The bearing_from/to methods take a single argument which can be: a [lat,lon] array, a geocoded object, or a geocodable address (string). The distance_from/to methods also take a units argument (:mi, :km, or :nm for nautical miles).

Search in Box rather than Circle

You can also look within a square rather than a radius (circle) by using the within_bounding_box scope:

distance = 20
center_point = [40.71, 100.23]
box = Geocoder::Calculations.bounding_box(center_point, distance)
Venue.within_bounding_box(box)
  • Note, however, that returned results do not include distance and bearing attributes.
  • Note that#near performs both bounding box and radius queries for speed.

Search in Donut or Ring

You can also specify a minimum radius (if you’re using ActiveRecord and not Sqlite) to constrain the lower bound (ie. think of a donut, or ring) by using the :min_radius option:

box = Geocoder::Calculations.bounding_box(center_point, distance, :min_radius => 10.5)

 

Auto Assignment of decoded info

So far we have looked at shortcuts for assigning geocoding results to object attributes.

However, if you need to do something fancy you can skip the auto-assignment by providing a block (takes the object to be geocoded and an array ofGeocoder::Result objects) in which you handle the parsed geocoding result any way you like, for example:

reverse_geocoded_by :latitude, :longitude do |obj,results|
  if geo = results.first
    obj.city    = geo.city
    obj.zipcode = geo.postal_code
    obj.country = geo.country_code
  end
end
after_validation :reverse_geocode

Every Geocoder::Result object, result, provides the following data:

  • result.latitude – float
  • result.longitude – float
  • result.coordinates – array of the above two in the form of [lat,lon]
  • result.address – string
  • result.city – string
  • result.state – string
  • result.state_code – string
  • result.postal_code – string
  • result.country – string
  • result.country_code – string

Sources

https://github.com/alexreisner/geocoder

http://www.rubygeocoder.com/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s