Skip to content

Measure Distances in Flutter

This tutorial shows how to let users tap on the map to place points and measure the distance between them using the Haversine formula.

Prerequisites

Before you begin, ensure you have:

Complete Measurement Tool

Tap on the map to add points. A line connects them and the total distance is calculated:

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

class MeasureDistanceScreen extends StatefulWidget {
  @override
  _MeasureDistanceScreenState createState() => _MeasureDistanceScreenState();
}

class _MeasureDistanceScreenState extends State<MeasureDistanceScreen> {
  MapMetricsController? mapController;
  List<LatLng> points = [];

  Set<Marker> get markers => points.asMap().entries.map((entry) {
    return Marker(
      markerId: MarkerId('point_${entry.key}'),
      position: entry.value,
      icon: BitmapDescriptor.defaultMarkerWithHue(
        entry.key == 0 ? BitmapDescriptor.hueGreen : BitmapDescriptor.hueRed,
      ),
      infoWindow: InfoWindow(
        title: 'Point ${entry.key + 1}',
        snippet: entry.key > 0
            ? 'Segment: ${_formatDistance(_haversine(points[entry.key - 1], entry.value))}'
            : 'Start point',
      ),
    );
  }).toSet();

  Set<Polyline> get polylines => points.length >= 2
      ? {
          Polyline(
            polylineId: PolylineId('measurement'),
            points: points,
            color: Colors.blue,
            width: 3,
            patterns: [PatternItem.dash(12), PatternItem.gap(8)],
          ),
        }
      : {};

  double get totalDistance {
    double total = 0;
    for (int i = 1; i < points.length; i++) {
      total += _haversine(points[i - 1], points[i]);
    }
    return total;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Measure Distance')),
      body: Column(
        children: [
          // Distance display
          Container(
            width: double.infinity,
            padding: EdgeInsets.all(12),
            color: Colors.grey[100],
            child: Row(
              children: [
                Icon(Icons.straighten, size: 20, color: Colors.blue),
                SizedBox(width: 8),
                Text(
                  points.length < 2
                      ? 'Tap on the map to start measuring'
                      : 'Total: ${_formatDistance(totalDistance)}  |  ${points.length} points',
                  style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
                ),
                Spacer(),
                if (points.isNotEmpty)
                  TextButton(
                    onPressed: _clearPoints,
                    child: Text('Clear'),
                  ),
              ],
            ),
          ),
          // Segment details
          if (points.length >= 2)
            Container(
              height: 50,
              child: ListView.builder(
                scrollDirection: Axis.horizontal,
                padding: EdgeInsets.symmetric(horizontal: 8),
                itemCount: points.length - 1,
                itemBuilder: (context, index) {
                  final dist = _haversine(points[index], points[index + 1]);
                  return Container(
                    margin: EdgeInsets.symmetric(horizontal: 4, vertical: 8),
                    padding: EdgeInsets.symmetric(horizontal: 10, vertical: 6),
                    decoration: BoxDecoration(
                      color: Colors.blue[50],
                      borderRadius: BorderRadius.circular(16),
                    ),
                    child: Center(
                      child: Text(
                        '${index + 1}${index + 2}: ${_formatDistance(dist)}',
                        style: TextStyle(fontSize: 12),
                      ),
                    ),
                  );
                },
              ),
            ),
          // Map
          Expanded(
            child: MapMetrics(
              styleUrl: 'https://gateway.mapmetrics.org/styles/YOUR_STYLE_ID?token=YOUR_API_KEY',
              onMapCreated: (controller) => mapController = controller,
              onMapClick: (Point point, LatLng coordinates) {
                setState(() {
                  points.add(coordinates);
                });
              },
              initialCameraPosition: CameraPosition(
                target: LatLng(48.8566, 2.3522),
                zoom: 13.0,
              ),
              markers: markers,
              polylines: polylines,
            ),
          ),
        ],
      ),
      floatingActionButton: points.isNotEmpty
          ? FloatingActionButton.small(
              onPressed: _undoLastPoint,
              child: Icon(Icons.undo),
              tooltip: 'Undo last point',
            )
          : null,
    );
  }

  void _clearPoints() {
    setState(() {
      points.clear();
    });
  }

  void _undoLastPoint() {
    if (points.isNotEmpty) {
      setState(() {
        points.removeLast();
      });
    }
  }

  /// Haversine formula to calculate distance between two LatLng points in meters
  double _haversine(LatLng a, LatLng b) {
    const double R = 6371000; // Earth's radius in meters
    final double lat1 = a.latitude * pi / 180;
    final double lat2 = b.latitude * pi / 180;
    final double dLat = (b.latitude - a.latitude) * pi / 180;
    final double dLng = (b.longitude - a.longitude) * pi / 180;

    final double h = sin(dLat / 2) * sin(dLat / 2) +
        cos(lat1) * cos(lat2) * sin(dLng / 2) * sin(dLng / 2);
    final double c = 2 * atan2(sqrt(h), sqrt(1 - h));

    return R * c;
  }

  /// Format distance for display
  String _formatDistance(double meters) {
    if (meters >= 1000) {
      return '${(meters / 1000).toStringAsFixed(2)} km';
    } else {
      return '${meters.toStringAsFixed(0)} m';
    }
  }
}

How the Haversine Formula Works

The Haversine formula calculates the great-circle distance between two points on a sphere:

dart
double _haversine(LatLng a, LatLng b) {
  const double R = 6371000; // Earth's radius in meters
  final double lat1 = a.latitude * pi / 180;
  final double lat2 = b.latitude * pi / 180;
  final double dLat = (b.latitude - a.latitude) * pi / 180;
  final double dLng = (b.longitude - a.longitude) * pi / 180;

  final double h = sin(dLat / 2) * sin(dLat / 2) +
      cos(lat1) * cos(lat2) * sin(dLng / 2) * sin(dLng / 2);
  final double c = 2 * atan2(sqrt(h), sqrt(1 - h));

  return R * c; // Distance in meters
}

This requires import 'dart:math'; for the sin, cos, atan2, sqrt, and pi functions.

Next Steps


Tip: The Haversine formula gives great-circle distance (straight line over the Earth's surface). For road/walking distance, you would need to use the MapMetrics Directions API.