Home

BILLEISENHAUER.COM

GeoKit Examples

The GeoKit Rails Plugin enables you to geocode, do distance calculations, do distance searches, and guess where your visitors are coming from. Demonstrations of each of these features appear on this page.

Use the links below to jump to the section you are interested in:

Locating New Site Visitors

If you are creating a location-based application which personalizes parts of the application based upon the user's location, you ordinarily have to wait until the user volunteers their location. But what if you could somehow guess where they are located and apply personalization right from the start? This may be just the kind of head start you need to draw your user in.

GeoKit helps you do this by providing an IP address geocoder and a controller macro which geocodes a user's IP address using a before_filter.

For instance, you appear to be from (Unknown City). Cool, right?

There is minimal controller code required to make this happen. With the call to geocode_ip_address in line 2, a user's first visit is geocoded with hostip.info's web service. The resulting GeoLoc instance is stored in the session and in a cookie under the :geo_location key. In subsequent visits, the cookie value is used to cache the location.

The code below shows the call within the ExamplesController, though you could choose to do this in the ApplicationController:

1
2
3
4
5
6
7
8
class ExamplesController < ApplicationController
  # Auto-geocode the user's ip address and store in the session.
  geocode_ip_address

  def geokit
    @location = session[:geo_location]  # @location is a GeoLoc instance.
  end
end

If you want to IP geocode yourself, you can do so like this:

1
@location = GeoKit::Geocoders::IpGeocoder.geocode(request.remote_ip)

Hostip.info may or may not have the IP address you are looking for, so you shouldn't completely count on auto-detecting your user's location. With that said, why don't you try it?

Geocoding Physical Locations

GeoKit supports the Google, Yahoo, Geocoder.us, and Geocoder.ca geocoders. All have the same interface and all return a GeoLoc instance. Some sample calls are below:

1
2
3
4
5
6
7
8
9
# Calling the Google Geocoder:
loc = GeoKit::Geocoders::GoogleGeocoder.geocode('1 Pennsylvania Ave, Washington, DC 20500')

# Calling the Yahoo Geocoder:
loc = GeoKit::Geocoders::YahooGeocoder.geocode('1 Pennsylvania Ave, Washington, DC 20500')

# Or if you prefer, you can do this:
loc = GeoKit::Geocoders::Geocoder.google_geocoder('1 Pennsylvania Ave, Washington, DC 20500')
loc = GeoKit::Geocoders::Geocoder.yahoo_geocoder('1 Pennsylvania Ave, Washington, DC 20500')

Pretty simple, so let's try it:

using

Distance Calculations

Distance calculations are enabled through the GeoKit::Mappable module which is mixed into LatLng and thus picked up by the GeoLoc subclass. You can mix GeoKit::Mappable into your own classes, just make sure that your classes have lat and lng attributes.

GeoKit::Mappable mixes in a class method called distance_between which takes from and to instances which are required to be classes which have lat and lng attributes. The method also takes an options hash which can be used to modify the distance units and the formula used to calculate the distance. The keywords are :units and :formula respectively. Values for :units are :miles (the default) and :kms. Values for :formula are :sphere (the default) and :flat.

Distance Finders

If you've always wanted to remove the ugly trigonometry from your find SQL or if you've just been too intimidated by it to develop distance calculations, then you may find the acts_as_mappable macro the most compelling part of the plugin. Adding it to a model is easy; check out the example below:

1
2
3
4
5
6
7
8
9
10
11
class BlogLocation < ActiveRecord::Base
  acts_as_mappable

  validates_length_of   :name, :within => 1..40
  validates_uniqueness_of :name
  validates_length_of   :city, :within => 0..60, :if => :city
  validates_length_of   :state, :within => 0..2, :if => :state
  validates_length_of   :postal_code, :within => 0..16, :if => :postal_code
  validates_length_of   :country_code, :within => 0..2, :if => :country_code
  validates_presence_of :name,  :lat, :lng
end

With this declaration, it now becomes possible to perform a variety of finds based upon distance. Some examples are below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# An origin can be a LatLng, a GeoLoc, a class that has applied acts_as_mappable, a 
# string containing a physical address to be geocoded, or an IP address to be geocoded.
# In this case, we use a simple LatLng coordinate.
origin = LatLng.new(32.921236, -96.950839)                

# Returns all the BlogLocation rows sorted nearest to farthest.  Notice there is no 
# trigonometry code for the distance column.
blogs = BlogLocation.find(:all, :origin => origin, :order => "distance ASC")

# Returns all the BlogLocation rows conditional upon distance.  This would normally 
# require you to write trigonometry code for the distance column in the select as well 
# as the distance condition in the where clause.
blogs = BlogLocation.find(:all, :origin => origin, :conditions => "distance < 3.97")

# A short version of the above.
blogs = BlogLocation.find_within(3.97, :origin => origin)

# Its a good time to point out that wherever :origin appears, it can always be replaced 
# by the :lat and :lng keys.  Since our origin contains the same coordinates, this call 
# would return the same results as the call above.
blogs = BlogLocation.find_within(3.97, :lat => 32.921236, :lng => -96.950839)

# Of course, compound conditions and parameter substitution are supported.
blogs = BlogLocation.find(:all, :origin => origin, 
                                :conditions => ["distance < ? and city = ?", 5, 'Coppell'])

# You can even wrap geocoding and finding into one method call.  This call uses the 
# failover geocoder, so it will roll through the geocoders in the order that you 
# have specified them in environment.rb.
blogs = BlogLocation.find(:all, :origin => "1 Pennsylvania Ave, Washington, DC 20500", 
                                :order => "distance ASC")

# Or you can IP geocode and find at the same time.
blogs = BlogLocation.find(:all, :origin => "12.215.42.19", :order => "distance ASC")

The acts_as_mappable is configured to use the Haversine method and return distance in miles, but this is configurable. You can pass acts_as_mappable a :default_units key set to either :kms or :miles. You can pass a :default_formula key set to either :flat or :sphere.

You can further configure column names for distance, lat, and lng. This is done by setting values to the :distance_column_name, :lat_column_name, and :lng_column_name respectively.

Enough talk, let's see an example. In the form below, you can enter your blog name or URL and a physical address. We'll geocode the address, store it, and then run a couple of queries to find fellow GeoKit fans both near and far. Give it a try!

Blog Name:
Address:
Distance Units: