We’re going to look at how to store a PostGIS, install PostgreSQL and PostGIS, point using ActiveRecord and activerecord-postgis-adapter gem.
I followed the Geo-Rails Part 2 by @danielazuma post to create a model with a point attribute. The tutorial then shows how easy it is to perform GIS queries but doesn’t get into the details of CRUD functionality through the web-site, this is where I’ll pick-up.
I’ll cover some of the tutorial here to get you started. if you want to see the GIS queries checkout Geo-Rails Part 2 by @danielazuma, his other post are great also, thanks Daniel!
Before we get started one thing I found confusing in Daniel’s tutorial is he calls the point attribute lat_long when in fact to insert data into the column the format is “POINT( LONGITUDE, LATITUDE). For my tutorial I’m calling the attribute location and the user will enter long, lat to make it consistent.
Create a migration specifying a location attribute as type point…
rails g migration port name:string location:point
Change the migration to make the location column geographic…
class CreatePorts < ActiveRecord::Migration def change create_table :port do |t| t.string :name t.point :location, :geographic => true
t.timestamps
end
end
end
Update the Model…
Let’s look at the Port model, there are several things we changed/updated.
class Port < ActiveRecord::Base include PointsHelper set_rgeo_factory_for_column(:location, RGeo::Geographic.spherical_factory(:srid => 4326))
validates :location, format: { with: VALID_LONG_LAT_DIRECTION, message: I18n.t("validation.point") }
def location
point_to_s(self[:location])
end
def location=(s)
# Parse "s" or do whatever you need to with it
# then set your real attribute.
self[:location] = create_point(s)
end
Validation needs to be added for the specific format(s) my customer wanted, i.e. 104.9847° W, 29.7392° N. The Port.location attribute ends up being a RGeo::Geographic::SphericalPointImpl object. I stored the validation format regex in ./config/initializers/constants.rb as a constant VALID_LONG_LAT_DIRECTION.
VALID_LONG_LAT_DIRECTION = /\A(\d*.\d*)(\xC2\xB0)(\s*)(\w),(\s*)(\d*.\d*)(\xC2\xB0)(\s*)(\w)\z/
Also notice I’m internationalizing the message using I18n.t(“validation.point”), the message is located in ./config/locales/en.yml. I’m keeping all my validation messages under validation:
validation:
point: "should be longitude, latitude with direction, i.e. 39.7392° N, 104.9847° W"
Location Getter & Setter
The location attribure is actually a RGeo::Geographic::SphericalPointImpl object, because of this we need to perform translations to and from an rgeo point implementation and the formatted string.
To translate between a point object and string I overrode location getter and setter. So I can use these translation methods in other models I created a PointsHelper, it’s included in first line of the model. The PointsHelper methods are s_to_point and point_to_s. Implementation of these methods is left as an exercise for the reader (I know I hate when people do that, just spoon feed me dammit!…)
Views
Since we overrode the location getter and setter only the form partial, _form, needs to be updated.
The form partial for location looks like this…
<div class="field">
<%= f.label "location" %>
<%= f.text_field :location, :value => @port.location %>
</div>
The change here is :value => @port.location. If we don’t add :value the getter is not called and the web page displays the value as seen in the database, 0101000020E6100000C2172653053F5A40053411363CBD3D40.
So now we have a way to enter and view GIS points with in the rails framework. In the near future I’m sure I’ll need to plot these points on a map.