Ninjas on a Penny Farthing

  posted  
Use Twitter? Follow on Twitter.

Mapstraction and Geokit


Welcome to the first in what is hopefully the first in a series of posts introducing some of the stuff we use behind the scenes for Ticket’s with a Twist. Today we’re going to talk about two open source libraries: One for Ruby and one for Javascript (including my own little wrapper around it).

GeoKit

First up is GeoKit – a Ruby library that makes it really simple to deal with multiple geocoders (in essence, web services that let us convert a location in the form of an address to co-ordinates). Written by Andre Lewis and Bill Eisenhauer, it gives us a unified way to access the Google Maps, Yahoo Maps, Geocoder.us and Geocoder.ca geocoders as well as a “Multigeocoder” – kind of like a unified version of all of the above with fallback and the like.

To start off, we’re going to pretty much follow the standard process for installing GeoKit.
In the root directory of your Rails plugin, you can use script/plugin to install it.

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

This will install a copy of all of the related files – The Ruby Geocoder interfaces and the like. Next, you’ll need to edit your initializers or add a new one – By default Geokit will append the configuration to the end of your config/environment.rb file. Since Google Map keys and the like are on a per-domain basis, you’ll likely want to set it in an initializer so that it loads it from a YAML file. For the moment, I’ll leave this as an exercise for the reader and in a few days I’ll post the code I’m using.

Once that’s done, you’ll need to set it up on each of your models / where you want to use it – the full usage information can be found on the official GeoKit site. In this example, we’ll assume my model “Person” has three columns – address, lat and lng.

class Person < ActiveRecord::Base # All of my other model stuff would usually be here acts_as_mappable :auto_geocode => {:field => :address, :error_message => 'Could not geocode address'} end

This will then set it up so that before the record is saved, GeoKit will use our multi-geocder to set the lat and lng attributes for the Person model, making it possible to then do distance based finds. So, I could then do the following:

Person.create :address => "1 Infinite Loop, California"

and GeoKit would get the correct lat / lng, storing it with the record in the database.
Once we’ve done this, we can then do all sorts of stuff such as finding locations with a certain distance:

Person.find :first, :origin => "1 Infinite Loop, California", :within => 5

Would then find the first person within 5 miles of 1 Infinite Loop – in this case, The person
we created before. Since it calculates the distance in sql, we can also use things such as
ordering by distance (e.g. :order => “distance ASC” to return the closest first)

There is a lot more to it than this – You can go further to integrate it with libraries such as Pat Allen’s Thinking Sphinx (Sphinx having built in support for calculating geographical distances – making it fairly easy to search based on distances like we did above) or you can use it in other non-rails-specific libraries.

Mapstraction

Mapstraction is an awesome Javascript library that abstracts out the actual mapping component of it all – it gives us a unified interface between Google Maps, Yahoo Maps, Microsoft’s Virtual Earth and a bunch of other different mapping API’s.

To start, you need to include the javascript for the respective mapping like you would usually do – e.g. with Google Maps you include the javascript with your API Key. Then, you add one line to include the Mapstraction Library.

<script type="text/javascript" src="http://mapstraction.com/svn/source/compressed/mapstraction.js"></script>

Note that usually you would download a copy to the local javascripts/ directory under public.

Once this has been done, the mapstraction library will do most of the work needed to provide a unified interface. For example, if we wanted to add a map with a single point, our HTML and Javascript would be:

<div id="my-map" style="width: 400px; height: 300px"></div> <script type="text/javascript"> var myMap = new Mapstraction('my-map', 'google'); myMap.addMarker(new Marker(new LatLonPoint(37.404,-122.008))); </script>

If you wanted to instead use Virtual Earth, you then would switch the Google Maps includes with

<script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6"></script>

Followed by switching one line of your javascript to:

var myMap = new Mapstraction('my-map', 'microsoft');

And then it’s done. It is just a trivial example but the point here is that Mapstraction will make it easy for you
to write javascript to utilise maps with ease that will also work across multiple providers.

My contribution: map_builder.rb

Map builder is a really really simple and lightweight wrapper around Mapstraction that we’re using in Ticket’s with a Twist
to generate the javascript (albeit in a slightly modified form). You can get it from my GitHub repository and it’s released under an MIT license.

To install it, visit the repo and get a copy of map_builder.rb – if you want to use it in your rails application, you should place the file in the
lib/ directory. Then, inside you’re code, you can then do the follow:

map = MapBuilder.new('div-id') # Also, you can specify the provider, Default is google map.add_point 1.0, 1.0 map.add_point an_object # must respond to .lat and .lng map.add_point 1.0, 1.0, :message => "Some HTML to set as the info message" map.auto_fit # Will take all points and auto zoom / centre map.set_zoom_and_center 5, 5.0, 5.0 # or, use an_object as before

Finally, to get the generated javascript for your map, you can simply use:

map.to_js

The beauty lies in the simplicity – you can pass in coordinates as either lat, lng pairs or
as an object that responds to .lat and .lng – making it really easy to use with Geokit or
on it’s own.

Conclusion

In combination, I’ve found Mapstraction + Geokit to be a great way to easily work with maps & geocoders, letting
us easily add geographically-specific stuff to your rails application. I am using it on our soon to be
released site and even though it’s a cobbled together set of components, it’s easy for us to then go and switch
between any one of the providers if we have the need to in the future.