Creating an app for mountain biking using Mapbox in React
I decided to start a project about two years ago I wanted to create a map for mountain biking in my local area. During my youth, I used to bike and walk on trails. I created an app that shows me parks with mountain biking trails. I used Mapbox again since I wanted to learn more about it after using it for a hackathon in 2020. It was my first time using GIS and I enjoyed it.
Finding the dataset
I used a state dataset, I found several sites with datasets. As a note, this will depend on the website and the state you decided to use for this project. Alternatively, you can use national data that covers the entire country rather than a specific state. I downloaded the JSON file to use.
Getting started with Mapbox API
First, I created a Mapbox account and an access token. After that, I went to their docs and followed the tutorials in these order. I created a custom style and had custom markers I made in Inkscape, but this is optional.
- Create a Mapbox account and create an access token
- Load data for the map
- Create a custom map style (optional)
- Add interactivity
To create the map there are two main steps, load the data and add interactivity.
Getting the basics done with JavaScript and React
To start with the application I created a test example using vanilla javascript. Mapbox has a few tutorials on how to use the Mapbox API. After testing the demo using regular javascript I decided to move on creating the react app. Mapbox's tutorial for React is a good start.
Hide access token
As good practice, hide your access token from being public. I created a .env file to store the Mapbox access token and placed it in the .gitignore.
App.js
import React, { useRef, useEffect} from 'react';
import ReactDOM from "react-dom"
import mapboxgl from "mapbox-gl"; // eslint-disable-line import/no-webpack-loader-syntax
import "./App.css"
mapboxgl.accessToken = process.env.REACT_APP_MAPBOX_ACCESS_TOKEN;
const App = () => {
const mapContainerRef = useRef();
// initialize map when component mounts
useEffect(() => {
const map = new mapboxgl.Map({
container: mapContainerRef.current,
style: 'style or custom style',
center: [lng, lat],
zoom: zoom
})
// add navigation control (the +/- zoom buttons)
map.addControl(new mapboxgl.NavigationControl(), 'bottom-right');
// load GeoJSON data
var geojson = {
//load your dataset
}
//load in the data using arrow functions
map.on("load", () => {
// adding the layer
map.addLayer({
'id': 'map id',
'type': 'symbol',
'source': 'locations',
'layout': {
'icon-image': '{icon}',
'icon-allow-overlap': true
},
})
})
// add markers to map
geojson.features.forEach(function(marker) {
// create a HTML element for each feature
var el = document.createElement('div');
el.className = 'marker';
// make a marker for each feature and add to the map
new mapboxgl.Marker(el)
.setLngLat(marker.geometry.coordinates)
.setPopup(
new mapboxgl.Popup({ offset: 25 }) // add popups
.setText(
marker.properties.Title +
' | ' +
marker.properties.Address +
' | ' +
marker.properties.City +
' | ' +
marker.properties.ZIPCode
)
)
.addTo(map);
});
// cleanup function to remove map on unmount
return () => map.remove()
}, [])
return <div ref={mapContainerRef} style={{ width: "100%", height: "100vh" }} />;
};
export default App;
App.css
.App {
text-align: center;
}
.map-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
.marker {
background-image: url('customIcon.svg');
background-size: cover;
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
}
.mapboxgl-popup {
max-width: 200px;
}
.mapboxgl-popup-content {
text-align: center;
font-family: 'Open Sans', sans-serif;
}