View All Posts

Building a 3D React Map Component Using Mapbox

Published on Jan 20, 2021 by Ben Tyler

This post is part of my 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 specialize in developing aesthetic and functional websites and applications. I love collaborating on projects, so if you need to hire a developer or an adviser for a project, please get in touch!

Get in touch!