Skip to main content

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.
Was this example helpful?