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!
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.
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 :)
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…
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
- 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
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.
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 final thing to do was include my client side dependencies in the public folder, so my Express application looked like the structure blelow-
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.