Skip to content

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:

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

PropertyTypeDescription
southwestLatLngBottom-left corner of the bounding box
northeastLatLngTop-right corner of the bounding box

Next Steps


Tip: Always add some padding (40–80 pixels) when fitting bounds so markers at the edges are not clipped by the screen border.