Simple MapTileServer using NodeJs & PostGIS — Part 1: Core concepts
By: Manivannan K
Tags: Tileserver, GIS

You are wondering, Why are we doing this!!? There are many commercial and opensource softwares like ArcGIS Online/Enterprise or Geoserver are available in market to perform this. Hmm…May be you want a customizable, simple, light-weight and non-commercial tileserver or May be, you just want to learn how they work or you wanna do this for fun :). If so, you can build it and this can be a good starting point(regardless of the programming language). In this two-part article, we will see how we can build a simple & lightweight tile server with NodeJs and PostGIS.

Basically, our node app will be drawing the vector data on a canvas dynamically and serve the image to the client. Be aware that, this is not a production ready implementation of a tileserver. We will not be caching the generated images; Everytime a request comes, our server creates a new image. The article only discuss about the nodejs implementation of the tile image rendering of a GIS vector data with a few PostGIS queries. We will not be discussing project architecture or data models here. Let’s get started!

What we will be building,

At the end of this article series, we will have a tileserver which renders vector geographical data dynamically and serves them to the client webgis apps using Leaflet or Openlayers.

It will have service url like this format: http://{host}/{layer}/{z}/{x}/{y}

Eg: http://localhost:8080/tileserver/layer_id/7/92/60

Our server in action with demo webgis frontend page

Demo url: https://gishorizon.com/demo.html

We will be using,

NodeJs & ExpressJs environment

NPM packages: node-canvas, node-postgres (Main focus of this article)

Postgresql database with PostGIS enabled — To store GIS data

Firebase firestore — To store non-GIS data: layers & styles(not covered in this article)

Having said that, it’s not mandatory to use any of the above. Each one of the above can be replaced with different with a different approach. For example, instead of Postgresql, one can use file system or Cloud storage bucket to store the GIS data and load & manipulate the data using gdal bindings for NodeJs.

Calculations:

Before we start writing any code, let’s understand some core concepts behind our server: Coordinate conversion for drawing GIS data on a canvas.

Check this algorithm for converting LatLng coordinates to Tile coordinates and vice versa. The Javascript implementation of the algorthim:

The above code will give the tile coordinates for the given location and zoom level. Let’s check with an example,

LatLng: Lat = 11.0, Lng = 79.0, Zoom level = 2

Lat = 11.0, Lng = 79.0, Zoom level = 2 Output(Tile): x = 2, y=1

For our example location and zoom, the tile coordinate is (2, 1) as shown above. Source

Our server will be drawing image for that particular tile coordinates(i.e. It will be drawing that highlighted box). Now, we know where our location is in the tile coordinates and what tile it will be drawing.

What’s next? We need to identify the pixel coordinates on that tile for our example location. Let’s go back to our latLngToTile function. You can notice, we have Math.floor() for both coordinates calculation. If you remove that, we will get some value like x = 2.8777, y=1.8771.

Yup, you are correct!!. The decimal part(0.8777, 0.8771) tells you exactly the position of our example point in the (2, 1)th tile. Now, if we multiply this value with the size of our canvas(tile image size: 256 pixels), we get pixel coordinates (221, 224). The corresponding JS implementation of this algorithm:

The canvas coordinates starts at top-right corner (0, 0); x increases as it moves to right; y to the bottom. The above image shows the pixel position of our point on the tile image(256x256 pixels).

Whether we are rendering raster or vector data, the above calculations are very important. Now that, we have covered the basics, let’s start writing some code.

If you find any gaps or problems, please do ping me or leave a comment. Will meet you in Part 2. Thanks:)