Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rendering Leaflet canvas after pan / zoom

I'm trying to display geoJSON data on Leaflet, using the path generator from d3 and React. With my following code, I'm able to generate a path that matchs the map correctly, see attached image.

working display

Everything is fine ! But, I soon as I pan or zoom within Leaflet, the drawing disappears ... The canvas html element is still here, and even correctly transformed to its new position but there is no more drawings.

Indeed, it appears only after the pan is completed. Thus, as long as I don't release my mouse click, the drawing is here and even correctly moved along the map. But eventually when the pan is finished, it disappears.

Any idea on this ? :)

Thanks for your help.

US data : https://bost.ocks.org/mike/leaflet/us-states.json

import React, { useEffect, useState } from 'react'
import 'leaflet/dist/leaflet.css'
import * as d3 from 'd3'
var L = null

const source = require('./data/states-us.geojson')


const AppCanvas = () => {
    const [mapElement, setMap] = useState(null)

    useEffect(() => {
        L = require('leaflet')
        // Map creation
        if (!mapElement) {
            const origin = [37.8, -96.9]
            const initialZoom = 4

            const map = L.map('map')
                .setView(origin, initialZoom)
                .addLayer(
                    new L.TileLayer(
                        'http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png'
                    )
                )

            map.whenReady(() => {
                // Create canvas element to the correct size
                L.canvas().addTo(map)
                const canvas = d3.select('#map').select('canvas')

                const projection = d3.geoTransform({
                    point: function (x, y) {
                        const point = map.latLngToLayerPoint(new L.LatLng(y, x))
                        this.stream.point(point.x, point.y) // this : NO ARROW FUNCTION
                    },
                })
                const context = canvas.node().getContext('2d')

                // Path generator
                const path = d3
                    .geoPath()
                    .projection(projection)
                    .context(context)

                d3.json(source).then((data) => {
                    context.beginPath()
                    path(data)
                    context.stroke()
                })

                setMap(map)
            })
        }
    })

    return (
        <div>
            <div
                style={{
                    position: 'absolute',
                    top: 0,
                    bottom: 0,
                    left: 0,
                    right: 0,
                    zIndex: 0,
                }}
                id="map"
            ></div>
        </div>
    )
}

Canvas to follow the map and be rerender every pan / zoom end.

like image 536
Alexandre Palo Avatar asked Oct 28 '25 14:10

Alexandre Palo


1 Answers

I've found a solution : re-render the path every times the 'update' event is trigger on the Leaflet canvas layer.

const canvasLayer = L.canvas().addTo(map)

canvasLayer.on('update', (e) => {
    context.save()
    context.clearRect(
        0,
        0,
        map.getSize().x,
        map.getSize().y
    )
    context.beginPath()
    path(data)
    context.stroke()
    context.restore()
})

Maybe it's not the best solution regarding performance, but I least it's visually great. The canvas is ALWAYS rendered and ALWAYS correctly positioned on the map. You can find examples on the internet where the canvas is just rendered again after the the map 'zoom' event, resulting in a visually removed / added canvas for the user. This is not the case here.

I've tested out a canvas with 350 polygons / 5000 points and no performance issue, everything is smooth.

Canvas layer example

like image 81
Alexandre Palo Avatar answered Oct 31 '25 11:10

Alexandre Palo