Instant Mapping Applications with PostGIS and Nodejs

Mapping Applications with PostGIS and Nodejs screenshot
After my post on Open Source Mapping with PHP and MongoDB, I wanted to follow up with a rewrite of our basic instant mapping demo using PostGIS, a full-featured collection of spatial mapping extensions for PostgreSQL.

This app also takes advantage of node-restify, LeafLet Maps, and map tiles from Stamen, to visualize the locations of major National Parks and Historic Sites.

We have a growing collection of these example mapping applications. If you would like to build a similar application that incorporates your own technology stack, or your own custom collection of map points and spatial data, we may already have a basic demo available as a reference:

The source for today’s example is available at:

You’ll be amazed to see how quickly a new PostGIS-powered mapping application can be put together!

The Datastore: PostGIS

OpenShift’s new postgresql-9.2 cartridge includes support for PostGIS 2.1, providing the most advanced collection of geo-spatial features currently available on the platform.

You can add postgres to any existing OpenShift application via the web console, or by running rhc cartridge add postgresql-9.2 APP_NAME from the command line. OpenShift will set up the database and basic permissions for you, returning a fresh set of authentication credentials to your application via environment variables.

Bootstrapping the Application

I’ve wired up this application’s deploy action_hook to bootstrap our database by running the following during our application’s build process:

npm run initdb

When npm receives this message, it will check the application’s package.json file for an associated scripts entry. Our app is configured to run bin/bootstrap.js whenever the initdb task is run.

It would have been possible to run bin/bootstrap.js directly from our application’s deploy action-hook. But, adding this extra step allows us to take advantage of the automation features available in OpenShift’s build process, while producing code that is less dependent on any particular platform (more portable).

There is an excellent post on using npm for task automation over at

Our bootstrap.js file is tiny, since all of our database functionality has been rolled up into a local module named db.js:

var db = require('./db.js')

The initDB function will automatically enable our PostGIS DB extension, create our table schema, import our map points, and add a spatial index to our data.

Let’s take a look inside bin/db.js to see how our pg queries are crafted and called:

var config      = require('config'),
    pg          = require('pg-query')
var pg_config   = config.pg_config,
    table_name  = config.table_name;
