0

I'm trying to create custom SVG markers using L.svgOverlay in Leaflet. The markers are implemented as SVG <g> elements. They work correctly when I zoom in and out: they stay at the correct geographic positions. However, if I pan (move) the map and then zoom, the markers go insane. I can't figure out what I do wrong.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
        <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
        <style>
            html, body {
                height: 100%;
                margin: 0;
                font-family: Arial;
            }

            #map {
                height: 100vh;
                width: 100vw;
            }

            .villageMarker {
                pointer-events: auto !important;
                cursor: pointer;
            }
        </style>
    </head>
    <body>
        <div id="map"></div>
        </div>
        <script>
            function updateSvgPositionsZoom() {
                const mapBounds = map.getBounds();
                svgOverlay.setBounds(mapBounds);

                const svgOverlayElement = svgOverlay.getElement();

                Array.from(svgOverlayElement.children).forEach(icon => {
                    const i = icon.id.split("_")[1];
                    const coords = coordinates[i];

                    const point = map.latLngToLayerPoint(coords);

                    icon.setAttribute('transform', `translate(${point.x},${point.y})`);
                });
            };

            async function show() {
                for (const i in iconWrappers) {
                    const coords = coordinates[i];
                    const iconWrapper = iconWrappers[i];

                    const circle = document.createElementNS(ns, "circle");
                    circle.setAttribute('r', 10);
                    circle.setAttribute('fill', '#fc43ab');
                    circle.setAttribute('stroke', '#000000');
                    circle.setAttribute('stroke-width', '1');

                    iconWrapper.appendChild(circle)

                    const point = map.latLngToLayerPoint(coords);
                    iconWrapper.setAttribute('transform', `translate(${point.x},${point.y})`);
                };
            };

            async function main() {
                map = L.map('map', {center: [52.38, 9.77], zoom: 6});

                L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                }).addTo(map);

                svg = document.createElementNS(ns, "svg");
                svg.style.pointerEvents = 'none';

                for (const i in coordinates) {
                    const iconWrapper = document.createElementNS(ns, "g");
                    iconWrapper.setAttribute('id', `marker_${i}`);
                    iconWrapper.classList.add('villageMarker');
                    
                    svg.appendChild(iconWrapper);
                    iconWrappers.push(iconWrapper);
                };

                svgOverlay = L.svgOverlay(svg, map.getBounds(), {
                    interactive: true,
                    opacity: 1
                }).addTo(map);

                svgOverlay.getElement().setAttribute('xmlns', ns);

                map.on('zoomend', updateSvgPositionsZoom);

                show();
            };

            let pathname;

            let map;
            let ns = "http://www.w3.org/2000/svg";
            let svgOverlay;
            let svg;

            let coordinates = [
                [52.38, 9.77],
                [51.52, 7.47],
                [53.56, 9.97]
            ]

            let iconWrappers = [];

            main();
        </script>
    </body>
</html>

What am I missing to make markers stable during both panning and zooming? Should I be handling different events or is there a fundamental mistake in my coordinate conversion?

3
  • Use a L.Marker instead of a L.SvgOverlay. Commented Aug 16 at 22:23
  • @IvanSanchez I'm afraid it isn't possible because in my real project there are thousands of markers with complex SVG icons. I can not even place them in Leaflet SVG pane because of how slow Leaflet process any transportation on moving of such a huge amount of elements. I ended with outer SVG container that always copies position on page of similar but empty Leaflet SVG pane. My problem now is that this outer container overlays all Leaflet element (PopUps, zoom buutons, attributions line), not only the map, indifferent to z-index value I install to this outer container. Do you have ideas on it? Commented Sep 16 at 12:23
  • For thousands of markers, I have a very biased opinion and approach: ivansanchez.gitlab.io/leaflet.gleo/demo/sprites.html Commented Sep 16 at 22:48

1 Answer 1

0

problem is that you’re mixing coordinate systems. L.svgOverlay expects you to manage bounds in geographic space, but you’re also positioning elements with latLngToLayerPoint, which is relative to the map pane. That double transform is why markers break when you pan+zoom. The simpler fix is to skip L.svgOverlay and instead use Leaflet’s built-in L.svg() renderer — then you can append <g>/<circle> directly into its <svg> and just update positions on map events. This way the <circle>s always stay pinned to their lat/lng, no matter how you pan or zoom.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.