Tuesday, July 26, 2011

Node.js + Express + Leaflet + PostGIS = Awesome Maps

Recently I blogged about using GeoJSON to create a very lean application for serving dynamic maps.  To date, all of my posts have related in some way to Microsoft technologies because that is my environment, day-in, day-out, but I thought it would be interesting to create a similar solution to the last blog post using technologies I haven't used before - why? - because it's good to learn something new, and these libraries are really cool!

The Objective

As you would recall from last time, the objective was to create a map with an Open Street Map (OSM) base layer, which overlays the cadastral boundaries as a layer that is retrieved when the user pans or zooms around the map.

The previous solution achieved this using Tile5 to render the map on the browser, and make ajax requests to an ASP.Net MVC application which would retrieve the cadastral boundaries for the current view extent from SQL Server, and return them as GeoJSON structured objects to Tile5 to render on the map.

The new solution uses a mapping library called Leaflet, which makes requests to a Node.js application written using the Express framework, which makes requests to a PostgreSQL/PostGIS database server to supply the requested spatial data, so the solution is using Javascript from end to end .


Leaflet

Leaflet is a lot like Tile5 - it is a javascript mapping library released by CloudMade and exposes functionality to define maps in a browser.  The library allows developers to include OSM data layers via CloudMade services, and has facilities to render GeoJSON, just like Tile5 did in the previous post.


Express

Express is a javascript web development framework built on Node which gives you the ability to write web sites in a similar way to ASP.Net MVC.  The interesting thing about this solution is that it uses javascript both on the client (in the browser) and the server (hosted by Node)

View templates in Express can be written in Jade or Haml, of which I chose the latter because I am using JetBrains' WebStorm as an IDE, and it had native support for Haml syntax checking.  Being a noob I thought that would help :)


PostgreSQL

The reason I chose PostgreSQL is that it appears to have a very rich spatial implementation with the PostGIS module, seeing as there were javascript libraries to perform database access I decided it would suite my purposes.

The first step in the development process was install PostgreSQL, and PostGIS and import my Queensland cadastral data into it.  The spatial indexing in PostGIS is a little different from SQL Server, and is worthy of a future blog post, suffice to say there are some idiosyncrasies in my spatial queries below that warrant a second glance.

Lets check out the code…


Installing Express

Express can be installed by installing Node, and NPM (Node Package Manager), which simplifies the installation of Node based packages.  Once these are installed you can install Express.

I found that there were some dependencies for Node and NPM that I didn't have on my fresh installation, so I had to install the following -
sudo apt-get update
sudo apt-get install git-core curl build-essential openssl libssl-dev
I also encountered a problem installing Express because I cloned the latest version of Node from GitHub, and it wasn't compatible with the later version, so I had to download version 0.4.10 which I extracted and then ran the following commands to install -
./configure
make
sudo make install
node -v
The final command should show the version of Node that is installed.

To install NPM -
curl "http://npmjs.org/install.sh" | sudo sh
npm -v
The final command should show the version of NPM that is installed.

I then installed Express -
npm install -g express
Express can then be used to spawn a template application, and install its dependencies using the following commands -
express /home/tjackson/geojsonexample && cd /home/tjackson/geojsonexample
npm install -d
This will create an Express application with a basic structure -


  • node_modules - contains all the framework libraries
  • public - contains all the folders that will be exposed on the web server, i.e. images, javascripts, stylesheets
  • views - contains all the view templates
  • app.js - is the main application script that is executed in Node, and contains all the route logic, and possibly your controllers


Controllers

Express doesn't come with the concept of Controllers straight out of the box, but there is an MVC example in the GitHub source branch that shows how to achieve a structure with Controllers in separate javascript files which are imported when the server starts up.  The routes must then be configured to point at the required controllers.

This example is so basic, with only two server actions, that the business logic has been included in the app.js file.
The changes I made to the originally generated app.js file were
  • importing pg, which is the PostgreSQL database access library
  • setting the view engine to haml, but this didn't execute my haml files correctly, so I installed haml-js (npm install hamljs)

You can see that the RetrieveCadastre action passes the body through to the function, which represents the bounds of the map to retrieve cadastral information from the database, which you can see being used to build up the SQL statement.

