Skip to content

Add a Generated Icon to the Map

Create a custom icon programmatically using the Canvas 2D API and register it with map.addImage().

Generate an Icon with Canvas API

javascript
function generateIcon(color) {
  const size = 48;
  const canvas = document.createElement('canvas');
  canvas.width = size;
  canvas.height = size;
  const ctx = canvas.getContext('2d');

  // Draw a circle
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
  ctx.fill();

  // White border
  ctx.strokeStyle = '#ffffff';
  ctx.lineWidth = 3;
  ctx.stroke();

  return ctx.getImageData(0, 0, size, size);
}

Register and Use the Icon

javascript
map.on('load', () => {
  const iconData = generateIcon('#3b82f6');

  // Register with map
  map.addImage('generated-icon', iconData);

  // Use in a symbol layer
  map.addLayer({
    id: 'icons',
    type: 'symbol',
    source: 'my-source',
    layout: {
      'icon-image': 'generated-icon',
      'icon-size': 1.0,
      'icon-allow-overlap': true,
    }
  });
});

Switch Icon at Runtime

javascript
// Update which image is used for the layer
map.setLayoutProperty('icons', 'icon-image', 'new-icon-name');

Complete Example

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
    <script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
    <style>#map { height: 500px; width: 100%; }</style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      const map = new mapmetricsgl.Map({
        container: 'map',
        style: '<StyleFile_URL_with_Token>',
        center: [10, 30],
        zoom: 1.5
      });

      function makeCircleIcon(color, size = 40) {
        const canvas = document.createElement('canvas');
        canvas.width = size; canvas.height = size;
        const ctx = canvas.getContext('2d');
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = '#fff';
        ctx.lineWidth = 3;
        ctx.stroke();
        return ctx.getImageData(0, 0, size, size);
      }

      map.on('load', () => {
        map.addImage('blue-dot', makeCircleIcon('#3b82f6'));
        map.addImage('red-dot', makeCircleIcon('#ef4444'));

        map.addSource('points', {
          type: 'geojson',
          data: {
            type: 'FeatureCollection',
            features: [
              { type: 'Feature', geometry: { type: 'Point', coordinates: [-74, 40.7] }, properties: {} },
              { type: 'Feature', geometry: { type: 'Point', coordinates: [2.35, 48.85] }, properties: {} },
            ]
          }
        });

        map.addLayer({
          id: 'icons',
          type: 'symbol',
          source: 'points',
          layout: { 'icon-image': 'blue-dot', 'icon-size': 0.8, 'icon-allow-overlap': true }
        });
      });
    </script>
  </body>
</html>
jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';

function makeCircleIcon(color, size = 40) {
  const canvas = document.createElement('canvas');
  canvas.width = size; canvas.height = size;
  const ctx = canvas.getContext('2d');
  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(size / 2, size / 2, size / 2 - 2, 0, Math.PI * 2);
  ctx.fill();
  ctx.strokeStyle = '#fff';
  ctx.lineWidth = 3;
  ctx.stroke();
  return ctx.getImageData(0, 0, size, size);
}

const pointsData = {
  type: 'FeatureCollection',
  features: [
    { type: 'Feature', geometry: { type: 'Point', coordinates: [-74, 40.7] }, properties: {} },
    { type: 'Feature', geometry: { type: 'Point', coordinates: [2.35, 48.85] }, properties: {} },
    { type: 'Feature', geometry: { type: 'Point', coordinates: [139.7, 35.7] }, properties: {} },
  ]
};

const AddGeneratedIcon = () => {
  const mapContainer = useRef(null);
  const map = useRef(null);

  useEffect(() => {
    if (map.current) return;
    map.current = new mapmetricsgl.Map({
      container: mapContainer.current,
      style: '<StyleFile_URL_with_Token>',
      center: [10, 30],
      zoom: 1.5
    });

    map.current.on('load', () => {
      map.current.addImage('blue-dot', makeCircleIcon('#3b82f6'));
      map.current.addSource('points', { type: 'geojson', data: pointsData });
      map.current.addLayer({
        id: 'icons',
        type: 'symbol',
        source: 'points',
        layout: { 'icon-image': 'blue-dot', 'icon-size': 0.8, 'icon-allow-overlap': true }
      });
    });

    return () => { map.current?.remove(); map.current = null; };
  }, []);

  return <div ref={mapContainer} style={{ height: '500px', width: '100%' }} />;
};

export default AddGeneratedIcon;

For more information, visit the MapMetrics GitHub repository.