GPS-style Latitudes and Longitudes on Rails Forms
Quite a few plugins exist to help you geocode post codes or zipcodes, and calculate the distance between two points. However few, if any, help you with latitudes and longitudes on forms, with input elements that match your GPS, and intelligent validation.
This is now solved.
I’m building a web application that holds cruising information for offshore sailors. Much of the information is located, in the sense that it applies to a specific point on the Earth’s surface. A report on an anchorage, for example, might say something like, “Beware the submerged rock to starboard at the entrance; strong katabatic winds in spring; herds of wildebeest ashore.” So we need the sailors to supply latitudes and longitudes on most of the forms they fill in.
The consensus among existing geo plugins is to treat latitudes and longitudes as floats, storing them in the database as decimals. However that’s not how people handle latitudes and longitudes. For excellent historical reasons, we think in degrees, minutes and seconds. Or, if you use a GPS — and all sailors do — degrees, minutes and decimal minutes (milli-minutes).
If you want people to enter the latitude and longitude of an anchorage on your site, and to enter them correctly, you need to let them just read it off their GPS. And if they enter an invalid value, you need to flag whether it’s the degrees, the minutes, or the milli-minutes — not simply say the whole lot “is invalid”.
Buy the Rails 2 Plugin Patterns Peepcode PDF. Using the PDF I was able to write the plugin right, first-time. No trial and error: straight to the solution. The time it saved was more than worth $9. And the author’s a great guy ;-)
So, install my GeoTools plugin:
$ script/plugin install git://github.com/airblade/geo_tools.git
(I’ll move all my plugins to Git soon, but they’ll remain available in Subversion for the forseeable future.)
Run a database migration to add latitudes and longitudes to the relevant tables. Let’s say we are talking about buried treasure:
def self.up create_table :treasures do |t| t.string :name t.boolean :display_on_map, :default => true t.with_options :precision => 15, :scale => 10 do |c| c.decimal :latitude c.decimal :longitude end t.timestamps end end
Note we store latitudes and longitudes in the conventional way, so you can use other geo plugins too.
Tell your models what to do:
class Treasure < ActiveRecord::Base acts_as_location end
Add latitude and longitude fields to your forms:
<% form_for @treasure do |f| %> <p> <label for='treasure_name'>Name</label> <%= f.text_field :name %> </p> <p> <label for='treasure_display_on_map'>Display on map?</label> <%= f.check_box :display_on_map %> </p> <p> <label for='treasure_latitude'>Latitude</label> <%= f.latitude_field :latitude %> </p> <p> <label for='treasure_longitude'>Longitude</label> <%= f.longitude_field :longitude %> </p> <% end %>
You can also easily display a latitude and longitude:
<p>Name: <%=h @treasure.name %></p> <p>Location: <%= @treasure.location %></p>
And that will produce:
<p>Name: King Solomon's Mines</p> <p>Location: 12°34.567′S, 98°76.543′W</p>
<% airbudd_form_for @treasure do |f| %> <%= f.text_field :name %> <%= f.check_box :display_on_map %> <%= f.latitude_field :latitude %> <%= f.longitude_field :longitude %> <% end %>
And that’s compatible with HAML too.
So now we have meaningful, GPS-style latitudes and longitudes with minimal effort. And treasure.