Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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 with MapController.fitBounds. LngLatBounds takes four scalar fields — longitudeWest, longitudeEast, latitudeSouth, latitudeNorth — rather than a pair of coordinate objects:

dart
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';

class FitBoundsScreen extends StatefulWidget {
  @override
  _FitBoundsScreenState createState() => _FitBoundsScreenState();
}

class _FitBoundsScreenState extends State<FitBoundsScreen> {
  MapController? 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',
                  const LngLatBounds(
                    longitudeWest: 2.225,
                    longitudeEast: 2.470,
                    latitudeSouth: 48.815,
                    latitudeNorth: 48.902,
                  ),
                ),
                SizedBox(width: 8),
                _regionButton(
                  'Manhattan',
                  const LngLatBounds(
                    longitudeWest: -74.020,
                    longitudeEast: -73.930,
                    latitudeSouth: 40.700,
                    latitudeNorth: 40.800,
                  ),
                ),
                SizedBox(width: 8),
                _regionButton(
                  'Central London',
                  const LngLatBounds(
                    longitudeWest: -0.180,
                    longitudeEast: -0.070,
                    latitudeSouth: 51.490,
                    latitudeNorth: 51.530,
                  ),
                ),
                SizedBox(width: 8),
                _regionButton(
                  'Tokyo Center',
                  const LngLatBounds(
                    longitudeWest: 139.700,
                    longitudeEast: 139.780,
                    latitudeSouth: 35.650,
                    latitudeNorth: 35.700,
                  ),
                ),
              ],
            ),
          ),
          // Map
          Expanded(
            child: MapMetricsView(
              options: MapOptions(
                initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
                initZoom: 5,
                initStyle:
                    'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
              ),
              onMapCreated: (controller) => mapController = controller,
            ),
          ),
        ],
      ),
    );
  }

  Widget _regionButton(String label, LngLatBounds bounds) {
    return ElevatedButton(
      onPressed: () => _fitToBounds(bounds),
      child: Text(label),
    );
  }

  void _fitToBounds(LngLatBounds bounds) {
    mapController?.fitBounds(
      bounds: bounds,
      padding: const EdgeInsets.all(50), // padding in pixels
    );
  }
}

Fit to Markers

Automatically calculate bounds from a set of marker points 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> {
  MapController? mapController;

  final List<Position> markerPositions = [
    Position(2.2945, 48.8584), // Eiffel Tower
    Position(2.3376, 48.8606), // Louvre
    Position(2.3499, 48.8530), // Notre-Dame
    Position(2.3431, 48.8867), // Sacré-Cœur
    Position(2.2950, 48.8738), // Arc de Triomphe
  ];

  List<Point> get points =>
      markerPositions.map((p) => Point(coordinates: p)).toList();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Fit to Markers')),
      body: MapMetricsView(
        options: MapOptions(
          initCenter: Position(2.3522, 48.8566), // lng, lat — Paris
          initZoom: 10,
          initStyle:
              'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
        ),
        onMapCreated: (controller) {
          mapController = controller;
          // Fit to all markers after map loads
          _fitToAllMarkers();
        },
        layers: [
          MarkerLayer(points: points, iconAnchor: IconAnchor.bottom),
        ],
      ),
      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.lat.toDouble();
    double maxLat = markerPositions.first.lat.toDouble();
    double minLng = markerPositions.first.lng.toDouble();
    double maxLng = markerPositions.first.lng.toDouble();

    for (final position in markerPositions) {
      final lat = position.lat.toDouble();
      final lng = position.lng.toDouble();
      if (lat < minLat) minLat = lat;
      if (lat > maxLat) maxLat = lat;
      if (lng < minLng) minLng = lng;
      if (lng > maxLng) maxLng = lng;
    }

    mapController?.fitBounds(
      bounds: LngLatBounds(
        longitudeWest: minLng,
        longitudeEast: maxLng,
        latitudeSouth: minLat,
        latitudeNorth: maxLat,
      ),
      padding: const EdgeInsets.all(60), // padding
    );
  }
}

Fit Bounds with Custom Padding

fitBounds takes a Flutter EdgeInsets for padding, so each side can be controlled independently:

dart
// Uniform padding
mapController?.fitBounds(
  bounds: bounds,
  padding: const EdgeInsets.all(50),
);

// Asymmetric padding — useful for keeping content clear of a bottom sheet
// or app bar
mapController?.fitBounds(
  bounds: bounds,
  padding: const EdgeInsets.only(top: 80, bottom: 200, left: 40, right: 40),
);

LngLatBounds Properties

PropertyTypeDescription
longitudeWestdoubleWestern (left) edge of the bounding box
longitudeEastdoubleEastern (right) edge of the bounding box
latitudeSouthdoubleSouthern (bottom) edge of the bounding box
latitudeNorthdoubleNorthern (top) edge 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.