Repository navigation
Description
During the timeline below, it was hinted at in #2208 by @maheshj01, then investigated by @mootw, that NaN was being produced internally by the following method in MapCamera:
flutter_map/lib/src/map/camera/camera.dart
Lines 297 to 298 in 35eb283
| double getZoomScale(double toZoom, double fromZoom) => | |
| crs.scale(toZoom) / crs.scale(fromZoom); |
It appears that when this method is called with identical arguments, the result is NaN? However, for some reason, before #2182, this did not cause issues. This needs to be investigated.
The method itself also needs to be investigated - why is it the shape that it is? See comments in #2199 for more information. Does it make more sense for the method to return 1 instead of NaN or 0 when the inputs are the same?
Internal link to start of discussion for @fleaflet/maintainers only: https://discord.com/channels/951867686378409984/951879268227485707/1521558461819916458
Timeline:
LatLngwithdouble.naninMarker/MarkerLayertriggers memory leak #2178 was opened, reporting thatMarkers with NaN points caused memory leaks and crashes- fix: throw when
LatLngis non-finite #2182 was opened and merged (resolvingLatLngwithdouble.naninMarker/MarkerLayertriggers memory leak #2178), which introduced a check for NaN during all projections fromLatLngs - this aimed to uncover the memory leak and throw instead, which was deemed an acceptable solution - Many interactions throw "LatLng is not finite" error #2199 was opened, reporting that the new check triggered unexpectedly during many typical and normal interactions and configurations
- fix: LatLng is not finite exception #2208 was opened, which fixed some cases of the NaN being produced internally, partially fixing Many interactions throw "LatLng is not finite" error #2199
- perf: cache zoom-independent marker projections in
MarkerLayer#2213 was merged, which (in addition to unrelated issues) introduced a check for NaN on theMarkerLayeronly, double fixingLatLngwithdouble.naninMarker/MarkerLayertriggers memory leak #2178 with a tighter scope - revert: fix: throw when
LatLngis non-finite #2218 was opened and merged, to revert fix: throw whenLatLngis non-finite #2182, close fix: LatLng is not finite exception #2208, and fix Many interactions throw "LatLng is not finite" error #2199 - this would've reopenedLatLngwithdouble.naninMarker/MarkerLayertriggers memory leak #2178, but perf: cache zoom-independent marker projections inMarkerLayer#2213 fixed it anyway
Activity
aki1770-del commentedon Jul 3, 2026
Hi @JaffaKetchup — took a look since this is squarely in an area I work in (a snow-driving nav stack on flutter_map).
Root cause:
getZoomScale(to, from)iscrs.scale(to) / crs.scale(from). The defaultEpsg3857extendsCrsWithStaticTransformation, which doesn't overridescale(), so it uses the baseCrs.scale=256 * math.pow(2, zoom). For normal zooms that's a finite number, sogetZoomScale(z, z)is a clean1.0— the common case is fine. It only degenerates to NaN whenscale(z)itself is0,±∞, orNaN(i.e.x/xwith a degeneratex). Reproduced against the current code:On your suggestion (return
1when the inputs are the same): it fixes the finite-extreme case and also catches±infinity(sinceinf == infistrue) — but it misses the NaN case, becauseNaN == NaNisfalsein Dart, so theto == fromguard is skipped and the NaN falls through. A complete guard would also short-circuit non-finite inputs (if (!toZoom.isFinite || !fromZoom.isFinite) return 1;).That said, the more interesting question is where a NaN/extreme zoom comes from —
getZoomScale(z, z)can't be NaN for a normalz(it's always1.0), so the NaN-during-interaction reports (e.g. #2199) suggest a NaN or runaway zoom is entering the camera upstream, probably from gesture/animation math, andgetZoomScaleis just where it first surfaces. A guard here stops the crash; tracing the upstream NaN would stop it at the source.Happy to put up a small PR with the guard + a regression test (I've got a local repro covering the identical-args finite/∞/NaN cases) if that'd be useful — or leave it to you. Whatever's easiest.