- Delulu Tech Share: Mapbox API
With Mapbox, you're not just making maps - you're creating cool stuff that people love. Whether it's an app to explore new places or a tool to help find your way around, Mapbox makes it easy. You can make your maps look exactly how you want, and it's super simple to use. So why not give it a try? Start making maps that stand out and make a difference!
Before setting up our map, we need to register in Mapbox to get access token if you have not.
Click here to navigate to the website. Then, click the sign up button at the upper right corner:
Once you've signed up and verified your email, follow these steps:
- Go to the account home page.
- Scroll down and click on create a token:
- Choose a name for your token and keep the token scope default.
- Scroll down and click create at the end:
- Now you have your access token. Click the button to copy it for later use:
If you haven't already, download and install Node.js, which includes npm.
Open your terminal, copy and paste and run the following line to install Create React App globally:
npm install -g create-react-app
Once you have it downloaded, navigate to the directory you want to create the Map app and run the following code:
npx create-react-app mapbox-app
-
After creating the React app, open the project folder in your code editor. Navigate to
src/index.js
and add the necessary stylesheets and Mapbox GL JS for your map:import React from 'react'; import ReactDOM from 'react-dom'; import 'mapbox-gl/dist/mapbox-gl.css'; // The stylesheet contains the Mapbox GL JS styles to display the map. import './index.css'; import App from './App'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
-
Now navigate to
src/App.js
and add the following (see comments for details):import React, { useRef, useEffect, useState } from 'react'; // To use Mapbox GL with Create React App, you must add an exclamation point to exclude mapbox-gl from transpilation and disable the eslint rule import/no-webpack-loader-syntax import mapboxgl from '!mapbox-gl'; // eslint-disable-line import/no-webpack-loader-syntax // use the access token you gain from registration mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN'; export default function App() { const mapContainer = useRef(null); const map = useRef(null); // The state stores the longitude, latitude, and zoom for the map. These values will all change as your user interacts with the map. const [lng, setLng] = useState(-70.9); const [lat, setLat] = useState(42.35); const [zoom, setZoom] = useState(9); useEffect(() => { if (map.current) return; // initialize map only once map.current = new mapboxgl.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/streets-v12', center: [lng, lat], zoom: zoom }); map.current.on('move', () => { setLng(map.current.getCenter().lng.toFixed(4)); setLat(map.current.getCenter().lat.toFixed(4)); setZoom(map.current.getZoom().toFixed(2)); }); }); return ( <div> <div className="sidebar"> Longitude: {lng} | Latitude: {lat} | Zoom: {zoom} </div> <div ref={mapContainer} className="map-container" /> </div> ); }
-
Then, navigate to
src/index.css
and add the following:.map-container { height: 100vh; /*edit here to change map size*/ } .sidebar { background-color: rgb(35 55 75 / 90%); color: #fff; padding: 6px 12px; font-family: monospace; z-index: 1; position: absolute; top: 0; left: 0; margin: 12px; border-radius: 4px; }
-
Now we are done with implementing the basic map, before we run it, we need to install mapbox-gl if we have not:
npm install mapbox-gl
after done installing, run the program with this line in terminal:
npm start
Navigate to
http://localhost:3000
to see the map:
To change the map style in the implemented map, modify the style
in src/App.js
to use the styles link provided by Mapbox. For instance, to switch to the satellite street style, replace it with the satellite map link:
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/satellite-streets-v12', // change here
center: [lng, lat],
zoom: zoom
});
Here we can see how the map looks like with new style:
Zoom levels control the extent of the world visible on a map. Mapbox offers maps across 23 zoom levels, ranging from 0 (fully zoomed out) to 22 (fully zoomed in).
Here is a reference of what users would see with different zoom levels:
at zoom level | what you can see |
---|---|
0 | The Earth |
3 | A continent |
4 | Large islands |
6 | Large rivers |
10 | Large roads |
15 | Buildings |
We can change the zoom level in src/App.js
:
const [zoom, setZoom] = useState(9); // Replace '9' with your preferred zoom level
The center location on a map typically refers to the geographical coordinates around which the map is centered and displayed to the user.
We can change the map center location by changing the longitude and latitude in src/App.js
:
const [lng, setLng] = useState(-70.9); // replace -70.9 to preferred longitude
const [lat, setLat] = useState(42.35); // replace -42.35 to preferred latitude
We can also set the center location to user's current location by change the following code in src/App.js
:
useEffect(() => {
if (map.current) return;
// Add this function to get current location
const getCurrentLocation = () => {
navigator.geolocation.getCurrentPosition((position) => {
const { longitude, latitude } = position.coords;
setLng(longitude);
setLat(latitude);
map.current.setCenter([longitude, latitude]);
});
};
map.current = new mapboxgl.Map({
container: mapContainer.current,
style: 'mapbox://styles/mapbox/streets-v12',
center: [lng, lat],
zoom: zoom
});
map.current.on('move', () => {
setLng(map.current.getCenter().lng.toFixed(4));
setLat(map.current.getCenter().lat.toFixed(4));
setZoom(map.current.getZoom().toFixed(2));
});
getCurrentLocation();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
The two major geographic data types are raster data and vector data. Raster data are pixel-based images (or say matrices) with geocoordinates, such as satellite imagery. Vector data are a series of coordinates based. The majority types of vector data include point, polyline, and polygon, which are usually represented by the coordinates of points on the map.
First, add a map.current.on('load', () => {});
event to the body of "useEffect" in the App function in src/App.js
. This enables loading and rendering the geographic data on the map when loading.
map.current.on('load', () => {
});
Then, include the map.current.addSource
method in the map.current.on('load', () => {});
. Change the data property if you have a URL of geographic data. The example below is a global earthquake dataset in "point" format.
map.current.addSource('earthquakes', {
type: 'geojson',
data: 'https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson'
});
Here is the dataset example of the earthquake geographic data asset if you open its link:
{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "id": "ak16994521", "mag": 2.3, "time": 1507425650893, "felt": null, "tsunami": 0 }, "geometry": { "type": "Point", "coordinates": [ -151.5129, 63.1016, 0.0 ] } },
{ "type": "Feature", "properties": { "id": "ak16994519", "mag": 1.7, "time": 1507425289659, "felt": null, "tsunami": 0 }, "geometry": { "type": "Point", "coordinates": [ -150.4048, 63.1224, 105.5 ] } },
{ "type": "Feature", "properties": { "id": "ak16994517", "mag": 1.6, "time": 1507424832518, "felt": null, "tsunami": 0 }, "geometry": { "type": "Point", "coordinates": [ -151.3597, 63.0781, 0.0 ] } },
{ "type": "Feature", "properties": { "id": "ci38021336", "mag": 1.42, "time": 1507423898710, "felt": null, "tsunami": 0 }, "geometry": { "type": "Point", "coordinates": [ -118.497, 34.299667, 7.64 ] } },
......
After adding the data to the new layer, you might want to style it using the paint properties for that layer type. The paint properties link provided by MapboxLayerStyle. The "map.current.addLayer" method below is the style settings of the data visualization. This method will also be included in the map.current.on('load', () => {});
.
map.current.addLayer({
'id': 'earthquakes-layer',
'type': 'circle',
'source': 'earthquakes',
'paint': {
'circle-radius': 4,
'circle-stroke-width': 2,
'circle-color': 'red',
'circle-stroke-color': 'white'
}
});
Now, you might want to zoom out to the entire earth and then you can see the visualization of the global earthquake points on the map. Here is a screenshot of the map. Each data point is represented by the red circle.
Replace the corresponding method above with the following two code blocks. This is a user defined GeoJSON data collection with two points. The symbol choices could be adaptive to fit into the dataset.
map.current.addSource('points', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
// feature for Mapbox DC
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [
-77.03238901390978, 38.913188059745586
]
},
'properties': {
'title': 'Mapbox DC'
}
},
{
// feature for Mapbox SF
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-122.414, 37.776]
},
'properties': {
'title': 'Mapbox SF'
}
}
]
}
});
// Add a symbol layer
map.current.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'points',
'layout': {
'icon-image': 'custom-marker',
// get the title name from the source's "title" property
'text-field': ['get', 'title'],
'text-font': [
'Open Sans Semibold',
'Arial Unicode MS Bold'
],
'text-offset': [0, 1.25],
'text-anchor': 'top'
}
});
Mapbox supports self-uploaded raster data and API-based raster data. You can upload raster data to your Mapbox account in the GeoTIFF format. Please check this link for more details about the self-uploaded raster data MapboxRaster. The Mapbox Raster Tiles API also allows you to request tiles from a Mapbox-hosted raster tileset. Here is a example of Raster Tiles API query. It references the Mapbox Satellite tileset ID:
https://api.mapbox.com/v4/mapbox.satellite/1/0/0@2x.jpg90?access_token= <UserAccessToken />
Now that we've developed a comprehensive map, here are additional functionalities we can incorporate.
To retrieve the coordinates of the mouse pointer, you can utilize Mapbox GL JS events.
In src/App.js
, add the following:
map.current.on('mousemove', (e) => {
document.getElementById('info').innerHTML =
// `e.point` is the x, y coordinates of the `mousemove` event
// relative to the top-left corner of the map.
JSON.stringify(e.point) +
'<br />' +
// `e.lngLat` is the longitude, latitude geographical position of the event.
JSON.stringify(e.lngLat.wrap());
});
Then, in the return section of the same file, include the following HTML element to display the coordinates:
<pre id="info"></pre>
This code listens for the mousemove
event on the map and displays the coordinates of the mouse pointer relative to the map container along with the corresponding longitude and latitude.
To show the map scale dynamically, you can use the ScaleControl
provided by Mapbox GL JS.
In src/App.js
, add:
map.current.addControl(new mapboxgl.ScaleControl());
This snippet initializes and adds a scale control, allowing users to visualize the map scale in real-time, typically in metric or imperial units, depending on the map's zoom level.
To add zoom and rotation controls to your Mapbox map, you can utilize the NavigationControl
provided by Mapbox GL JS.
Add the following code snippet to src/App.js
:
map.current.addControl(new mapboxgl.NavigationControl());
These controls empower users to zoom in, zoom out, and rotate the map view, providing an intuitive way to interact with the map and customize their view as needed.
A search bar is a common feature in maps and navigation. Luckily, Mapbox has a complementary search library for both web and React that we will use to implement search for our map. The React version of the library will provide us with a component that we can use to handle search.
-
To start, install the search package for react using npm:
npm install @mapbox/search-js-react
-
Then, we will update our
src/App.js
file by importing theGeocoder
component from the newly installed package:import { Geocoder } from '@mapbox/search-js-react'
-
Next, we will integrate a Geocoder component into the returned HTML. Remember, the accessToken is necessary, and you already have it!
// in src/App.js useEffect(() => { ... map.current = new mapboxgl.Map({ container: mapContainer.current, style: 'mapbox://styles/mapbox/streets-v12', center: [lng, lat], zoom: zoom }); ... }, []); return ( <div> <div className='searchbox'> <form> <Geocoder map={map.current} value='' placeholder='Search Here' accessToken={ mapboxgl.accessToken } /> </form> </div> <div ref={mapContainer} className="map-container" /> </div> );
The Geocoder
component also has other props that can be passed in to set its behavior. For our example, we use the map
prop, which takes a map instance, to center the provided map to the searched location. Both value
and placeholder
act similarly to their uses in HTML's input
and just set placeholder text and values. For the full list of props, click here.
This section contains additional information about implementing popup windows for locations, it allows a user to click on a place and show the details for a certain place or area.
Implementing popup windows for locations requires a substantial amount of code. Due to its complexity, it is impractical to include all the code here. If you are interested in adding this functionality, you can find detailed instructions below:
- Follow the Part 1 tutorial to make a create a dataset and add points.
- You need to customize your own map style and configure your Popup style. Follow the Part 2 tutorial.
- Copy the code below and paste it into your own code of your Map initialization block.
map.current.on('click', (event) => {
// If the user clicked on one of your markers, get its information.
const features = map.current.queryRenderedFeatures(event.point, {
layers: ['YOUR_LAYER_NAME'] // replace with your layer name in part 1
});
if (!features.length) {
return;
}
const feature = features[0];
const popup = new mapboxgl.Popup({ offset: [0, -15] })
.setLngLat(feature.geometry.coordinates)
.setHTML(
`<h3>${feature.properties.title}</h3><p>${feature.properties.description}</p>`
)
.addTo(map.current);
});