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:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
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
- Add a Polyline — Draw styled lines on the map
- Draw a Circle — Show a radius around a point
- Map Interactions — Handle all tap events
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.