http://www.developer.com/

Back to article

Geocoding with the Rails GeoKit Plugin


July 21, 2008

In the opening installment of this series, you learned how to integrate Google Maps into your Rails applications by using the powerful YM4R/GM plugin. YM4R/GM makes it trivial to add maps and map features such as icons and information windows, but requires you to first derive the latitudinal and longitudinal coordinates of the desired locations. But what if you don't know these coordinates? Enter the GeoKit plugin.

In this tutorial, I'll introduce you to GeoKit, showing you not only how to retrieve coordinates using GeoKit, but also to take advantage of several other fascinating GeoKit features to perform tasks such as calculating the distance between two points, finding all points within a specified radius, and identifying user locations simply by ascertaining their IP address.

Introducing the GeoKit Plugin

Created by Bill Eisenhauer and Andre Lewis, GeoKit makes the process of determining the latitudinal and longitudinal coordinates of addresses (known as geocoding) a snap, not to mention performing a variety of complex distance calculations that would otherwise leave you searching for those old college geometry textbooks. But first, you need to install GeoKit. To do so, execute the following command from your project directory:

%>ruby script/plugin install svn://rubyforge.org/var/svn/
   geokit/trunk

Once installed, you need to open your project's environment.rb file and assign your Google Maps API key to the GeoKit::Geocoders::google directive. That's it! You're ready to begin using GeoKit. Finally, place the following line at the top of any controller where you'd like to use GeoKit's geocoder:

include GeoKit::Geocoders

If you're only interested in the distance calculation features, insert the following line:

include GeoKit::Mappable

Geocoding a Street Address

