status

location
Washington, DC
Subscribe to GeoRSS Subscribe to KML


Spatial programming with Ruby on Rails

Published in Geolocation, Howto, Programming, Ruby  |  11 Comments


Many of my projects employ a spatial aspect. I am fascinated by location, context, community, and of course, the technology that drives this all. I created my travelogue a while ago, but it’s rather dated, buggy, and not fun to maintain or update since it’s all written in PHP as I was learning PHP.

I’ve really gotten “rolling with rails” (pun intended). One of the factors that really excites many developers is an active community. This can be seen by observing the Mac community (hyperlink any number of weblogs, fan sites, howtos, books, et al. here), car clubs, kennel clubs, etc. Ruby and Rails are both pumping out huge numbers of plugins, add-ons, and howtos that further fuels the fire.

However, there aren’t currently any good spatial libraries or support within RoR. Specifically, I would like to store general geometry information (areas, points, lines) in a database, create the model in Rails, and then query the model with code like: @user.get_within_radius(:users, 50).

Databases and GIS

My biggest difficulty is that my shared host (Dreamhost) does not provide PostgreSQL databases, only MySQL. As anyone in the GIS field knows, PostGIS, an extension module for PostgreSQL is the requirement for proper spatial database storage and retrieval. Instead of storing locations as a pair of floats, true geometry in the form of POINT, POLYGON, CURVE, and others are used.

MySQL Spatial Extensions are began to emerge in MySQL 4.1. However, as this article on GIS and Spatial Extensions with MySQL points out, many of the core functions are missing (most notably Distance()). This is true even in the current MySQL 5.0. The result is having to do more complex queries to get distance information:

SELECT
  c.cab_driver,
  ROUND(GLength(LineStringFromWKB(LineString(AsBinary(c.cab_loc),
                                             AsBinary(a.address_loc)))))
    AS distance
FROM cab c, address a
WHERE a.address = 'Foobar street 110'
ORDER BY distance ASC LIMIT 1;

In the meantime, there are several stop-gap solutions. Bryan Wood (glytch.com) put together a Ruby library file that provides a small number spatial operation utilities, and is useful for operating on the “simple database scheme” of a latitude, longitude pair of float columns. The operations are performed by doing a simple SQL query where the latitude and longitude are within max/min bounds. This can’t handle radius or spherical operations quickly, but is good for a rough estimate.

For an actual spherical measurement, you can do the following in your ruby code (via GeocodeAmerica):

@places = Place.find_by_sql ["select p.* from places p where 
     ((3963.0 * acos(sin(p.latitude/57.2958) *
       sin(?/57.2958) +  cos(p.latitude/57.2958) * cos(?/57.2958) *
        cos(?/57.2958 - p.longitude/57.2958))) < ?)", 
        lat, lat, lon, distance]

Extending Ruby on Rails

A better solution would be to develop a model that provides a GIS interface to a PostGIS or other OGC-style database. This requires extending the Ruby on Rails model to arbitrary column data types, and then providing the data types as getters/setters.

Given the SQL create cod:

CREATE TABLE address (
  address CHAR(80) NOT NULL,
  address_loc POINT NOT NULL,
  PRIMARY KEY(address),
  SPATIAL KEY(address_loc)
);
class Address < ActiveRecord::Base
        def address_loc=(textrepresentation)
           write_attribute(:address_loc, 
           Address.find_by_sql(["SELECT GeomFromText(?) 
as value", textrepresentation]).first.value)
        end

        def address_loc
          Address.find_by_sql(["SELECT AsText(address_loc) as value 
FROM addresses WHERE id = ?", id]).first.value
        end
end

Geocoding

Geocoding is the act of converting a location name (major name, street address, etc.) into latitude and longitude (or whatever your preferred numerical location representation may be).

Ruby Geocoding Resources

Similar Posts


Responses

  1. guilhem says:

    February 22nd, 2006 at 5:58 am (#)

    I released a few weeks ago georuby, along with a PostGIS adapater for ActiveRecord ( http://rubyforge.org/projects/georuby/ ). They transparently provide to a Rails application some of the functionalities you are describing. Basically, the geometric columns are treated the same way as other base data type columns. There are still no automatically generated spatial query methods (like “within_radius” or “find_by_… ” with spatial operators instead of =) due to lack of time on my part but it should not be very hard to add.

  2. Barry says:

    March 16th, 2006 at 12:29 am (#)

    Checkout http://www.geocoder.ca/ if you’re in Canada!
    Just a hint, but I think someone wrote a Rails wrapper for the service that should be out this month.

  3. Andrew says:

    March 16th, 2006 at 6:45 am (#)

    Excellent – looking forward to it. What would be nice is to pull all the Geocoders from various services/countries into a single plugin/library and make the switching between services transparent to the calling code.

    Barry – would you have any info on this “hinted developer”?

  4. Xin says:

    March 16th, 2007 at 7:30 pm (#)

    Check out http://geokit.rubyforge.org/.

    It provides geocoding, location finders, and distance calculation in one cohesive package.

    It appears quite simple to use.

    I did try georuby and spatial adapter for a good couple of days, but it is too much for what I need. All I need is points.

  5. Andrew says:

    March 19th, 2007 at 7:42 pm (#)

    @Xin – YM4R/Mapstraction is a great mapping plugin for doing points, lines, etc. and very easy to pick up too.

  6. Roman Alberto says:

    April 1st, 2007 at 10:05 pm (#)

    nice site

  7. LenZ says:

    June 25th, 2007 at 1:42 am (#)

    JFYI, MySQL has started to improve the GIS support and has added several functions that are not using MBRs but precise operations instead, e.g. BUFFER, DIFFERENCE, DISTANCE, INTERSECTION, UNION. For more info, see http://www.planetmysql.org/entries/7897

  8. Spatial programming with Ruby on Rails at Freak Enterprises says:

    October 21st, 2007 at 7:27 am (#)

    [...] http://highearthorbit.com/spatial-programming-with-ruby-on-rails/ [...]

  9. Jacob Lyles says:

    September 17th, 2008 at 8:05 am (#)

    If you have SSH access to your domain with Dreamhost, you can install your own software for your account. I imagine that you can install PostgreSQL. The only problem is that you have to do it manually i.e. from source (But I doubt that´s a problem for you)

  10. victor hazbun says:

    September 1st, 2011 at 5:14 pm (#)

    Suggestion… you must use Engine Yard, I´m working with Rails 3 and Postgres DB with Postgis.

  11. Bowral says:

    October 26th, 2011 at 6:52 am (#)

    For the last little while, I’ve been looking at ways to get Postgis and RoR to be friends. I’m starting to feel that extending RoR could be the best option. Looking at the Gems that are available, they all seem to forget simplicity somehow.