GeoTools

You have lots of plugin choices if you want to geocode North American addresses, or find all the locations near somewhere. But few help you with forms and validation.

This plugin does four things:

  • Adds latitude_field and longitude_field form helpers to Rails' default form builder.
  • Lets your model acts_as_location, to work seamlessly with the form helpers.
  • Validates the location data entered on the form and in the database.
  • Gives you a within named scope to find all lcoations within a given bounding box, such as you would have on a Google map.

Compatibility

Works on Ruby 1.8+ and 1.9+.

Designed for Rails 2.3.

Assumptions

  • Any model that acts_as_location has integers defined for each component of the latitude and longitude:

    # In your model's migration's self.up method:
    create_table :thingies do |t|
      # Your model's various fields.
      t.string :name
      t.timestamps
      ...
    
      # Stuff GeoTools needs:
      t.integer :latitude_degrees,  :latitude_minutes,  :latitude_decimal_minutes, :latitude_decimal_minutes_width
      t.string  :latitude_hemisphere
      t.integer :longitude_degrees, :longitude_minutes, :longitude_decimal_minutes, :longitude_decimal_minutes_width
      t.string  :longitude_hemisphere
    end
    

    Storing the components separately like this avoids the round-trip rounding errors you get when using floating point numbers. If you need a floating point representation in the database, for example to use a mapping plugin, simply add an after_update callback to your model to write the float value to the database.

  • A latitude should be entered on a form like this:

    xx <degree symbol> yy <decimal point> zz h
    

    where:

    xx is degrees (0 <= integer <= 90; maximum length of 2 digits)
    yy is minutes (0 <= integer <= 59; maximum length of 2 digits; optional; defaults to 0)
    zz is decimal-minutes (0 <= integer <= 99; maximum length of 2 digits; optional; defaults to 0)
    h is hemisphere ('N' or 'S')
    

    Note with decimal minutes 2, 20 and 200000 are equivalent. This is because 3.2, 3.20 and 3.200000 are equivalent.

  • Similarly, a longitude should be entered on a form like this:

    xxx <degree symbol> yy <decimal point> zz h
    

    where:

    xxx is degrees (0 <= integer <= 180; maximum length of 3 digits)
    yy is minutes (0 <= integer <= 59; maximum length of 2 digits; optional; defaults to 0)
    zz is decimal-minutes (0 <= integer <= 99; maximum length of 2 digits; optional; defaults to 0)
    h is hemisphere ('E' or 'W')
    

Example

# Model
class Treasure < ActiveRecord::Base
  acts_as_location
end

# View
<% form_for @treasure do |f| %>
  <%= f.text_field :spot_marked_by %>
  <%= f.latitude_field :latitude %>
  <%= f.longitude_field :longitude %>
<% end %>

# Controller
# ...same as usual...

You'll get validation on every field (degrees, minutes, decimal-minutes, hemisphere) generated by the form helpers, though not the overall value any more (TBD).

Here's an example script/console session:

>> puts Treasure.find(:first).location
12°34.56′N, 012°34.56′W   # N.B. If this looks weird online, set your browser's text encoding to UTF-8.

>> puts Treasure.find(:first).location.latitude
12.576

>> puts Treasure.find(:first).location.longitude
-12.576

Someday / Maybe

  • Refactor the multiple string columns into a single string column (per lat. and per lng.), and use virtual attributes to map the single db field back and forth to multiple form fields. Add a float to 'cache' the lat/lng values. This will simplify the code significantly.
  • Add a validation for the overall latitude and longitude values (to catch for example 90°00.01′N).
  • Use method in the form helpers so user can give database columns different names (e.g. my_lat_degrees, etc). See the way Paperclip allows different attachment names.
  • DRY up form helper methods.
  • DRY up location.rb.

Intellectual Property

Copyright (c) 2010 Andy Stewart, AirBlade Software Ltd. Released under the MIT license