Skip to content

Display a Remote SVG Symbol ​

Fetch an SVG from a remote URL, render it onto a canvas using an Image element, and register it with map.addImage() for use in symbol layers.

Approach: SVG → Blob → Image → Canvas → addImage ​

SVG images cannot be passed directly to map.addImage(). The recommended workflow is:

  1. Get the SVG string (inline or fetched)
  2. Create a Blob and object URL from the SVG
  3. Draw it onto a <canvas> via an Image element
  4. Extract ImageData and register with map.addImage()
javascript
function svgToImageData(svgString, size) {
  return new Promise((resolve, reject) => {
    const blob = new Blob([svgString], { type: 'image/svg+xml' });
    const url = URL.createObjectURL(blob);
    const img = new Image(size, size);
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = size;
      canvas.height = size;
      canvas.getContext('2d').drawImage(img, 0, 0, size, size);
      URL.revokeObjectURL(url);
      resolve(canvas.getContext('2d').getImageData(0, 0, size, size));
    };
    img.onerror = reject;
    img.src = url;
  });
}

Fetch SVG from a Remote URL ​

javascript
async function loadRemoteSvg(url, size) {
  const response = await fetch(url);
  const svgText = await response.text();
  return svgToImageData(svgText, size);
}

map.on('load', async () => {
  const imageData = await loadRemoteSvg('https://example.com/pin.svg', 48);
  map.addImage('remote-svg', imageData);

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

Using an Inline SVG String ​

javascript
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#3b82f6">
  <circle cx="12" cy="12" r="10"/>
  <circle cx="12" cy="12" r="4" fill="white"/>
</svg>`;

svgToImageData(svg, 48).then(imageData => {
  map.addImage('dot-icon', imageData);
});

CORS Note ​

When fetching SVGs from external servers, the server must send Access-Control-Allow-Origin: *. If CORS is blocked, use an inline SVG string or a data URI instead.

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 svgToImageData(svgString, size) {
        return new Promise((resolve) => {
          const blob = new Blob([svgString], { type: 'image/svg+xml' });
          const url = URL.createObjectURL(blob);
          const img = new Image(size, size);
          img.onload = () => {
            const canvas = document.createElement('canvas');
            canvas.width = size; canvas.height = size;
            canvas.getContext('2d').drawImage(img, 0, 0, size, size);
            URL.revokeObjectURL(url);
            resolve(canvas.getContext('2d').getImageData(0, 0, size, size));
          };
          img.src = url;
        });
      }

      const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ef4444">
        <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
      </svg>`;

      map.on('load', async () => {
        const imageData = await svgToImageData(svg, 48);
        map.addImage('pin', imageData);

        map.addSource('pts', {
          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: 'pins',
          type: 'symbol',
          source: 'pts',
          layout: { 'icon-image': 'pin', 'icon-size': 1, 'icon-allow-overlap': true, 'icon-anchor': 'bottom' }
        });
      });
    </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 svgToImageData(svgString, size) {
  return new Promise((resolve) => {
    const blob = new Blob([svgString], { type: 'image/svg+xml' });
    const url = URL.createObjectURL(blob);
    const img = new Image(size, size);
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = size; canvas.height = size;
      canvas.getContext('2d').drawImage(img, 0, 0, size, size);
      URL.revokeObjectURL(url);
      resolve(canvas.getContext('2d').getImageData(0, 0, size, size));
    };
    img.src = url;
  });
}

const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#ef4444">
  <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>`;

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 DisplayRemoteSvgSymbol = () => {
  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', async () => {
      const imageData = await svgToImageData(svg, 48);
      map.current.addImage('pin', imageData);
      map.current.addSource('pts', { type: 'geojson', data: pointsData });
      map.current.addLayer({
        id: 'pins',
        type: 'symbol',
        source: 'pts',
        layout: { 'icon-image': 'pin', 'icon-size': 1, 'icon-allow-overlap': true, 'icon-anchor': 'bottom' }
      });
    });

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

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

export default DisplayRemoteSvgSymbol;

For more information, visit the MapMetrics GitHub repository.