The interesting part of the SQL statement is that it looks like it is using the same filter twice in the where expression.  In fact the first part is using the && operator which instructs the query engine to use the spatial index to do an index seek using the bounding box of the filter to find features whose bounding boxes also intersect the filter.  This will result in more matches than we expect because some feature's bounding rectangles will intersect our area without the shape inside actually intersecting the area, so what we do is then filter the index results such that the underlying geometries are intersecting our spatial area.  For my specific purposes the first filter would be enough, but I included the example for posterity.

The other interesting thing about the SQL is that the selection is returning the GeoJSON of the resulting objects using the ST_AsGeoJSON function.  This is really cool because it saves me from having to parse the result into a GeoJSON structure to send back to the client.

After the statement is executed, you can see that it forms a javascript object for each result feature, using JSON.parse, and then adds this to a feature collection object, which is structured according to the GeoJSON standard.  This collection is then returned to the client for rendering.  Wow, that’s even leaner than last time round!


Views

The only view I have is for displaying the initial map.  As you can see it is much like the Tile5 example from last time around, but with some slight interface changes for Leaflet's differences.
The obvious main difference is the use of Haml rather than Razor for the view template itself.  The one interesting experience I had with Haml was problems with the indenting, which took me back to the days of Fortran77.  The thing I had to watch was the linespace between my javascript functions, and that there was the same amount of indenting on the blank lines (spaces from left) as the number of spaces to the start of the function statements.  I guess this is so that the resulting html is indented according to the developer's intentions, considering closing tags are not required in the Haml markup.


Includes

The final thing to do was include my client side dependencies in the public folder, so my Express application looked like the structure blelow-




Conclusion

As you can see, the code to achieve the solution is even leaner than the previous solution, mainly due to PostGIS being able to return the database results already formatted into GeoJSON.

The example application gives a little peek into the sophistication that can be achieved using javascript as both a client and server side solution.  It is also surprisingly zippy considering that I have the entire set of Queensland cadastral boundaries in the PostgresSQL database on a low spec VirtualBox instance of Ubuntu desktop, with the cadastral boundaries refreshing in under a second on maps of the extent of the example below.


8 comments:

  1. This was a great read. I've been needing to take a closer look at node.

    I thought I'd mention that in your app.js when you're building the spatial SQL, you can leave out most of line 55 as PostGIS' ST_Intersects automatically makes use of any spatial indexes you've built.
    http://postgis.refractions.net/docs/ST_Intersects.html

    ReplyDelete
  2. Thanks for the feedback Jason. With regard to info about PostGIS and how ST_Intersects should utilise the index - I had some problems with this which is why I added the bounding box filter to the statement as well. I discussed it a little more in my latest post http://goo.gl/tJRga, but I am wondering if the issue (i.e. not using the spatial index) might have been caused by me using the geography data type instead of a geometry type.

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. ciao, ho letto il tuo interessante articolo, premetto che non sono pratico ne di gis ne di siti web ma ci sto provando, sto usando leaflet, postgis e dreamweaver cs6 ma non so come fare, ho scaricato il tuo esempio da github e seguito tutte le tue istruzioni ma non riesco a farlo funzionare, inoltre avrei bisogno dell'index.html per vedere nel mio browser.Mi potresti aiutare o consigliare una guida ?

    ReplyDelete
  5. you'll need to change app.register to app.engine with Express 3.0

    ReplyDelete
  6. Am working on a project on maps. I come across this tutorial in mapbox : https://www.mapbox.com/mapbox.js/example/v1.0.0/linking-to-external-data/

    Merely looking at it, since it looks like a jason format, i thought it would be possible that i would get information from the database and create a json file using php and calling it back to javascript using ajax. The problem is it won't accept the newly encoded json though its generating the correct format for the json. So are there any other ways of doing this?

    ReplyDelete
  7. This is good stuff Todd.
    I just started with node.js, and I am very ESRI-based programmer, who is discovering the world of open-source outside ESRI.
    My target is to build a project with node.js, leaflet, and Angular to add these three technologies together.
    What you did is going to help me a lot.

    ReplyDelete