I like to write almost as much as I like to build. I write about dashboards, maps, freelancing, music, and the outdoors. If this interests you too, please join my mailing list to get updates whenever I publish new content.

Subscribe

Building a 3D React Map Component Using Mapbox

Date: 1/20/2021

Time to Read: 2 minutes

Mapbox released GL JS V2 recently which has a whole slew of awesome features, but the one I am most excited is the addition of 3D terrain rendering. I have been waiting for this feature for a looooooooong time. A lot of the applications I build are focused on the outdoors and feature an interactive map. Just about every one of these apps would benefit greatly from the ability to render things in 3D. That is a now a reality!

The best part about the new release is just how easy it is to render 3D terrain. This post will walk you through how to create a React Map component with 3D terrain rendering. The process is more or less exactly what you would follow for creating any other Mapbox Gl JS map in React.

Before You Get Started

This guide assumes the following:

  • you are adding this component to an existing React app. If you do not already have an app to add this too or are unsure of how to setup a React app, check out the React docs.
  • you already have a Mapbox account and access token. You can sign up here

Installing Mapbox

To get started, let's install Mapbox.

1
2
# yarn
3
yarn add mapbox-gl
4
5
# npm
6
npm install mapbox-gl --save
7
8

Then make sure you include the GL JS CSS file in the <head> of your html document. If you are using Create React App or a similarly structured app, add it to the <head> of the index.html file in the public directory.

index.html
1
<link
2
href="https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.css"
3
rel="stylesheet"
4
/>
5

Developing the Map Component

The next several steps will walk you through how to create a dead simple Map component with 3D rendering enabled. Create a new component called Map and then copy and paste the snippet below. This will render a simple interactive map.

Map.js
1
import React, { useRef, useEffect } from "react"
2
import mapboxgl from "mapbox-gl"
3
4
// Grab the access token from your Mapbox account
5
// I typically like to store sensitive things like this
6
// in a .env file
7
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN
8
9
export const Map = () => {
10
const mapContainer = useRef()
11
12
// this is where all of our map logic is going to live
13
// adding the empty dependency array ensures that the map
14
// is only created once
15
useEffect(() => {
16
// create the map and configure it
17
// check out the API reference for more options
18
// https://docs.mapbox.com/mapbox-gl-js/api/map/
19
const map = new mapboxgl.Map({
20
container: "map",
21
style: "mapbox://styles/mapbox/satellite-streets-v11",
22
center: [-119.99959421984575, 38.619551620333496],
23
zoom: 14,
24
})
25
}, [])
26
27
return (
28
<div
29
id="map"
30
ref={mapContainer}
31
style={{ width: "100%", height: "100vh" }}
32
/>
33
)
34
}
35

Taking It All 3D

At this point you should have a basic 2D satellite streets map rendering successfully. Converting this rendering to 3D is a surprisingly small amount of work. We need to do the following:

  • adjust the map pitch (aka the camera angle) so that we are not looking straight down at the map
  • add the Mapbox DEM (digital elevation model) source to our map

First, we add the pitch property to the map configuration. This value can be between 0 and 85. For this example, I personally prefer 60. And then lastly, we need to add a load event listener and define the logic for adding Mapbox's DEM tiles, generating the 3D terrain, and adding a sky layer for a nice touch.

That's it! If you revisit your app, you should not have a 3D rendering that resembles what you would see in Google Earth.

Map.js
1
import React, { useRef, useEffect } from "react"
2
import mapboxgl from "mapbox-gl"
3
4
// Grab the access token from your Mapbox account
5
// I typically like to store sensitive things like this
6
// in a .env file
7
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN
8
9
export const Map = () => {
10
const mapContainer = useRef()
11
12
// this is where all of our map logic is going to live
13
// adding the empty dependency array ensures that the map
14
// is only created once
15
useEffect(() => {
16
// create the map and configure it
17
// check out the API reference for more options
18
// https://docs.mapbox.com/mapbox-gl-js/api/map/
19
const map = new mapboxgl.Map({
20
container: "map",
21
style: "mapbox://styles/mapbox/satellite-streets-v11",
22
center: [-119.99959421984575, 38.619551620333496],
23
zoom: 14,
24
pitch: 60,
25
})
26
27
map.on("load", () => {
28
map.addSource("mapbox-dem", {
29
type: "raster-dem",
30
url: "mapbox://mapbox.mapbox-terrain-dem-v1",
31
tileSize: 512,
32
maxZoom: 16,
33
})
34
map.setTerrain({ source: "mapbox-dem", exaggeration: 1.5 })
35
map.addLayer({
36
id: "sky",
37
type: "sky",
38
paint: {
39
"sky-type": "atmosphere",
40
"sky-atmosphere-sun": [0.0, 90.0],
41
"sky-atmosphere-sun-intensity": 15,
42
},
43
})
44
})
45
}, [])
46
47
return (
48
<div
49
id="map"
50
ref={mapContainer}
51
style={{ width: "100%", height: "100vh" }}
52
/>
53
)
54
}
55

Join the Newsletter

I periodically send out a newsletter. Interested? Sign up below. You can unsubscribe at any time.

Interested in working together?

Drop me a line
© 2021 Lost Creek Designs