visit
Even if you’ve used Postgres for a while, you might not be familiar with PostGIS. The GIS stands for Geographic Information System. There are many solutions in the space. But the thing that makes PostGIS nice is that it’s based on the well-loved PostgreSQL database. In addition to all of the performance you’d expect from Postgres, we get a full-featured tool for storing geospatial data.
To get started, you’ll need an app of any size. Then, you add an instance of Heroku Postgres to your app.
$ heroku login
$ heroku addons:create heroku-postgresql:basic -a postgis-demo
Creating heroku-postgresql:basic on ⬢ postgis-demo... ~$0.013/hour (max $9/month)
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-fitted-78461 as DATABASE_URL
Once I’ve created my Postgres database, I only have a few more steps .
$ heroku pg:psql -a postgis-demo
--> Connecting to postgresql-fitted-78461
…
postgis-demo::DATABASE=> \x on;
Expanded display is on.
postgis-demo::DATABASE=> show extwlist.extensions;
…
address_standardizer,address_standardizer_data_us,amcheck,autoinc,bloom,btree_gin,btree_gist,citext,cube,dict_int,earthdistance,fuzzystrmatch,hstore,insert_username,intarray,isn,lo,ltree,moddatetime,pg_partman,pg_stat_statements,pg_trgm,pgcrypto,pgrowlocks,postgis,postgis_raster,postgis_topology,refint,seg,sslinfo,tablefunc,tcn,tsm_system_rows,tsm_system_time,unaccent,uuid-ossp
We see postgis
in the list of available extensions. From there, we can create the extension.
postgis-demo::DATABASE=> CREATE EXTENSION postgis;
CREATE EXTENSION
postgis-demo::DATABASE=> SELECT postgis_version();
-[ RECORD 1 ]---+--------------------------------------
postgis_version | 3.4 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
Now that I’ve got the PostGIS extension enabled, I need to load in a dataset to play around with. I’m using the dataset provided by the . The downloaded data bundle is a 21.5 MB zip file. In the data
subfolder of the extracted archive, there’s a 9.5 MB file called nyc_data.backup
.
We can restore the data backup directly to our Heroku Postgres instance by using heroku pg:backups:restore
command. This is incredibly convenient. However, keep in mind the following caveats:
nyc_data.backup
available.
postgis
extension. So, even though we showed above how to install the extension manually, we’ll need to add a flag when we restore our database to pre-install the extension before loading the data.
$ heroku pg:backups:restore \
//github.com/Giorgi/PostgresSamples/raw/main/nyc_data.backup \
-e postgis \
-a postgis-demo
Our backup file is specified through a publicly accessible URL. You can always download the dataset from the PostGIS tutorial, extract the nyc_data.backup
file, and post it online to a location of your own choosing.
The -e postgis
flag specifies that we want to install the postgis
extension prior to loading the backup’s schema and data.
The first thing to remember is that PostGIS is an extension within Postgres. That means that you can also perform any standard Postgres query.
postgis-demo::DATABASE=> SELECT count(*)
postgis-demo::DATABASE-> FROM nyc_streets
postgis-demo::DATABASE-> WHERE name LIKE 'B%';
count
1282
(1 row)
postgis-demo::DATABASE=> SELECT boroname, count(*)
postgis-demo::DATABASE-> FROM nyc_neighborhoods
postgis-demo::DATABASE-> GROUP BY boroname;
boroname | count
---------------+-------
Queens | 30
Brooklyn | 23
Staten Island | 24
The Bronx | 24
Manhattan | 28
(5 rows)
So far, we’ve just done standard PostgreSQL. Now, let’s take a look at how to use PostGIS features.
postgis-demo::DATABASE=> SELECT Sum(ST_Length(geom))
/ 1000 as street_length
FROM nyc_streets;
street_length
--------------------
10418.904717199996
(1 row)
postgis-demo::DATABASE=> SELECT Sum(ST_Area(geom)) / 4047 as acreage
FROM nyc_neighborhoods
WHERE boroname = 'Manhattan';
acreage
-------------------
13965.32012239119
(1 row)
Note that these calculations come from the geospatial data, not from columns related to aggregated data of this sort. Not only that, but these queries execute extremely quickly.
One final query that I’m really amazed by involves the use of . Much like standard database joins, spatial joins can unite multiple tables, but on the basis of spatial relationships. For example, we can query for which neighborhood a specific subway station is in, using the spatial data. To do this, we can use ST_Contains
from PostGIS to determine if the geometry of the neighborhood completely contains the geometry of the subway station.
Based on the name of the subway (in nyc_subway_stations
), we query for the neighborhood (in nyc_neighborhoods
) for which ST_Contains
is true. Our query looks like this:
postgis-demo::DATABASE=> SELECT
subways.name AS subway_name,
neighborhoods.name AS neighborhood_name,
neighborhoods.boroname AS borough
FROM nyc_neighborhoods AS neighborhoods
JOIN nyc_subway_stations AS subways
ON ST_Contains(neighborhoods.geom, subways.geom)
WHERE subways.name = 'Broad St';
subway_name | neighborhood_name | borough
-------------+--------------------+-----------
Broad St | Financial District | Manhattan
(1 row)
PostGIS provides even more advanced location querying functionality with , but that’s outside the scope of our simple demo here.
$ heroku apps:destroy postgis-demo
▸ WARNING: This will delete ⬢ postgis-demo including all add-ons.
▸ To proceed, type postgis-demo or re-run this command with --confirm postgis-demo
> postgis-demo
Destroying ⬢ postgis-demo (including all add-ons)... done
Wait, that’s all? Yeah, that’s all. With a single command and confirmation, everything is torn down, and I don’t need to worry about it anymore.
$ heroku apps
You have no apps.
$ heroku addons
No add-ons.
Now that I’ve deleted my app, you have an incredible opportunity: The unique app name postgis-demo
is available for the first reader who wants to grab it on Heroku! Are you ready to build your next great PostGIS app? Today is the day!