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

A Complete Guide to Sources and Layers in React and Mapbox GL JS

Date: 2/22/2021

Time to Read: 6 minutes

Sources and Layers Infographic

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!

The previous posts in the series have covered

  • how to use Mapbox Studio to manage spatial data and create custom base maps (read post)
  • how to create a basic application using Mapbox and React (read post).

These posts are helpful in understanding the basics of Mapbox Studio and the relation between Studio and Mapbox GL JS. Understanding these fundamentals is essential when you start developing much larger, data-driven mapping applications.

The aim of this post is to provide an introduction to adding a variety of spatial data formats to a React application using Mapbox GL JS. Understanding how to add sources and layers to a map will open a lot of doors for the types of applications you can build using Mapbox GL JS.

If you do not care too much about the explanations and are just looking for a snippet, check out the Code Sandbox for this guide or scroll to the bottom of the post.

Deciding Between a Custom Style and Custom Code

I covered how to manage spatial data in Mapbox Studio using Datasets and Tilesets as well as how to add custom layers to a Mapbox style in earlier posts. If your spatial data is static and will not need to respond much to user inputs in your application, adding the spatial data to a custom Mapbox Style and then using that style in your application is probably the most ideal workflow.

However, if your application and map are fairly data-driven, this guide should be very relevant to your workflow. Here are a few common examples of when it is probably easier to bring spatial data into your map and application using Mapbox GL JS versus a custom style in Mapbox Studio. There are ways to accommodate these use cases using Mapbox Studio, but I just find it easier to manage them entirely using Mapbox GL JS.

  • the map needs to display data that updates frequently
    • i.e. a delivery map that shows near real-time status and position of drivers
  • the map needs to use data from a third party API
  • the map needs the ability to style and filter layer features based on user input

What We Will Build

Completed Interactive Map

We will use Mapbox GL JS and React to build an interactive map with several custom sources and layers. We will be adding sources and layers for

  • avalanche slide paths
  • nearby weather stations
  • bus routes
  • 3D terrain
  • the sky

The next sections will provide an overview of Sources and Layers followed by some concrete usage examples.

If you do not care too much about the explanations and are just looking for a snippet, check out the Code Sandbox for this guide.

Sources

I like to think of a Source as a mini datastore for my map. It tells Mapbox where to find my data as well as how to represent it. There are multiple types of Sources that you can use including: vector, raster, raster-dem, geojson, image, and video. This provides a lot of flexibility in terms of what kind of data can be added to a Mapbox GL JS application.

Each source type has their own configuration options, but you can generally do things like set the min and max zoom thresholds for a source. The Mapbox Style Specification provides a comprehensive summary of each type. For this guide though, we will be focused on the vector and geojson source types.

Adding a Vector Source

https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#vector

Probably the most common way of adding spatial data to a map is adding a vector tile source hosted by Mapbox. Sidenote: If you are not overly familiar with the difference between vector and raster data in the context of GIS, check out this helpful guide from Carto.

You can add one of Mapbox's tilesets or add your own custom tileset that is hosted on Mapbox. See this earlier post for instructions on how to create your own tileset.

1
// adding a Mapbox tileset
2
// method expects you to provide an id for the source
3
// as well some configuration options
4
map.addSource("mapbox-streets", {
5
type: "vector",
6
url: "mapbox://mapbox.mapbox-streets-v8",
7
})
8
9
// adding your own tileset
10
map.addSource("avalanche-paths", {
11
type: "vector",
12
url: "mapbox://lcdesigns.arckuvnm",
13
})
14

Adding a GeoJSON Source

https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#geojson

This method is great for adding in spatial data from third party APIs or pulling in data from your own APIs. You can define the GeoJSON inline, read GeoJSON directly from a local file, or hit an API endpoint that returns GeoJSON.

