Fit to Bounding Box in Flutter
This tutorial shows how to adjust the map camera to fit a set of coordinates or a bounding box within the visible viewport.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
Basic Fit to Bounds
Fit the camera to show a specific rectangular area:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class FitBoundsScreen extends StatefulWidget {
@override
_FitBoundsScreenState createState() => _FitBoundsScreenState();
}
class _FitBoundsScreenState extends State<FitBoundsScreen> {
MapMetricsController? mapController;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Fit to Bounds')),
body: Column(
children: [
// Region buttons
SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.all(8),
child: Row(
children: [
_regionButton('Paris', LatLng(48.815, 2.225), LatLng(48.902, 2.470)),
SizedBox(width: 8),
_regionButton('Manhattan', LatLng(40.700, -74.020), LatLng(40.800, -73.930)),
SizedBox(width: 8),
_regionButton('Central London', LatLng(51.490, -0.180), LatLng(51.530, -0.070)),
SizedBox(width: 8),
_regionButton('Tokyo Center', LatLng(35.650, 139.700), LatLng(35.700, 139.780)),
],
),
),
// Map
Expanded(
child: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) => mapController = controller,
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 5.0,
),
),
),
],
),
);
}
Widget _regionButton(String label, LatLng southwest, LatLng northeast) {
return ElevatedButton(
onPressed: () => _fitToBounds(southwest, northeast),
child: Text(label),
);
}
void _fitToBounds(LatLng southwest, LatLng northeast) {
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(southwest: southwest, northeast: northeast),
50.0, // padding in pixels
),
);
}
}Fit to Markers
Automatically calculate bounds from a set of markers and zoom to show them all:
dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class FitToMarkersScreen extends StatefulWidget {
@override
_FitToMarkersScreenState createState() => _FitToMarkersScreenState();
}
class _FitToMarkersScreenState extends State<FitToMarkersScreen> {
MapMetricsController? mapController;
final List<LatLng> markerPositions = [
LatLng(48.8584, 2.2945), // Eiffel Tower
LatLng(48.8606, 2.3376), // Louvre
LatLng(48.8530, 2.3499), // Notre-Dame
LatLng(48.8867, 2.3431), // Sacré-Cœur
LatLng(48.8738, 2.2950), // Arc de Triomphe
];
Set<Marker> get markers => markerPositions.asMap().entries.map((entry) {
return Marker(
markerId: MarkerId('marker_${entry.key}'),
position: entry.value,
infoWindow: InfoWindow(title: 'Point ${entry.key + 1}'),
);
}).toSet();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Fit to Markers')),
body: MapMetrics(
styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
onMapCreated: (controller) {
mapController = controller;
// Fit to all markers after map loads
_fitToAllMarkers();
},
initialCameraPosition: CameraPosition(
target: LatLng(48.8566, 2.3522),
zoom: 10.0,
),
markers: markers,
),
floatingActionButton: FloatingActionButton(
onPressed: _fitToAllMarkers,
child: Icon(Icons.fit_screen),
tooltip: 'Fit All Markers',
),
);
}
void _fitToAllMarkers() {
if (markerPositions.isEmpty) return;
// Calculate bounds from all marker positions
double minLat = markerPositions.first.latitude;
double maxLat = markerPositions.first.latitude;
double minLng = markerPositions.first.longitude;
double maxLng = markerPositions.first.longitude;
for (final position in markerPositions) {
if (position.latitude < minLat) minLat = position.latitude;
if (position.latitude > maxLat) maxLat = position.latitude;
if (position.longitude < minLng) minLng = position.longitude;
if (position.longitude > maxLng) maxLng = position.longitude;
}
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: LatLng(minLat, minLng),
northeast: LatLng(maxLat, maxLng),
),
60.0, // padding
),
);
}
}Fit Bounds with Custom Padding
Use different padding on each side:
dart
// Uniform padding
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(bounds, 50.0),
);
// If your SDK version supports asymmetric padding:
mapController?.animateCamera(
CameraUpdate.newLatLngBounds(
bounds,
50.0, // This is applied equally on all sides
),
);LatLngBounds Properties
| Property | Type | Description |
|---|---|---|
southwest | LatLng | Bottom-left corner of the bounding box |
northeast | LatLng | Top-right corner of the bounding box |
Next Steps
- Restrict Map Panning — Prevent users from panning outside bounds
- Fly to a Location — Animate camera to a single point
- Jump to Locations — Navigate through a series of locations
Tip: Always add some padding (40–80 pixels) when fitting bounds so markers at the edges are not clipped by the screen border.