Building a 3D React Map Component Using Mapbox
Published on Jan 20, 2021 by Ben Tyler
This post is part of my free Building Interactive Maps with React course - a course for anyone wanting to learn how to build interactive maps and integrate them into their React applications. If you enjoy this guide, then chances are you will enjoy the course too!
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.
# yarnyarn add mapbox-gl# npmnpm install mapbox-gl --save
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.
<link href="https://api.mapbox.com/mapbox-gl-js/v2.0.0/mapbox-gl.css" rel="stylesheet"/>
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.
import React, { useRef, useEffect } from "react"import mapboxgl from "mapbox-gl"// Grab the access token from your Mapbox account// I typically like to store sensitive things like this// in a .env filemapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKENexport const Map = () => { const mapContainer = useRef() // this is where all of our map logic is going to live // adding the empty dependency array ensures that the map // is only created once useEffect(() => { // create the map and configure it // check out the API reference for more options // https://docs.mapbox.com/mapbox-gl-js/api/map/ const map = new mapboxgl.Map({ container: "map", style: "mapbox://styles/mapbox/satellite-streets-v11", center: [-119.99959421984575, 38.619551620333496], zoom: 14, }) }, []) return ( <div id="map" ref={mapContainer} style={{ width: "100%", height: "100vh" }} /> )}
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.
import React, { useRef, useEffect } from "react"import mapboxgl from "mapbox-gl"// Grab the access token from your Mapbox account// I typically like to store sensitive things like this// in a .env filemapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKENexport const Map = () => { const mapContainer = useRef() // this is where all of our map logic is going to live // adding the empty dependency array ensures that the map // is only created once useEffect(() => { // create the map and configure it // check out the API reference for more options // https://docs.mapbox.com/mapbox-gl-js/api/map/ const map = new mapboxgl.Map({ container: "map", style: "mapbox://styles/mapbox/satellite-streets-v11", center: [-119.99959421984575, 38.619551620333496], zoom: 14, pitch: 60, }) map.on("load", () => { map.addSource("mapbox-dem", { type: "raster-dem", url: "mapbox://mapbox.mapbox-terrain-dem-v1", tileSize: 512, maxZoom: 16, }) map.setTerrain({ source: "mapbox-dem", exaggeration: 1.5 }) map.addLayer({ id: "sky", type: "sky", paint: { "sky-type": "atmosphere", "sky-atmosphere-sun": [0.0, 90.0], "sky-atmosphere-sun-intensity": 15, }, }) }) }, []) return ( <div id="map" ref={mapContainer} style={{ width: "100%", height: "100vh" }} /> )}
If you found thus post useful, give me a follow on Twitter or consider picking up a copy of the Building Interactive Maps with React course.
About Me
Howdy, I am Ben Tyler. I am a product-focused engineer who cares deeply about finding the simplest solution to a user's problem.
I love collaborating on projects, so if you need a hand, please schedule a call or get in touch at hello@lostcreekdesigns.co!
Schedule a Call!