Generate and add a missing icon to the map
This example uses addLayer
to add a layer to a style, but it references an image that does not exist in the style's sprite or list of available images. Since the image is missing, the property that references the image throws a styleimagemissing
event.
This example uses that styleimagemissing
event to detect the missing image, then it generates a new image and adds that image to the map style with addImage
within the event listener callback to make sure that an icon is rendered even if the referenced image is missing.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Generate and add a missing icon to the map</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v3.8.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.8.0/mapbox-gl.js"></script>
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
center: [0, 0],
zoom: 2,
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/streets-v12'
});
map.on('styleimagemissing', (e) => {
const id = e.id; // id of the missing image
// Check if this missing icon is
// one this function can generate.
const prefix = 'square-rgb-';
if (!id.includes(prefix)) return;
// Get the color from the id.
const rgb = id.replace(prefix, '').split(',').map(Number);
const width = 64; // The image will be 64 pixels square.
const bytesPerPixel = 4; // Each pixel is represented by 4 bytes: red, green, blue, and alpha.
const data = new Uint8Array(width * width * bytesPerPixel);
for (let x = 0; x < width; x++) {
for (let y = 0; y < width; y++) {
const offset = (y * width + x) * bytesPerPixel;
data[offset + 0] = rgb[0]; // red
data[offset + 1] = rgb[1]; // green
data[offset + 2] = rgb[2]; // blue
data[offset + 3] = 255; // alpha
}
}
map.addImage(id, { width: width, height: width, data: data });
});
map.on('load', () => {
map.addSource('points', {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [0, 0]
},
'properties': {
'color': '255,0,0'
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [50, 0]
},
'properties': {
'color': '255,209,28'
}
},
{
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [-50, 0]
},
'properties': {
'color': '242,127,32'
}
}
]
}
});
map.addLayer({
'id': 'points',
'type': 'symbol',
'source': 'points',
'layout': {
'icon-image': ['concat', 'square-rgb-', ['get', 'color']]
}
});
});
</script>
</body>
</html>
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN
with an access token from your Mapbox account.import React, { useEffect, useRef } from 'react';
import mapboxgl from 'mapbox-gl';
import 'mapbox-gl/dist/mapbox-gl.css';
const MapboxExample = () => {
const mapContainerRef = useRef();
const mapRef = useRef();
useEffect(() => {
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
mapRef.current = new mapboxgl.Map({
container: 'map',
center: [0, 0],
zoom: 2,
style: 'mapbox://styles/mapbox/streets-v12'
});
mapRef.current.on('styleimagemissing', (e) => {
const id = e.id; // id of the missing image
// Check if this missing icon is
// one this function can generate.
const prefix = 'square-rgb-';
if (!id.includes(prefix)) return;
// Get the color from the id.
const rgb = id.replace(prefix, '').split(',').map(Number);
const width = 64; // The image will be 64 pixels square.
const bytesPerPixel = 4; // Each pixel is represented by 4 bytes: red, green, blue, and alpha.
const data = new Uint8Array(width * width * bytesPerPixel);
for (let x = 0; x < width; x++) {
for (let y = 0; y < width; y++) {
const offset = (y * width + x) * bytesPerPixel;
data[offset + 0] = rgb[0]; // red
data[offset + 1] = rgb[1]; // green
data[offset + 2] = rgb[2]; // blue
data[offset + 3] = 255; // alpha
}
}
mapRef.current.addImage(id, { width: width, height: width, data: data });
});
mapRef.current.on('load', () => {
mapRef.current.addSource('points', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [0, 0]
},
properties: {
color: '255,0,0'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [50, 0]
},
properties: {
color: '255,209,28'
}
},
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [-50, 0]
},
properties: {
color: '242,127,32'
}
}
]
}
});
mapRef.current.addLayer({
id: 'points',
type: 'symbol',
source: 'points',
layout: {
'icon-image': ['concat', 'square-rgb-', ['get', 'color']]
}
});
});
}, []);
return <div id="map" ref={mapContainerRef} style={{ height: '100%' }}></div>;
};
export default MapboxExample;
This code snippet will not work as expected until you replace
YOUR_MAPBOX_ACCESS_TOKEN
with an access token from your Mapbox account.Was this example helpful?