Skip to content

Commit

Permalink
Add parameter to fit to integer zoom levels (#1367)
Browse files Browse the repository at this point in the history
* Add parameter to fit to integer zoom levels

* Simplify doc comment

* Add test for controller fit bounds methods
  • Loading branch information
Robbendebiene authored Sep 26, 2022
1 parent 9e7bf7c commit fb3f270
Show file tree
Hide file tree
Showing 3 changed files with 162 additions and 2 deletions.
6 changes: 6 additions & 0 deletions lib/flutter_map.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,18 @@ class FitBoundsOptions {
final double? zoom;
final bool inside;

/// By default calculations will return fractional zoom levels.
/// If this parameter is set to [true] fractional zoom levels will be round
/// to the next suitable integer.
final bool forceIntegerZoomLevel;

const FitBoundsOptions({
this.padding = EdgeInsets.zero,
this.maxZoom = 17.0,
@Deprecated('This property is unused and will be removed in the next major release.')
this.zoom,
this.inside = false,
this.forceIntegerZoomLevel = false,
});
}

Expand Down
13 changes: 11 additions & 2 deletions lib/src/map/flutter_map_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,12 @@ class FlutterMapState extends MapGestureMixin

final paddingTotalXY = paddingTL + paddingBR;

var zoom = getBoundsZoom(bounds, paddingTotalXY, inside: options.inside);
var zoom = getBoundsZoom(
bounds,
paddingTotalXY,
inside: options.inside,
forceIntegerZoomLevel: options.forceIntegerZoomLevel,
);
zoom = math.min(options.maxZoom, zoom);

final paddingOffset = (paddingBR - paddingTL) / 2;
Expand All @@ -524,7 +529,7 @@ class FlutterMapState extends MapGestureMixin
}

double getBoundsZoom(LatLngBounds bounds, CustomPoint<double> padding,
{bool inside = false}) {
{bool inside = false, bool forceIntegerZoomLevel = false}) {
var zoom = this.zoom;
final min = options.minZoom ?? 0.0;
final max = options.maxZoom ?? double.infinity;
Expand All @@ -540,6 +545,10 @@ class FlutterMapState extends MapGestureMixin

zoom = getScaleZoom(scale, zoom);

if (forceIntegerZoomLevel) {
zoom = inside ? zoom.ceilToDouble() : zoom.floorToDouble();
}

return math.max(min, math.min(max, zoom));
}

Expand Down
145 changes: 145 additions & 0 deletions test/flutter_map_controller_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:latlong2/latlong.dart';

void main() {
testWidgets('test fit bounds methods', (tester) async {
final controller = MapController();
final bounds = LatLngBounds(
LatLng(51, 0),
LatLng(52, 1),
);
final expectedCenter = LatLng(51.50274289405741, 0.49999999999999833);

await tester.pumpWidget(TestApp(controller: controller));

{
const fitOptions = FitBoundsOptions();

final expectedBounds = LatLngBounds(
LatLng(51.00145915187144, -0.3079873797085076),
LatLng(52.001427481787005, 1.298485398623206),
);
const expectedZoom = 7.451812751543818;

final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
controller.move(fit.center, fit.zoom);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));

controller.fitBounds(bounds, options: fitOptions);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));
}

{
const fitOptions = FitBoundsOptions(
forceIntegerZoomLevel: true,
);

final expectedBounds = LatLngBounds(
LatLng(50.819818262156545, -0.6042480468750001),
LatLng(52.1874047455997, 1.5930175781250002),
);
const expectedZoom = 7;

final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
controller.move(fit.center, fit.zoom);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));

controller.fitBounds(bounds, options: fitOptions);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));
}

{
const fitOptions = FitBoundsOptions(
inside: true,
);

final expectedBounds = LatLngBounds(
LatLng(51.19148727133182, -6.195044477408375e-13),
LatLng(51.8139520195805, 0.999999999999397),
);
const expectedZoom = 8.135709286104404;

final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
controller.move(fit.center, fit.zoom);
await tester.pump();

expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));

controller.fitBounds(bounds, options: fitOptions);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));
}

{
const fitOptions = FitBoundsOptions(
inside: true,
forceIntegerZoomLevel: true,
);

final expectedBounds = LatLngBounds(
LatLng(51.33232774035881, 0.22521972656250003),
LatLng(51.67425842259517, 0.7745361328125),
);
const expectedZoom = 9;

final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
controller.move(fit.center, fit.zoom);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));

controller.fitBounds(bounds, options: fitOptions);
await tester.pump();
expect(controller.bounds, equals(expectedBounds));
expect(controller.center, equals(expectedCenter));
expect(controller.zoom, equals(expectedZoom));
}
});
}

class TestApp extends StatelessWidget {
final MapController controller;

const TestApp({
required this.controller,
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
// ensure that map is always of the same size
child: SizedBox(
width: 200,
height: 200,
child: FlutterMap(
mapController: controller,
options: MapOptions(),
),
),
),
),
);
}
}

0 comments on commit fb3f270

Please sign in to comment.