1
// inline geojson
2
// method expects you to provide an id for the source
3
// as well some configuration options
4
map.addSource("mapbox-streets", {
5
type: "geojson",
6
data: {
7
"type": "Feature",
8
"geometry": {
9
"type": "Polygon",
10
"coordinates": [
11
[
12
[-67.13734351262877, 45.137451890638886],
13
[-66.96466, 44.8097],
14
[-68.03252, 44.3252],
15
[-69.06, 43.98],
16
[-70.11617, 43.68405],
17
[-70.64573401557249, 43.090083319667144],
18
[-70.75102474636725, 43.08003225358635],
19
[-70.79761105007827, 43.21973948828747],
20
[-70.98176001655037, 43.36789581966826],
21
[-70.94416541205806, 43.46633942318431],
22
[-71.08482, 45.3052400000002],
23
[-70.6600225491012, 45.46022288673396],
24
[-70.30495378282376, 45.914794623389355],
25
[-70.00014034695016, 46.69317088478567],
26
[-69.23708614772835, 47.44777598732787],
27
[-68.90478084987546, 47.184794623394396],
28
[-68.23430497910454, 47.35462921812177],
29
[-67.79035274928509, 47.066248887716995],
30
[-67.79141211614706, 45.702585354182816],
31
[-67.13734351262877, 45.137451890638886]
32
]
33
]
34
}
35
});
36
37
// adding GeoJSON read from a file
38
import ExampleData from "./ExampleData.json";
39
map.addSource("avalanche-paths", {
40
type: "geojson",
41
data: ExampleData,
42
});
43
44
// adding GeoJSON from an API
45
import ExampleData from "./ExampleData.json";
46
map.addSource("avalanche-paths", {
47
type: "geojson",
48
data: "https://opendata.arcgis.com/datasets/4347f3565fbe4d5dbb97b016768b8907_0.geojson",
49
});
50

Layers

Layers are the visual representation of a source's data, they are what actually get rendered on the map. Once you add a source to a map, you can create any number of layers using it. For instance, if I added a source that contained city parks, I could create the following three layers from that single source.

  • a fill layer that represents the park boundaries as shaded polygons
  • a line layer that represents the boundaries as an outline
  • a symbol layer that displays the park names as text labels

Mapbox supports a lot of different layer types including background, fill, line, symbol, raster, circle, fill-extrusion, heatmap, hillshade, and sky. It is beyond the scope of this guide to cover all of these layer types, but this guide will focus on the what you will be most likely to use, fill, line, symbol,and circle.

A note about layers is that they are rendered in the order they are defined. So if you want a layer to be below another one of your layers, make sure you add it first. Alternatively though, you can tell Mapbox which layer a layer should be added before/after. See this guide to learn how to do so.

Each layer is created in a similar fashion, but has its own unique set of layout and paint properties (aka how it looks) that can be configured. It is unfortunately beyond the scope of this guide to cover all of these configuration options, but the Mapbox docs do a great job. For a deeper dive into layers, check out the Mapbox Style Specification.

Adding a Fill Layer

https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#fill

Fill layers will be your go to for visualizing polygons on a map. Think use cases like boundaries, census tracts, bodies of water, avalanche paths, building footprints, etc. The general syntax for adding a layer is more or less the same regardless of layer type. The major differences between layer types is in the layout and paint configuration options (i.e how the layer is presented and styled).

1
// add a fill layer to the map
2
map.addLayer({
3
id: "avalanche-paths-fill",
4
type: "fill",
5
source: "avalanche-paths",
6
"source-layer": "Utah_Avalanche_Paths-9s9ups",
7
paint: {
8
"fill-opacity": 0.5,
9
"fill-color": "#f05c5c",
10
},
11
})
12

Adding a Circle Layer

https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#circle

Circle layers are useful any time you want to visualize point data. A symbol layer can also be used to visualize point data but the simplicity of the circle layer type can be nice, especially if you want do things like data-driven styling.

1
// add a circle layer to the map
2
map.addLayer({
3
id: "snotel-sites-circle",
4
type: "circle",
5
source: "snotel-sites",
6
paint: {
7
"circle-color": "#ffff00",
8
"circle-radius": 8,
9
"circle-stroke-color": "#333333",
10
"circle-stroke-width": 2,
11
},
12
})
13

Adding a Line Layer

https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#line

Line layers are your best friend anytime you want to visualize a line string, think use cases like bus routes, Lyft routes, hiking tracks, rivers and streams, etc.

1
// add a line layer
2
map.addLayer({
3
id: "bus-routes-line",
4
type: "line",
5
source: "bus-routes",
6
paint: {
7
"line-color": "#15cc09",
8
"line-width": 4,
9
},
10
})
11

Adding a Symbol Layer

Symbol layers are the ones that took me the longest to get my head around. There are two primary use cases for symbol layers: 1) if you want to visualize data using an icon and 2) if you want to label map features with some text.