pg.connectionParameters = pg_config + '/' +table_name;
var points = require('../parkcoord.json');
function initDB(){
  pg('CREATE EXTENSION postgis;', createDBSchema);
function createDBSchema(err, rows, result) {
  if(err && err.code == "ECONNREFUSED"){
    return console.error("DB connection unavailable, see README notes for setup assistance\n", err);
  var query = "CREATE TABLE "+table_name+
    " ( gid serial NOT NULL, name character varying(240), the_geom geometry, CONSTRAINT "+table_name+ "_pkey PRIMARY KEY (gid), CONSTRAINT enforce_dims_geom CHECK (st_ndims(the_geom) = 2), CONSTRAINT enforce_geotype_geom CHECK (geometrytype(the_geom) = 'POINT'::text OR the_geom IS NULL),CONSTRAINT enforce_srid_geom CHECK (st_srid(the_geom) = 4326) ) WITH ( OIDS=FALSE );";
  pg(query, addSpatialIndex);
function addSpatialIndex(err, rows, result) {
  pg("CREATE INDEX "+table_name+"_geom_gist ON "+table_name+" USING gist (the_geom);", importMapPoints);
function importMapPoints(err, rows, result) {
  var query = "Insert into "+table_name+" (name, the_geom) VALUES " +",") + ';';
  pg(query, function(err, rows, result) {
    var response = 'Data import completed!';
    return response;
function mapPinSQL(pin) {
  var query = '';  
  if(typeof(pin) == 'object'){
    query = "('" + pin.Name.replace(/'/g,"''") + "', ST_GeomFromText('POINT(" + pin.pos[0] +" "+ pin.pos[1] + " )', 4326))";  
  return query;

brianc’s pg-query module makes this code exceedingly simple – Just map your queries to their related callback functions.

The application’s map data is loaded from the parkcoord.json file, incorporating the following object structure:

  "Name" : "Abraham Lincoln Birthplace National Historical Park", 
  "pos"  : [-85.7302 , 37.5332 ] 

Each point is stored as a pg geometry type in WGS84 format (SRID:4326). PostGIS and Leaflet have support for handling much more complicated queries and geometry types, so you shouldn’t run into too many limitations if you decide to extend or enhance a project based on this general design.

Now that our data has been imported and indexed, let’s build our Back-end and create an API endpoint our map’s bounding-box query.

The Back-End: restify

Restify is a modern, express-styled API microframework for javascript, providing a killer collection of back-end of features for serving up data (in addition to static assets).

var config      = require('config'),
    restify     = require('restify'),
    fs          = require('fs'),
    db          = require('./bin/db.js')
var app = restify.createServer()
// Routes
app.get('/parks/within', db.selectBox);
app.get('/parks', db.selectAll);
// Static assets
app.get(/\/(css|js|img)\/?.*/, restify.serveStatic({directory: './static/'}));
app.get('/', function (req, res, next)
  var data = fs.readFileSync(__dirname + '/index.html');
  res.header('Content-Type', 'text/html');
  res.end(data.toString().replace(/host:port/g, req.header('Host')));
app.listen(config.port, config.ip, function () {
  console.log( "Listening on " + config.ip + ", port " + config.port )

The db.selectBox handler for the /parks/within endpoint is also defined in our application’s bin/db.js file:

function select_box(req, res, next){
  //clean our input variables before forming our DB query:
  var query = req.query;
  var limit = (typeof(query.limit) !== "undefined") ? query.limit : 40;
    && Number(query.lon1) 
    && Number(query.lat2) 
    && Number(query.lon2)
    && Number(limit)))
    res.send(500, {http_status:400,error_msg: "this endpoint requires two pair of lat, long coordinates: lat1 lon1 lat2 lon2\na query 'limit' parameter can be optionally specified as well."});
    return console.error('could not connect to postgres', err);
  pg('SELECT gid,name,ST_X(the_geom) as lon,ST_Y(the_geom) as lat FROM ' + table_name+ ' t WHERE ST_Intersects( ST_MakeEnvelope('+query.lon1+", "+query.lat1+", "+query.lon2+", "+query.lat2+", 4326), t.the_geom) LIMIT "+limit+';', function(err, rows, result){
    if(err) {
      res.send(500, {http_status:500,error_msg: err})
      return console.error('error running query', err);
    return rows;

With our Back-end work completed, we’re ready to build a simple single-page front-end with help from Leaflet Maps.

The Front-End: Leaflet

Leaflet makes it really simple to craft our bounding box query, and add the resulting pins to the map. It provides the cleanest developer interface for client-side mapping that I’ve seen.

We just need to include a link to Leaflet’s css stylesheet and javascript code in our index.html:

<link rel="stylesheet" href="//" />
<script src="//"></script>

We’ll also need to initialize our map view, and include a few event hooks and callbacks for updating our map content whenever our viewport is being loaded or modified.
This code includes a tileLayer and attribution for the terrain tiles from Stamen:

var map ='map').setView([37.8, -122.3], 10);
var markerLayerGroup = L.layerGroup().addTo(map);
L.tileLayer('http://{s}{z}/{x}/{y}.png', {
  maxZoom: 18,
  minZoom: 5,
  attribution: 'Map tiles by <a href="">Stamen Design</a>, under <a href="">CC BY 3.0</a>. Data by <a href="">OpenStreetMap</a>, under <a href="">CC BY SA</a>.'
function getPins(e){
  bounds = map.getBounds();
  url = "parks/within?lat1=" + bounds.getSouthWest().lat + "&lon1=" + bounds.getSouthWest().lng + "&lat2=" + bounds.getNorthEast().lat + "&lon2=" + bounds.getNorthEast().lng;
  $.get(url, pinTheMap, "json")
function pinTheMap(data){
  //clear the current pins
  //add the new pins
  var markerArray = new Array(data.length)
  for (var i = 0; i < data.length; i++){
    park = data[i];
    markerArray[i] = L.marker([, park.lon]).bindPopup(;
  markerLayerGroup = L.layerGroup(markerArray).addTo(map);
map.on('dragend', getPins);
map.on('zoomend', getPins);

The dragend and zoomend map events should now fire whenever our map’s viewport is adjusted, sending our screen’s North East and South West bounding coordinates to the application’s back-end. When our Back-end API returns it’s JSON response, we clear the current collection of map pins and then update the map with our new collection of points.

Leaflet’s documentation is excellent, and is bound to give you several ideas on how to extend or improve an application of this type.

Instant Satisfaction

From my experience, successfully building, hosting, and maintaining a production-quality postgres-backed application has always been a major undertaking. Now, in a matter of minutes, it is possible to provision a new pg-backed application with it’s own horizontally scaleable system architecture, and integrated build, development, and deployment workflows.

To instantly clone+deploy a copy of this application with the rhc command line tool in a single step, run:

rhc app create parks nodejs-0.10 postgresql-9.2 --from-code=

Or, link to a web-based clone+deploy on OpenShift Online or your own open cloud:

It’s never been so easy to spin up quick PostGIS-backed spatial applications!

A live demo of the finished product is available here:

Thanks for following along!

Next Steps

  1. Sign up for OpenShift Online
  2. Spin up your own clone of this application
  3. Learn about setting up a local development environment for this application
  4. Configure your application to work with a custom domain name
  5. Showcase your application in the OpenShift App Gallery
Node.js, OpenShift Online, PostgreSQL
, , , ,
Comments are closed.