Excerpt of Italian telephone area codes map

How to Build a Cloud-Hosted Vector Tiles Map Web App in React — Part II

Alessandro Cristofori
The Startup
Published in
7 min readJan 25, 2021

--

App design

In this part of the tutorial we will focus on writing the code of our web app, assuming we already have a React app in development mode (created in the first section), we will first design the server side code and then the mapping part on the client side, the finished code for this section can be found here.

Before proceeding, let’s have a look at the project folder structure to see where we are going to place the files that we will create.

root-folder/
public/
index.html - this is where React will render
src/
client/
components/
App.jsx - React app wrapper
...more client code
index.js - React instantiation / app entry point for Webpack
server/
here we will place our server code
styles/
here we will place our sass code
babel.config.json
package.json
webpack.config.js
Procfile - process file to be run on the environment

Design you server side code

The server of our application will handle requests/responses of the app bundle and the vector tiles using Express. The workflow is based on a Node example you can find here, with Express it will be easier to expand our app in case we wish to include more API endpoints later.

First, let’s install Express and CORS with npm install --save express cors in your terminal.

We then install the dependencies to turn our source data into vector tiles and send them to the client in Protocol Buffer format PBF, if you want to discover more about PBF have a look at this article to start with. Our native GeoJSON data will be tiled with geojson-vt and subsequently serialised to PBF using vt-pbf which wraps the pbf library implementation to work with MapBox vector tiles. The usual terminal command to install these dependencies will be npm install --save geojson-vt vt-pbf.

Finally, the last required dependency is the AWS SDK for JavaScript, serving as the code interface to access datasets in S3 buckets. Install it with npm install --save aws-sdk.

Let’s create a file called index.js in our server folder that will look like follows, if you downloaded the starter pack from git hub this file is already included.

Server side code

For displaying the data stored in the S3 bucked you will have to replace the values between <> with your credentials and Amazon AWS region, we set those up in part one of this tutorial.

The built code will be placed by Webpack in the build folder when making a production bundle. On top of it, we will have a route for serving the vector tiles, the endpoint will be:

http://<domain-name>/tileserver/?z={zoom-level}&x={tile x index}&y={tile y index}.pbf

For those who are not experienced in how tiled data works this is a brief explanation. Our client side mapping library OpenLayers will take care of making the requests to the server, working out z, x and y for us and filling the query strings with these values. When the server starts geojson-vt creates the tileIndex variable, this is in fact a grid made of our sliced GeoJSON, our tiles. Each tile can be located on the grid as it will be assigned an x, y and z value, the first two are the positions of the tile on the respective axis and the third is the zoom level at which the tile will render. When we request the data, geojson-vt calculates which tiles will be displayed from the provided z, x, and y. We will receive the tiles as “chunks” of pbf data that OpenLayers will parse and render on the map at the right place.

To test that everything works as expected navigate to the server folder and from your terminal run node index.js, if our NodeJs application started correctly we should see the message of the app listening on the port, 5000 in our case. The app will not start immediately but only after the data has been tiled and this may take a few seconds.

Design your front end code

Let’s start with creating a simple base map, including OpenLayers in our dependencies, from your terminal do an npm install — save ol. Since we use React we will wrap the OpenLayers code in a functional component, having there both HTML and state management. There are various examples on the web on how to set up OpenLayers in React, I based mine on this because it is simple and well documented. Inside the client folder create a components folder, here we will create a file that will hold our client side logic, we will call it MapComponent.jsx.

Client side code — map component

Notice how working with hooks slightly changes how we instantiate the OpenLayer Map object in a functional component; we call a useEffect hook, followed by an empty array as second argument. This ensures the hook is called only once and when the component will mount. We use the setMap hook after the map instantiation, creating an accessible reference in the state in case we applied changes to the map object later on, using another useEffect hook for example. You can notice that we haven’t yet created the HTML reference node that OpenLayers needs to attach the map to. React has a hook for it, useRef replacing theReact.createRef method used in component classes. Notice the url endpoint to get the vector tiles, in our simple example we will run client and server code from the same server, that’s why we don’t need the specify an absolute url. Besides, notice how the order of the query string parameters (z,x,y) is the same as specified in server/index.js.

