Tinkering with Babylon.JS – Part 2 (backend)

Continuing our last blog post, this time we’ll start by creating the backend services that our project requires. For this we’ll use Fastify. So create an empty dir and initialize npm on it:

mkdir backend
cd backend
npm init -y
npm i fastify

Also create a server.js file with the following contents:

// Require the framework and instantiate it
const fastify = require('fastify')({ logger: true })

// Declare the routes
fastify.get('/getLatLng', async (request, reply) => {
  return { error: 0 }
})

fastify.get('/getHeightMap', async (request, reply) => {
  return { error: 0 }
})

// Run the server!
const start = async () => {
  try {
    await fastify.listen(3000)
  } catch (err) {
    fastify.log.error(err)
    process.exit(1)
  }
}
start()

Now we’ll add the code to each route:

/getLatLng

This method transforms a given address into latitude and longitude, using Google API geocoder. For simplification purposes we are going to use the npm library node-geocoder. So replace the corresponding code above with the following:

fastify.get('/getLatLng', async (request, reply) => {
   const Google_API = 'Your API key here';
   const NodeGeocoder = require('node-geocoder');
   const address = request.params.address;
   const geocoder = NodeGeocoder({ provider:'google', apiKey:Google_API });
   try {
      const res = await geocoder.geocode(address);
      return { error:0, lat:res[0].latitude, lng:res[0].longitude };
   } catch(e) {
      return { error:1, data:e };
   }
}

And install the asociated npm library:

npm i node-geocoder


/getHeightMap

This method will take the following parameters: lat, lng, pixel_size (meters), distance (km), and return a base64 png image. The final resolution will depend on the values of pixel_size and distance given.

At first one should think there was an API for this, but there isn’t one free. So we’ll use some open source npm libraries and an online service called open-elevation.com, which gives us the elevation value (in meters) for a given latitude/longitude.

The steps needed to create this method are the following:

  1. First, by using the npm library geolib (method computeDestinationPoint), we’ll get a bounding box around our given lat/lng values, expanding till the ‘distance’ (km) given value.

  2. Then, we’ll create a mesh-grid of latitudes and longitudes within out bounding box. For this we need to get the number of steps between points within the width of the bounding box. We’ll get this by dividing the distance (multiplied by 1000, because we need everything in meters) between lat/lng top-left and top-right of our bounding box, by the pixel_size value. This will get us a step value which we are going to use next.

  3. To create the mesh-grid, we’ll create 2 loops (one for y(rows), and one for x(columns)), starting from the topLeft lat/lng and calculating the next position to the right, adding the step value distance for each x. For each row (y), the topLeft starting position will be modified to the result of calculating the new position by adding the distance of y*stepvalue, for bearing 180º. This will give us a 2D array of the locations inside our bounding box, as sampled by our pixel_size value.

  4. Here we have 3 alternatives:
    1. Query the elevation for each position in the mesh and create an image using each value for creating a grayscale elevation image. We do this as follow:
      1. For each position in our mesh-grid, we’ll query the elevation value using the free service of open-elevation.com. We are going to format the array as the POST method that open-elevation API requires (just 1D array) and call it one for each row of positions, in parallel. The API is going to return the positions in the same order we gave them. After obtaining this, we’ll search for the lowest value and the max (for determining the max and lowest brightness value for our map). The result will be a 2D array of lat/lng/elevation values. I know this is not the most efficient way, but it serves our purpose and it’s an easy way.
      2. With our elevation 2D array, we’ll create an image/canvas, creating a grayscale color pixel for each value, where the actual color is the distance between the lowest value (of step 4) and the current elevation value (the more value, brighter the point). This will be our final heightmap. After this, we can scale it using a bicubic algo, if we need it to be of bigger resolution.
    2. Get the unique mercator tile coordinates (z,x,y) for each position in the mesh, and build a tile grid, which we can later use to get different map layers. For the heightmap, we can use a dataset within a free AWS S3 bucket (which has being available for the last 3 years), which is in mapbox rgb format. So in essence we just take each tile coordinate, and download each image and stitch them together. After the puzzle is complete we just transform de RGB png to dem (grayscale) using an npm library called terrain-rgb-height, and return the new image.
    3. Do as option 2 but on the frontend side of things, using a lib called mosaic. I’ve found this one doesn’t work well in the server.



To simplify the implementation, and maybe re-use it in something else in the future (maybe from Unity, mapbox or three), I created a node package for the above steps. (alternatives 1 and 2)

Today, we are going to use the tile coordinates alternative, but the library has support for both (feel free to try them).

So, just install the library ..

npm i puntorigen/heightmap

.. and modify the /getHeightmap route code for the following:

fastify.get('/getHeightmap', async (request, reply) => {
   const heightmap = require('heightmap');
   try {
      let map = new heightmap(request.params);
      let tiles = await map.getTiles()
      let base64 = await map.renderTiles(tiles);
      return { error:0, data:base64 };
   } catch(e) {
      return { error:1, data:e };
   }
}

In my next part post, we’ll create the basic UI elements using VUE.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s