It makes sense to begin with GeoKit's most obvious feature: geocoding. Geocoding an address is surprisingly simple; just pass an address into the GoogleGeocoder.geocode method. GoogleGeocoder.geocode returns a GeoLoc object containing lat and lng attributes (representing latitude and longitude, respectively). From here, you're free to use these coordinates within Ym4r to build the map (Ym4r was introduced in this series' previous installment).

The following controller method demonstrates how to use GoogleGeocoder.geocode to retrieve the coordinates for the Columbus Museum of Art, in addition to preparing a Google Map for rendering in the view that follows:

def map
   coordinates = GoogleGeocoder.geocode("480 E Broad St,
                                         Columbus, Ohio 43215")
   @map = GMap.new("map")
   @map.center_zoom_init([coordinates.lat, coordinates.lng], 14)
end

Executing this action produces the map shown in Figure 1.

Figure 1: Centering the Map on the Columbus Art Museum's Location

Automatically Geocoding Model Addresses

Chances are you're going to want to repeatedly refer to the coordinates of addresses stored in a model. For instance, suppose you were building the world's largest pizzeria directory, and wanted to map the pizzeria locations. Continuously geocoding the pizzeria addresses doesn't make sense, so you'll probably want to modify your pizzeria model to include two additional attributes, lat and lng. Understanding the importance of such a feature, GeoKit's creators added the ability to automatically geocode addresses at the time they're added to the database through acts_as_mappable:

class Pizzeria << ActiveRecord::Base
   acts_as_mappable :auto_geocode=>{:field=>:street_address}
end

This has the effect of examining the street_address attribute at creation time, geocoding the address, and saving the coordinates to the model's lat and lng attributes.

Calculating the Distance Between Two Points

Although GeoKit's geocoding features are great, its ability to perform complex distance-finding calculations is particularly fascinating. For instance, suppose you were considering having a drink at the swanky Mitchell's Steakhouse in downtown Columbus, and then walking over to the Columbus Art Museum for a stroll through the galleries. You don't want to walk too far, and so have decided to write a little script to determine the distance. Using GeoKit's Location.distance_between method, this is easily accomplished. Begin with the controller action:

def distance

   # Retrieve the coordinate for Mitchell's Steakhouse
   mitchells = GoogleGeocoder.geocode("45 North Third Street,
                                       Columbus, Ohio 43215")

   # Retrieve the coordinates for the art museum
   museum = GoogleGeocoder.geocode("480 E Broad St, Columbus,
                                    Ohio 43215")

   # Retrieve the coordinates for Columbus, Ohio
   # (for map centering purposes)
   city = GoogleGeocoder.geocode("Columbus, Ohio")

   # Calculate the distance between the two locations
   @distance = mitchells.distance_from(museum)

   # Create a new map and center it on Columbus
   @map = GMap.new("map")
   @map.center_zoom_init([city.lat, city.lng], 14)

   # Add an icon denoting Mitchell's location
   mitchells_marker = GMarker.new([mitchells.lat,mitchells.lng])
   @map.overlay_init(mitchells_marker)

   # Add an icon denoting the museum's location
   museum_marker = GMarker.new([museum.lat,museum.lng])
   @map.overlay_init(museum_marker)

end

The relevant part of the distance.html.erb view is shown next:

<p>
   The distance between Mitchell's Steakhouse and the Columbus
   Art Museum is <%= @distance.round(2) %> miles.
</p>
<%= @map.div(:width => 400, :height => 300) %>

Executing this action produces the outcome shown in Figure 2.



Click here for a larger image.

Figure 2: Calculating the difference between two points

Finding All Points Within a Specified Radius

Continuing your tour of GeoKit's fascinating distance calculation features, suppose your pizzeria directory consisted of numerous pizzerias strewn throughout the Ohio region, but the user only wanted to see pizzerias located within 25 miles of a specific location. Doing so with GeoKit is laughably easy, thanks to a set of finder methods. For instance, to identify all pizzerias located within 25 miles of the Youngstown, Ohio area, you would execute the following code within your controller:

city = GoogleGeocoder.geocode("Youngstown, Ohio")
pizzerias = Pizzeria.find(:all, :origin => [city.lat, city.lng],
   :within => 25)

From here, you can cycle through each pizzeria, adding its icon to the map. Combining this approach with what you learned in the previous installment, the user might encounter four pizzerias in the Youngstown area:

Figure 3: Finding all points within a specific radius

Identifying User Locations

Many mapping applications are intended to help the user take advantage of the service in the context of his current location. Even though you could explicitly ask the user for his location, wouldn't it be much cooler if you could automatically derive his location and adjust the map accordingly? Using GeoKit's IP geocoding feature, you can do this simply by obtaining the user's IP address.

GeoKit accomplishes this seemingly magical feat by passing the user's IP address to the Hostip.info API, which will look up the address in its database and return the user's city and state if found.

To retrieve a user's IP address, pass request.remote_ip into the IpGeocoder.geocode method. Doing so will return GeoKit's GeoLoc object, which includes a number of attributes, among them city, state, and even street_address. Consider the following controller method:

def ip
   @user_location = IpGeocoder.geocode(request.remote_ip)
end

You then could access the city within the view, like so:

It appears you're located in <%= @user_location.city %>.

Keep in mind that although this feature can be very useful, it isn't foolproof. Remember,GeoKit relies on Hostip.info to determine the location based on an IP address, and even though Hostip.info's database is massive (over 8.5 million entries and counting), it is by no means complete. For instance, Hostip.info was unable to identify my location in downtown Columbus, Ohio, although tests with known IP addresses from other cities worked fine. If your IP address isn't found, be sure to take a moment and add it through the Hostip.info website.

Where to From Here?

With the geocoder component in place, you now have a complete mapping solution at your disposal. But, why stop here? In the next installment of this series, I'll show you how to add interactive features to your maps, giving your users a variety of exciting new ways to take advantage of your application!

About the Author

W. Jason Gilmore is a freelance developer, consultant, and technical writer. He's the author of several books, including the best-selling Beginning PHP and MySQL: From Novice to Professional, Third Edition (Apress, 2008. 1,080pp.).

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date