vrijdag 29 oktober 2021

Leaflet merged custom markers

It is possible to have custom markers in Leaflet. Those markers will be dynamically created by your Angular/Typescript or javascript code.

The marker is created using a canvas object. All the images are drawn in the canvas. That canvas is turned into base64. Why base64? Because it is good to cache the created markers, so you can reuse them. A nice way to cache a marker is turning it into a normal string using base64. That string can be used when creating your Leaflet L.Icon() object. Just put it in the iconUrl attribute. Shadows on your markers also look professional. You can easily configure them when creating a marker with L.icon().

let sources = [];  // will contain all properties about the images

// load a .png from the webserver. Position and width and height is supplied.
sources.push({ src: '/assets/img/icon.png', x: 0, y: 0, dx: 20, dy: 20 });

// or load a svg
let svgStr = '<svg> .... </svg>';
let svgDataStr = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponentsvgStr 
sources.push({ src: svgDataStr, x: 0, y: 0, dx: 20, dy: 20 });

const images = sources.map(source => new Promise((resolve, reject) => {
    const img = new Image();
    img.onerror = () => {
        reject(new Error('Couldn\'t load image'));
    };
    img.onload = () => {
        source["image"] = img;
        resolve(source);
    };
    img.src = source.src;
}));

let mergedData;
await Promise.all(images).then(images => {
    let canvas = document.createElement('canvas');
    canvas.width = 40;
    canvas.height = 40;
    const context = canvas.getContext('2d');

    images.forEach(image => {
        context.globalAlpha = 0.65; // a bit transparent
        context.drawImage(image["image"], (image["x"] || 0) + offsetX, (image["y"] || 0) + offsetY, image["dx"], image["dy"]);
    });
    mergedData = canvas.toDataURL('image/png', 0.92);
});

const icon = L.icon({
    iconUrl: mergedData,
    shadowUrl: ...
});
leafletMarker.setIcon(icon);