-
Notifications
You must be signed in to change notification settings - Fork 527
Description
Environment
- leaflet: ^1.9.4
- leaflet.heat: ^0.2.0
- Vue 3 + @vue-leaflet/vue-leaflet with
:use-global-leaflet="false"(recommended for SSR/tree-shaking)
Summary
When the map is created by a different Leaflet instance than the one leaflet.heat uses (e.g. vue-leaflet with useGlobalLeaflet: false bundles its own Leaflet), calling L.heatLayer(...).addTo(map) leads to:
Cannot read properties of undefined (reading 'x')
in _redraw → bounds.contains(p).
Root cause
-
Two Leaflet instances
- Instance A: used by the map (e.g. vue-leaflet’s internal Leaflet).
- Instance B: the
Lwe set onwindowand use to load leaflet.heat and callL.heatLayer(...).
-
In HeatLayer.js
_redraw(around lines 126–146):boundsis built with Instance B’sL.BoundsandL.point.p = this._map.latLngToContainerPoint(...)returns a Point from Instance A (because the map is from Instance A).bounds.contains(p)is Instance B’sBounds.contains(p).
-
In Leaflet’s Bounds.contains (e.g.
leaflet/src/geometry/Bounds.js):- The branch is chosen by
obj instanceof Point(Point from Instance B). - Instance A’s Point is not
instanceofInstance B’s Point, so the code treatsobjas bounds and doesobj = toBounds(obj). toBounds(Point from A)in Instance B builds aBoundsby iterating withpoints.length; a Point has nolength, so the loop never runs and the Bounds ends up withmin/maxundefined.- Then
containsdoesmin = obj.min(undefined) and accessesmin.x→ "Cannot read properties of undefined (reading 'x')".
- The branch is chosen by
So the failure is a cross-instance issue: heat uses one Leaflet, the map (and thus latLngToContainerPoint) uses another; instanceof Point and toBounds assume a single Leaflet world.
Steps to reproduce
- Vue 3 app with vue-leaflet and
:use-global-leaflet="false". - In the component:
import L from 'leaflet', setwindow.L = L, thenawait import('leaflet.heat'). - Get the map instance from vue-leaflet (its internal Leaflet).
- Run:
L.heatLayer(latlngs, { radius: 25, blur: 15 }).addTo(map). - Open the browser console → the error appears when the heat layer redraws.
Repro repository & live demo:
- Repository: https://github.com/daishu0000/leaflet-heat-vue-repro
- Live demo: https://daishu0000.github.io/leaflet-heat-vue-repro/
Minimal repro (Vue 3 + Vite):
<template>
<l-map
ref="mapRef"
:zoom="5"
:center="[35, 110]"
:use-global-leaflet="false"
@ready="onMapReady"
>
<l-tile-layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
layer-type="base"
attribution="© OpenStreetMap"
/>
</l-map>
</template>
<script setup>
import { ref } from 'vue'
import { LMap, LTileLayer } from '@vue-leaflet/vue-leaflet'
import L from 'leaflet'
const mapRef = ref(null)
async function onMapReady(mapInstance) {
const map = mapInstance ?? mapRef.value?.leafletObject?.value ?? mapRef.value?.leafletObject
if (!map) return
if (typeof window !== 'undefined') window.L = L
await import('leaflet.heat')
const heatData = [
[30, 120, 1], [31, 121, 2], [32, 122, 3],
[35, 110, 2], [36, 115, 1],
]
L.heatLayer(heatData, { radius: 25, blur: 15 }).addTo(map)
}
</script>Suggested direction for a fix
Make heat layer robust when the map’s Leaflet is a different instance:
- Option A: In
_redraw, normalize the container point so it’s a “plain” point before callingbounds.contains(p)(e.g. usep.x/p.yto build a point in the sameLheat uses, or use a duck-typing check like'x' in p && 'y' in pand then compare coordinates instead of relying onBounds.containsandinstanceof Point). - Option B: Use the map’s
L(e.g. fromthis._mapor from the map’s constructor) for creatingBoundsand forcontains, so thatlatLngToContainerPointandbounds.containsshare the same Leaflet instance. That might require leaflet.heat to accept an explicitLor to detect it from the map.
I can open a PR toward Option A (normalizing the point before contains) if that aligns with the maintainers’ preference.
Expected behavior
When the map comes from another Leaflet instance (e.g. vue-leaflet with useGlobalLeaflet: false), the heat layer should still redraw without throwing; either by normalizing points by coordinates or by using the map’s Leaflet for bounds/point checks.