Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions lib/src/gestures/map_interactive_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -647,10 +647,13 @@ class MapInteractiveViewerState extends State<MapInteractiveViewer>
var newZoom = _camera.zoom;

// Handle pinch zoom.
if (hasPinchZoom && details.scale > 0.0) {
// After a gesture-race win, _scaleCorrector is 1 - lastScale, so this
// sum can be <= 0 even when details.scale itself is positive.
final scale = details.scale + _scaleCorrector;
if (hasPinchZoom && scale > 0.0) {
newZoom = _getZoomForScale(
_mapZoomStart,
details.scale + _scaleCorrector,
scale,
);

// Handle starting of pinch zoom.
Expand Down Expand Up @@ -1401,10 +1404,19 @@ class MapInteractiveViewerState extends State<MapInteractiveViewer>

// Utilities

/// Converts a pinch [scale] into a zoom level starting from [startZoom].
///
/// Returns [startZoom] when [scale] is not a positive finite number.
/// `math.log` is undefined for those inputs and would produce `NaN` or
/// `-Infinity`, which then poison the camera.
@visibleForTesting
static double zoomForScale(double startZoom, double scale) {
if (scale <= 0.0 || !scale.isFinite) return startZoom;
return scale == 1.0 ? startZoom : startZoom + math.log(scale) / math.ln2;
}

double _getZoomForScale(double startZoom, double scale) {
final resultZoom =
scale == 1.0 ? startZoom : startZoom + math.log(scale) / math.ln2;
return _camera.clampZoom(resultZoom);
return _camera.clampZoom(zoomForScale(startZoom, scale));
}

Offset _rotateOffset(Offset offset) {
Expand Down
38 changes: 38 additions & 0 deletions test/gestures/map_interactive_viewer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,42 @@ void main() {
},
);
});

group('MapInteractiveViewerState.zoomForScale', () {
test('adds log2(scale) to the start zoom', () {
expect(MapInteractiveViewerState.zoomForScale(13, 2), 14);
expect(MapInteractiveViewerState.zoomForScale(13, 1), 13);
expect(MapInteractiveViewerState.zoomForScale(13, 0.5), 12);
});

test(
'returns the start zoom when the corrected pinch scale is not '
'positive (regression: details.scale + _scaleCorrector can be '
'<= 0 after a gesture-race win, and math.log of that is NaN - '
'https://github.com/fleaflet/flutter_map/issues/2237)',
() {
// _scaleCorrector = 1.0 - _lastScale, with _lastScale == 2.5
const scaleCorrector = 1.0 - 2.5;
const detailsScale = 1;
final scale = detailsScale + scaleCorrector;

expect(scale, lessThanOrEqualTo(0));

final zoom = MapInteractiveViewerState.zoomForScale(13, scale);

expect(zoom, 13);
expect(zoom.isFinite, isTrue);
expect(zoom.isNaN, isFalse);
},
);

test('returns the start zoom for zero, NaN, and infinite scale', () {
expect(MapInteractiveViewerState.zoomForScale(13, 0), 13);
expect(MapInteractiveViewerState.zoomForScale(13, double.nan), 13);
expect(
MapInteractiveViewerState.zoomForScale(13, double.negativeInfinity),
13,
);
});
});
}