PostGIS and Rails
PostGIS is and extension for PostgreSQL that adds support for geographic objects. Daniel Azuma has developed a suite of tools for Rails to process geographic data.
Working with geo-spacial data has gotten easier, but setting up an environment, is still a bit of work. This tutorial will walk through setting up PostGIS for Rails.
Dependencies
This tutorial assumes that you are running Mac OS X and have the following installed:
With all of the above setup the first thing that we need to install is PostgreSQL and PostGIS.
brew install postgres postgis
Geos and Proj are both dependencies of PostGIS and are installed by Homebrew with PostGIS. Both of these are needed for the RGeo gem.
Setting Up Rails
For this example we'll create a new Rails application, if you are adding PostGIS support to your current Rails app then skip this step.
rails new geo_events
cd geo_events
Open the Gemfile
and add the following gems to it.
The file should contain the following lines:
gem 'pg'
gem 'rgeo'
gem 'activerecord-postgis-adapter'
If the pg
gem has already been installed into your system and was build
against a different version of PostgreSQL you
need to reinstall it. Simply run gem install pg
. The same goes for
RGeo and
PostGIS. Run gem install rgeo
to
reinstall the gem with native extensions.
The activerecord-postgis-adapter
is the database adapter that will be using
instead of the normal postgresql
adapter. Add the following line after
require 'rails/all'
to config/application.rb
.
require 'active_record/connection_adapters/postgis_adapter/railtie'
The config/application.rb
file should now look like:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
require 'active_record/connection_adapters/postgis_adapter/railtie'
if defined?(Bundler)
Bundler.require(*Rails.groups(:assets => %w(development test)))
end
module GeoEvents
class Application < Rails::Application
config.encoding = "utf-8"
config.filter_parameters += [:password]
config.active_support.escape_html_entities_in_json = true
config.active_record.whitelist_attributes = true
config.assets.enabled = true
config.assets.version = '1.0'
end
end
Next update the database.yml
file to make use of the new adapter and
geo-spacial extensions.
development:
adapter: postgis
database: geoevents-dev
encoding: utf8
postgis_extension: postgis
schema_search_path: '"$user", public, postgis'
Notice that the adapter was changed to postgis
and the postgis_extension
and schema_search_path
lines were added. The postgis_extension
tells the
app to install the PostGIS extensions to
the database when you run rake db:create
. It will run commands similar to
the following:
-- NOTE: These commands do not need to be run manually unless you are
-- installing into a existing database.
CREATE SCHEMA postgis;
CREATE EXTENSION postgis WITH SCHEMA postgis;
The PostGIS extensions are namespaced in
the database so that they aren't exported to the db/schema.rb
file. Because
the extensions are stored in a different namespace the schema_search_path
line is added so that anytime a query is run it will look for tables or views
in the postgis
path in addition to the default "$user"
and public
path.
Now bundle the app and create the database.
bundle
rake db:create
Verify that the extensions are installed with psql
.
psql geoevents-dev
geoevents-dev# \d
No relations found.
geoevents-dev# show search_path;
search_path
----------------
"$user",public
(1 row)
geoevents-dev# set search_path = "$user", public, postgis;
SET
geoevents-dev# \d
List of relations
Schema | Name | Type | Owner
---------+--------------------+----------+-----------
postgis | geography_columns | view | waratuman
postgis | geometry_columns | view | waratuman
postgis | raster_columns | view | waratuman
postgis | raster_overviews | view | waratuman
postgis | spatial_ref_sys | table | waratuman
(5 rows)
The PostGIS views and table are installed.
Creating a Model
Now create a model that will have a geo-spacial feature.
rails g model earthquake
Open the database migration for the model and add the columns given below.
class CreateEarthquakes < ActiveRecord::Migration
def change
create_table :earthquakes do |t|
t.point :center, :srid => 4326, :null => false
t.decimal :magnitude, :null => false
t.timestamps
end
end
end
The column center
is a geometric column with an
SRID of 4326.
You don't need to worry much about weather the column is geometric or
geography, for more info on this read this.
If you don't know stick with geometric and an SRID 4326.
SRID specified the spacial reference
system that is used. This is the one used by Google maps. There are other
types of geometric columns such as line, polygon,and multi-polygon. This will
tell you about all of the options.
Now migrate the database.
rake db:migrate
And add the RGeo factory for creating the
point to the Earthquake model (app/models/earthquake.rb
).
class Earthquake < ActiveRecord::Base
self.rgeo_factory_generator = RGeo::Geos.factory_generator(:srid => 4326)
end
And try to create a model from the console.
rails c
> e = Earthquake.new
> e.center = 'POINT(112.5 5655.6)'
> e.magnitude = 5
> e.save
> e.center.x
> e.center.y
The center is inputed as as a string in WKT format.
Notice that the center isn't validated to be within ±180 for longitude and ±90
for latitude. Also calling center.latitude
or center.longitude
results in
an error. Only center.x
or center.y
work. Lets fix that by monkey patching
RGeo. Create a file namelib/rgeo.rb
and
type the following.
class RGeo::Geos::CAPIPointImpl
alias_method :lat, :y
alias_method :latitude, :y
alias_method :lon, :x
alias_method :lng, :x
alias_method :longitude, :x
end
This simple creates aliases for x and y so that latitude
and longitude
can
be called. Add the following line to the end of application.rb
so that our
extension gets loaded.
require "#{Rails.root}/lib/rgeo"
Now add validation to the Earthquake model.
class Earthquake < ActiveRecord::Base
self.rgeo_factory_generator = RGeo::Geos.factory_generator(:srid => 4326)
delegate :latitude, :to => :center, :allow_nil => true
delegate :longitude, :to => :center, :allow_nil => true
validates :latitude, :presence => true, :numericality => { :greater_than_or_equal_to => -90, :less_than_or_equal_to => 90 }
validates :longitude, :presence => true, :numericality => { :greater_than_or_equal_to => -180, :less_than_or_equal_to => 180 }
end
Now try to create the same model as before in the console and it should have errors on the latitude and longitude.
rails c
> e = Earthquake.new
> e.center = 'POINT(112.5 5655.6)'
> e.magnitude = 5
> e.save
=> false
> e.errors.to_a
=> ["Latitude must be less than or equal to 90"]
And now a valid model:
rails c
> e = Earthquake.new
> e.center = 'POINT(12.5 5.6)'
> e.magnitude = 5
> e.save
> e.center.x
> e.center.y
> e.center.latitude
> e.center.longitude
Conclusion
These are the basics of setting up Rails for geo-spacial processing. There are many more data types and all kinds of uses for web applications. The reference manual for PostGIS is a great resource. If you are looking for some data to play around with TIGER has some great data from the the U.S. Census Bureau.