fix: prevent RangeError in Proj4Crs.zoom for out-of-range scales#2209
fix: prevent RangeError in Proj4Crs.zoom for out-of-range scales#2209AlexLaroche wants to merge 2 commits into
Proj4Crs.zoom for out-of-range scales#2209Conversation
When the requested scale is larger than the largest entry in the CRS's `_scales`, `_closestElement` returns the last element, so `downZoom` is `_scales.length - 1`. Falling through to the interpolation block then indexes `_scales[downZoom + 1]` and throws `RangeError`. Return `double.infinity` in that case, mirroring the `double.negativeInfinity` already returned when the requested scale is smaller than every defined level. Callers such as `FitBounds._getBoundsZoom` then clamp via `.clamp(min, max)` and the camera ends up at `maxZoom`, instead of the whole frame throwing. Closes fleaflet#1223.
|
@JaffaKetchup: Could you take a look when you have time? Please. |
|
I'll be back next week, so hopefully in the next few weeks I'll be back on top of things. Apologies it's taken so long! |
|
Hey, I think this looks good, huge thanks :) Just a question, how (if at all) does this relate to #1358? |
Unsure that the PR has actually resolved the issue
JaffaKetchup
left a comment
There was a problem hiding this comment.
Just taking another check over this. Could you please post the code you are using to reproduce this issue?
I'm using the MRE found in #1223, migrated to the latest version, except with a different tile layer since it's not important to the reproduction anyway.
Code sample
final _epsg2039 = Proj4Crs.fromFactory(
code: 'EPSG:2039',
proj4Projection: proj4.Projection.add('EPSG:2039',
'+proj=tmerc +lat_0=31.73439361111111 +lon_0=35.20451694444445 +k=1.0000067 +x_0=219529.584 +y_0=626907.39 +ellps=GRS80 +towgs84=-48,55,52,0,0,0,0 +units=m +no_defs'),
resolutions: <double>[
793.751587503175,
264.583862501058,
132.291931250529,
66.1459656252646,
26.4583862501058,
13.2291931250529,
6.61459656252646,
2.64583862501058,
1.32291931250529,
0.661459656252646,
0.330729828126323
],
origins: [Point(-5403700, 7116700)],
);
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: const MenuDrawer(HomePage.route),
body: Stack(
children: [
FlutterMap(
options: MapOptions(
crs: _epsg2039,
initialCameraFit: CameraFit.bounds(
bounds: LatLngBounds(
LatLng(31.699685, 34.936118),
LatLng(31.697378, 34.932516),
),
maxZoom: 10),
minZoom: 0,
maxZoom: 10,
),
children: [openStreetMapTileLayer], // or any tile layer
),
],
),
);
}That code still doesn't run, and it's for the same reason discussed in that issue. This PR has changed the zoom method, but the scale method still throws. Can you confirm you can reproduce this? If so, I would not consider the issue fixed.
Summary
Fixes a
RangeErrorthrown byProj4Crs.zoomwhen the requested scale is finer than the deepest level defined in the CRS'sresolutionslist. Closes #1223 (which has documented this exact crash since 2022).Root cause
In
lib/src/geo/crs.dart:When the caller asks for a scale beyond the largest defined level,
_closestElementreturns_scales.last, sodownZoomis the last index. The interpolation block then indexes_scales[downZoom + 1]and throws.The most common path that hits this is
FitBounds._getBoundsZoom, which callscamera.getScaleZoom(scale)before clamping the result tomaxZoom(camera_fit.dart:162 vs :176). So settingmaxZoomonMapOptions/CameraFit.boundsdoes not protect against the crash — it crashes insideProj4Crs.zoombefore the clamp can run. This is also why issue #1223's reproduction isfitBoundswith a custom CRS.Fix
When
downZoom == _scales.length - 1and the requested scale is strictly larger thandownScale, returndouble.infinityinstead of falling through to the out-of-bounds index access.This mirrors the existing lower-bound behavior in the same function: when the requested scale is smaller than every entry in
_scales, the function already returnsdouble.negativeInfinity. The fix makes the upper bound symmetric.Callers like
FitBounds._getBoundsZoomalready.clamp(min, max)the returned zoom, so+∞becomesmaxZoomand the camera settles correctly. The whole frame no longer throws.Why
double.infinityrather than clamping to_scales.length - 1?double.negativeInfinityfor the lower bound.FitBoundsalready has its ownmaxZoomthat may be lower than the deepest CRS level.Happy to switch to a finite clamp if reviewers prefer.
Test plan
test/geo/crs_test.dartcovers exact match, interpolation, lower-bound-∞, and upper-bound+∞.+∞test reproduces the originalRangeErroratcrs.dart:356:30when the fix is reverted.flutter test test/geo/ test/flutter_map_test.dart— all 20 tests pass.flutter analyze lib/src/geo/crs.dart test/geo/crs_test.dart— no issues.dart formatclean on changed files.