You can see all of the icons that are available for use as a symbol layer over at this page. Just hover over any of the icons to see the name (i.e. airfield-15). You can also create and upload your own icons, but that will likely be the topic of another post.

Adding a label layer is relatively straightforward too and you can use any of the properties (fields) in your data source as labels. In the example below, there is a field called "Station Name" that I am using to label features. I am using a Mapbox Expression (["get", "Station Name"]) to grab the values from the Station Name field.

1
// add a symbol layer - icon
2
map.addLayer({
3
id: "bus-stops-symbol",
4
type: "symbol",
5
source: "bus-stops",
6
layout: {
7
icon-image: 'bus-15',
8
}
9
});
10
11
// add a symbol layer - text label
12
map.addLayer({
13
id: "snotel-sites-label",
14
type: "symbol",
15
source: "snotel-sites",
16
layout: {
17
"text-field": ["get", "Station Name"],
18
"text-size": 14,
19
"text-offset": [0, -1.5],
20
},
21
paint: {
22
"text-color": "#ffff00",
23
"text-halo-color": "#333333",
24
"text-halo-width": 1,
25
},
26
});
27

Adding Sources and Layers to a React Map

With all of that foundation established (a lot of it!), the following steps should hopefully make a bit more sense. In this section, we are going to use these specific methods from Mapbox GL JS to add sources and layers to an interactive map in a React application.

Process Overview

Regardless of what type of spatial data you are adding to your application, there will always be two key components:

  • Adding a source
  • Adding a layer

Adding the source tells Mapbox that "hey, this is a data store that contains or more layers that could get added to the map". When you add a layer to a map, you then point it at the source and tell Mapbox how to represent the source on the map.

If you want to follow along outside of this post, you can check the Code Sandbox or the Github repo.

Process Implementation

The rest of the guide is going to pick up where my earlier Introduction to Mapbox and React post left off. I have put together a working snippet below filled with comments. I started out trying to explain every last bit of what was happening but think it is a lot more apparent in a lot of ways if I let the code speak for itself. I have provided links to the relevant Mapbox docs which do a much better job of explaining than I ever could. You can also refer to the primer above on sources and layers.

