In a previous life, I was what you could call a “cartographer”. I spent my days making maps, but probably not in the way that immediately comes to mind. I wasn’t out exploring uncharted wildernesses; I was sitting in front of a computer teasing away at how things related to each geographically. Instead of using a compass and the stars, my toolset included things like ArcGIS and Python. One thing from this past life that I find myself coming back to in my role as a Software Engineer is PostGIS – an extension for PostgreSQL that enables you to perform spatial queries against the database.
What the heck is GIS?
To understand what solutions PostGIS can provide, we should first start with GIS itself. GIS stands for Geographic Information Systems and boils down to a few things:
- Nearly everything relates to a place that can be represented by a reference system
- Things that can be represented by a reference system have relationships with each other
- Those relationships can be managed, displayed, and analyzed using software
GIS has managed to proliferate society to the point where most of us would – quite literally – be lost without it. When you use Google Maps to get directions to your next hiking destination, you’re leveraging Google’s topology rules to get you there most efficiently. Or when you search Yelp for the best Thai restaurants nearby, you’re performing a spatial query to find the closest places to your current location. The applications are seemingly endless.
While there are niche software applications that are used everyday by GIS professionals, there are more user-friendly tools out there to get your next web application spatially aware.
GIS and Rails
We’re Ruby on Rails fans over here at Simple Thread, so when a project that I work on needed an improvement to a basic geofence feature, I went looking for a way to incorporate PostGIS into our stack. The initial quick-and-dirty approach we took for this feature was to solve the problem client-side, using the Haversine formula written in Javascript. This worked, but we wanted to implement something more robust for a long-term solution.
In our search for a new solution, we found the wonderful postgis-activerecord-adapter gem (originally created by Daniel Azuma) and I instantly fell in love. This takes all of the beauty of PostGIS and tacks it on to Rail’s ActiveRecord, adding new column types and methods for you to use.
After a few quick configuration steps, the gem was ready to be used and our queries were ready to be improved. I’m here to walk you through these steps in order to show you just how easy it is.
Installation
First, you need to have the PostGIS extension for PostgreSQL installed on your computer. For Mac users, running brew install postgis
should do the trick. If you’re using a different operating system, the PostGIS Documentation has all of the information you need to get started.
Next, just like any other gem, you will need to add it to your Rails project. Add gem activerecord-postgis-adapter
to your Gemfile, run ./bin/bundle install
, and you’re good to move on to configuring your app to use it.
Configuration
In order for your Rails application to speak the PostGIS language, you’ll have to set up your PostgreSQL database to use it. This is done by going into your config/database.yml
file and changing the adapter
option to postgis
.
In addition to modifying your database adapter, you’ll also need to write a migration that enables the PostGIS extension itself. See below for a code snippet on how to do so
class AddPostGisExtension < ActiveRecord::Migration[7.0]
def change
enable_extension 'postgis'
end
end
After completing the configuration and installation steps above, your app is now ready to use all of the tools PostGIS has to offer. But how do you take advantage of that?
Models
To unlock the full spatial potential for an ActiveRecord model, you simply need to add a geographic column to its schema. These are column types enabled by the PostGIS extension and come in several different flavors:
:geometry
– The base geographic column type; stores any type of geometry (point, line, polygon):st_point
– Point data; stored as longitude/latitude coordinates:line_string
– Line data; a straight line between two longitude/latitude coordinates:st_polygon
– A collection of line strings, connected to form a shape:geometric_collection
– A collection of geometric features:multi_point
– A collection of:st_point
features:multi_line_string
– A collection of:line_string
features:polygon_connection
– A collection of:st_polygon
features
Adding one of these column types to your model opens the door to use additional ActiveRecord methods from the activerecord-postgis-adapter
gem. So now that we have everything set up, how the heck do we use this thing?
Using the Gem
So far I explained how to install PostGIS on our machine, configure our Rails app to use the activerecord-postgis-adapter gem, and create a geographically enabled column on our model. In order to see it in action, let’s pretend our database is seeded with the following data:
- Metro Stops: Locations of Washington Metropolitan Area Transit Authority (WMATA) rail stations
Name | Metro Line | Lon/Lat |
Smithsonian | Orange | 38.889, -77.028 |
Judiciary Square | Red | 38.897, -77.015 |
L’Enfant Plaza | Green | 38.886, -77.021 |
- Smithsonian Museums: Museums operated by the Smithsonian Institution on the National Mall
Name | Type | Lon/Lat |
National Museum of African American History and Culture | History | 38.891, -77.032 |
National Museum of Natural History | History | 38.891, -77.026 |
National Gallery of Art | Art | 38.891, -77.019 |
National Air and Space Museum | Technology | 38.887, -77.019 |
Hirshhorn Sculpture Garden | Garden | 38.889, -77.022 |
As you might be able to guess, we’re planning a visit to Washington, D.C to take in some of the free museums located there. I am going to pose a few questions and demonstrate how to answer them with our newfound knowledge.
- Our hotel is located on the Red Line. What is the closest museum to a red line station?
red_line_station = MetroStation.find_by(metro_line: :red)
museum_geo_table = Museum.arel_table
closest_museum = Museum.order(museum_geo_table[:lonlat].st_distance(red_line_station.lonlat)).first
puts closest_museum.name
=> "National Gallery of Art"
- It will be raining the day we’re visiting. Are there any indoor museums within a ¼ mile of the Smithsonian station so we don’t have to walk very far?
station = MetroStation.find_by(name: ‘Smithsonian’)
buffer = station.lonlat.buffer(400) # 400m ~= ¼ mile
museum_geo_table = Museum.arel_table
Museum.where(museum_geo_table[:lonlat].st_within(buffer)).where.not(type: :garden).pluck(:name)
=> ["National Museum of Natural History"]
- How far away is the closest museum from the L’Enfant Plaza station?
station = MetroStation.find_by(name: “L’Enfant Plaza”)
museum_geo_table = Museum.arel_table
closest_museum = Museum.order(museum_geo_table[:lonlat].st_closest(station.lonlat)).first
distance = closest_museum.lonlat.distance(station.lonlat) # Result will be in meters
=> 205.9732700584829
As you can see, answering these questions was as simple as writing some straightforward queries. This is really just scratching the surface of what you can do with PostGIS and I hope it encourages you to explore its full potential. If you’re looking for more resources in order to dig deeper, I would recommend checking out this introduction to PostGIS and the activerecord-postgis-adapter repository. Happy geo-querying!
Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.