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:
- Get the SVG string (inline or fetched)
- Create a
Bloband object URL from the SVG - Draw it onto a
<canvas>via anImageelement - Extract
ImageDataand register withmap.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.