If we want to expand our app later it will be good to create a parent that will wrap the map together with more components, this will our App component. After this we need to create our app entry point for both React and Webpack client/index.js, and the the HTML file where the whole application will render public/index.html

App.jsx
import React from 'react';
import MapComponent from './MapComponent';
const title = 'Welcome to Vector Tiles Map';
export default function App () {
return (
<div>
{title}
<MapComponent />
</div>);
}
//index.js
import "../styles/style.scss";
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
ReactDOM.render(
<App />,
document.getElementById('app')
);
//this line is needed for having Hot Module Replacement while in dev mode
module.hot.accept();
//index
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>S3 Vector Tiles</title>
</head>
<body>
<div id="app"></div>
</body>
</html>

And finally we will create a style.scss file inside the styles folder that will contain the sass styles definition for our application, we will define here the OpenLayers map height and width.

//style.scss
body {
font-family: $font;
color: $primary-color;
margin: 0;
.map {
width: 100%;
height: 100vh;
}
}

Run the App in development mode

Now that we have a client and server code base we can serve our app in development mode and see how it looks like. To do this we need the follow these steps:

  1. Install and configure Babel to transpile our React code from jsx to js.
  2. Have Webpack to bundle up our client code.
  3. Have Webpack dev server to run our bundle.
  4. Have Nodemon to run our server code.
  5. Set up Webpack dev server to proxy every request from the client to the server that will run on a different port.

Instructions about how to install and configure Webpack dev server and Babel are not part of this tutorial. You can find a lot of examples online (like this one) or you can clone my example repository from GitHub. In the examples below I will just put the parts of a webpack.config.js and package.json that will change using the files you can find in part one as base. Replace the relevant parts and you will able start your application. Remember you need to install nodemon, from your terminal do an npm install --save-dev nodemon first. Client code (webpack-dev-server) will run on port 3000, server code (express) will run on port 5000.

NOTE for those who work on Windows: I designed this app on Windows and I couldn’t use the && notation to run dev-server and dev-client at the same time as I would do on Linux with npm run dev-server && npm run dev-client. I will need a couple of additional configuration steps, install concurrently, from the terminal npm install --save-dev concurrently, and get the client code to run only when the server is ready to accept requests.

Our server code will connect to S3 and make the vector tiles, by the time this happens our dev server will have started already. If we left it like that the browser would open with an empty base map, since the tiled features would not be ready yet. What we want instead is the front end code to run only when the tiles are ready. In our help comes wait-on, a multiplatform library that will npm run dev-server only when the server (the nodemon process) is ready to listen on port 5000. To install it, from your terminal run npm install — -save -g wait-on and see how to use it in the example below

// webpack.config.js
...
devServer: {
contentBase: path.resolve(__dirname, './build'),
open: true,
hot: true,
port: 3000,
proxy: {
'/tileserver': 'http://localhost:5000'
}
}
...
package.json
...
"scripts"
: {
"test": "npm run test",
"dev-server": "nodemon ./src/server/index.js",
"dev-client": "webpack serve --mode development --config ./webpack.config.js",
"start": "nodemon ./node_modules/concurrently/bin/concurrently.js \"npm:dev-server\" \"npm:dev-client-wait\"",
"build": "webpack --mode production --config ./webpack.config.js",
"dev-client-wait": "wait-on tcp:5000 && npm run dev-client"
},
...

Now it is time to run our application in development mode, navigate to your project root folder and do an npm start, after some time the browser page will open and you should see the OSM base map with our tiled vector layer overlay.

App running in development mode

If this is what you see then, congratulations, you are running your own vector tiled server sourcing data from an S3 bucket.

But we do not want to keep all this good stuff for us, do we? We want to publish our work, share our map ideas or present a technical prototype. This is exactly what we are going to do in the next section.

--

--