1
import React, { useRef, useEffect } from "react"
2
import mapboxgl from "mapbox-gl"
3
import SnotelSites from "./lcc_snotel_sites.json"
4
// import the mapbox styles
5
// alternatively can use a link tag in the head of public/index.html
6
// see https://docs.mapbox.com/mapbox-gl-js/api/
7
import "mapbox-gl/dist/mapbox-gl.css"
8
import "./app.css"
9
10
// Grab the access token from your Mapbox account
11
// I typically like to store sensitive things like this
12
// in a .env file
13
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_TOKEN
14
15
const App = () => {
16
const mapContainer = useRef()
17
18
// this is where all of our map logic is going to live
19
// adding the empty dependency array ensures that the map
20
// is only rendered once
21
useEffect(() => {
22
// create the map and configure it
23
// check out the API reference for more options
24
// https://docs.mapbox.com/mapbox-gl-js/api/map/
25
const map = new mapboxgl.Map({
26
container: mapContainer.current,
27
style: "mapbox://styles/mapbox/outdoors-v11",
28
center: [-111.75, 40.581],
29
zoom: 12,
30
pitch: 60,
31
bearing: 80,
32
})
33
34
// only want to work with the map after it has fully loaded
35
// if you try to add sources and layers before the map has loaded
36
// things will not work properly
37
map.on("load", () => {
38
// add mapbox terrain dem source for 3d terrain rendering
39
map.addSource("mapbox-dem", {
40
type: "raster-dem",
41
url: "mapbox://mapbox.mapbox-terrain-dem-v1",
42
tileSize: 512,
43
maxZoom: 16,
44
})
45
map.setTerrain({ source: "mapbox-dem" })
46
47
// avalanche paths source
48
// example of how to add a custom tileset hosted on Mapbox
49
// you can grab the url from the details page for any tileset
50
// you have created in Mapbox studio
51
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#vector
52
map.addSource("avalanche-paths", {
53
type: "vector",
54
url: "mapbox://lcdesigns.arckuvnm",
55
})
56
57
// snotel sites source
58
// example of using a geojson source
59
// data is hosted locally as part of the application
60
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#geojson
61
map.addSource("snotel-sites", {
62
type: "geojson",
63
data: SnotelSites,
64
})
65
66
// bus routes source
67
// another example of using a geojson source
68
// this time we are hitting an ESRI API that returns
69
// data in the geojson format
70
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/#geojson
71
map.addSource("bus-routes", {
72
type: "geojson",
73
data:
74
"https://opendata.arcgis.com/datasets/4347f3565fbe4d5dbb97b016768b8907_0.geojson",
75
})
76
77
// avalanche paths - fill layer
78
// source-layer can be grabbed from the tileset details page
79
// in Mapbox studio
80
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#fill
81
map.addLayer({
82
id: "avalanche-paths-fill",
83
type: "fill",
84
source: "avalanche-paths",
85
"source-layer": "Utah_Avalanche_Paths-9s9ups",
86
paint: {
87
"fill-opacity": 0.5,
88
"fill-color": "#f05c5c",
89
},
90
})
91
92
// snotel sites - circle layer
93
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#circle
94
map.addLayer({
95
id: "snotel-sites-circle",
96
type: "circle",
97
source: "snotel-sites",
98
paint: {
99
"circle-color": "#1d1485",
100
"circle-radius": 8,
101
"circle-stroke-color": "#ffffff",
102
"circle-stroke-width": 2,
103
},
104
})
105
106
// snotel sites - label layer
107
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#symbol
108
map.addLayer({
109
id: "snotel-sites-label",
110
type: "symbol",
111
source: "snotel-sites",
112
layout: {
113
"text-field": ["get", "Station Name"],
114
"text-size": 16,
115
"text-offset": [0, -1.5],
116
},
117
paint: {
118
"text-color": "#1d1485",
119
"text-halo-color": "#ffffff",
120
"text-halo-width": 0.5,
121
},
122
})
123
124
// bus routes - line layer
125
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#line
126
map.addLayer({
127
id: "bus-routes-line",
128
type: "line",
129
source: "bus-routes",
130
paint: {
131
"line-color": "#15cc09",
132
"line-width": 4,
133
},
134
})
135
136
// add a sky layer
137
// the sky layer is a custom mapbox layer type
138
// see https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#sky
139
map.addLayer({
140
id: "sky",
141
type: "sky",
142
paint: {
143
"sky-type": "atmosphere",
144
"sky-atmosphere-sun": [0.0, 90.0],
145
"sky-atmosphere-sun-intensity": 15,
146
},
147
})
148
})
149
150
// cleanup function to remove map on unmount
151
return () => map.remove()
152
}, [])
153
154
return <div ref={mapContainer} style={{ width: "100%", height: "100vh" }} />
155
}
156
157
export default App
158

Next Steps

This guide just scratches the surface in terms of the types of sources and layers that can be added to a map using Mapbox GL JS. I encourage you to explore the Mapbox docs and extend my examples. You could try things like...

  • tweaking and expanding the layer styling
  • adding your own sources and layers

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.

Useful Links and Resources

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