Skip to content

Commit fb3f270

Browse files
Add parameter to fit to integer zoom levels (#1367)
* Add parameter to fit to integer zoom levels * Simplify doc comment * Add test for controller fit bounds methods
1 parent 9e7bf7c commit fb3f270

File tree

3 files changed

+162
-2
lines changed

3 files changed

+162
-2
lines changed

lib/flutter_map.dart

+6
Original file line numberDiff line numberDiff line change
@@ -340,12 +340,18 @@ class FitBoundsOptions {
340340
final double? zoom;
341341
final bool inside;
342342

343+
/// By default calculations will return fractional zoom levels.
344+
/// If this parameter is set to [true] fractional zoom levels will be round
345+
/// to the next suitable integer.
346+
final bool forceIntegerZoomLevel;
347+
343348
const FitBoundsOptions({
344349
this.padding = EdgeInsets.zero,
345350
this.maxZoom = 17.0,
346351
@Deprecated('This property is unused and will be removed in the next major release.')
347352
this.zoom,
348353
this.inside = false,
354+
this.forceIntegerZoomLevel = false,
349355
});
350356
}
351357

lib/src/map/flutter_map_state.dart

+11-2
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,12 @@ class FlutterMapState extends MapGestureMixin
510510

511511
final paddingTotalXY = paddingTL + paddingBR;
512512

513-
var zoom = getBoundsZoom(bounds, paddingTotalXY, inside: options.inside);
513+
var zoom = getBoundsZoom(
514+
bounds,
515+
paddingTotalXY,
516+
inside: options.inside,
517+
forceIntegerZoomLevel: options.forceIntegerZoomLevel,
518+
);
514519
zoom = math.min(options.maxZoom, zoom);
515520

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

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

541546
zoom = getScaleZoom(scale, zoom);
542547

548+
if (forceIntegerZoomLevel) {
549+
zoom = inside ? zoom.ceilToDouble() : zoom.floorToDouble();
550+
}
551+
543552
return math.max(min, math.min(max, zoom));
544553
}
545554

test/flutter_map_controller_test.dart

+145
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_map/flutter_map.dart';
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:latlong2/latlong.dart';
5+
6+
void main() {
7+
testWidgets('test fit bounds methods', (tester) async {
8+
final controller = MapController();
9+
final bounds = LatLngBounds(
10+
LatLng(51, 0),
11+
LatLng(52, 1),
12+
);
13+
final expectedCenter = LatLng(51.50274289405741, 0.49999999999999833);
14+
15+
await tester.pumpWidget(TestApp(controller: controller));
16+
17+
{
18+
const fitOptions = FitBoundsOptions();
19+
20+
final expectedBounds = LatLngBounds(
21+
LatLng(51.00145915187144, -0.3079873797085076),
22+
LatLng(52.001427481787005, 1.298485398623206),
23+
);
24+
const expectedZoom = 7.451812751543818;
25+
26+
final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
27+
controller.move(fit.center, fit.zoom);
28+
await tester.pump();
29+
expect(controller.bounds, equals(expectedBounds));
30+
expect(controller.center, equals(expectedCenter));
31+
expect(controller.zoom, equals(expectedZoom));
32+
33+
controller.fitBounds(bounds, options: fitOptions);
34+
await tester.pump();
35+
expect(controller.bounds, equals(expectedBounds));
36+
expect(controller.center, equals(expectedCenter));
37+
expect(controller.zoom, equals(expectedZoom));
38+
}
39+
40+
{
41+
const fitOptions = FitBoundsOptions(
42+
forceIntegerZoomLevel: true,
43+
);
44+
45+
final expectedBounds = LatLngBounds(
46+
LatLng(50.819818262156545, -0.6042480468750001),
47+
LatLng(52.1874047455997, 1.5930175781250002),
48+
);
49+
const expectedZoom = 7;
50+
51+
final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
52+
controller.move(fit.center, fit.zoom);
53+
await tester.pump();
54+
expect(controller.bounds, equals(expectedBounds));
55+
expect(controller.center, equals(expectedCenter));
56+
expect(controller.zoom, equals(expectedZoom));
57+
58+
controller.fitBounds(bounds, options: fitOptions);
59+
await tester.pump();
60+
expect(controller.bounds, equals(expectedBounds));
61+
expect(controller.center, equals(expectedCenter));
62+
expect(controller.zoom, equals(expectedZoom));
63+
}
64+
65+
{
66+
const fitOptions = FitBoundsOptions(
67+
inside: true,
68+
);
69+
70+
final expectedBounds = LatLngBounds(
71+
LatLng(51.19148727133182, -6.195044477408375e-13),
72+
LatLng(51.8139520195805, 0.999999999999397),
73+
);
74+
const expectedZoom = 8.135709286104404;
75+
76+
final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
77+
controller.move(fit.center, fit.zoom);
78+
await tester.pump();
79+
80+
expect(controller.bounds, equals(expectedBounds));
81+
expect(controller.center, equals(expectedCenter));
82+
expect(controller.zoom, equals(expectedZoom));
83+
84+
controller.fitBounds(bounds, options: fitOptions);
85+
await tester.pump();
86+
expect(controller.bounds, equals(expectedBounds));
87+
expect(controller.center, equals(expectedCenter));
88+
expect(controller.zoom, equals(expectedZoom));
89+
}
90+
91+
{
92+
const fitOptions = FitBoundsOptions(
93+
inside: true,
94+
forceIntegerZoomLevel: true,
95+
);
96+
97+
final expectedBounds = LatLngBounds(
98+
LatLng(51.33232774035881, 0.22521972656250003),
99+
LatLng(51.67425842259517, 0.7745361328125),
100+
);
101+
const expectedZoom = 9;
102+
103+
final fit = controller.centerZoomFitBounds(bounds, options: fitOptions);
104+
controller.move(fit.center, fit.zoom);
105+
await tester.pump();
106+
expect(controller.bounds, equals(expectedBounds));
107+
expect(controller.center, equals(expectedCenter));
108+
expect(controller.zoom, equals(expectedZoom));
109+
110+
controller.fitBounds(bounds, options: fitOptions);
111+
await tester.pump();
112+
expect(controller.bounds, equals(expectedBounds));
113+
expect(controller.center, equals(expectedCenter));
114+
expect(controller.zoom, equals(expectedZoom));
115+
}
116+
});
117+
}
118+
119+
class TestApp extends StatelessWidget {
120+
final MapController controller;
121+
122+
const TestApp({
123+
required this.controller,
124+
Key? key,
125+
}) : super(key: key);
126+
127+
@override
128+
Widget build(BuildContext context) {
129+
return MaterialApp(
130+
home: Scaffold(
131+
body: Center(
132+
// ensure that map is always of the same size
133+
child: SizedBox(
134+
width: 200,
135+
height: 200,
136+
child: FlutterMap(
137+
mapController: controller,
138+
options: MapOptions(),
139+
),
140+
),
141+
),
142+
),
143+
);
144+
}
145+
}

0 commit comments

Comments
 (0)