A faithful Dart port of the core geometry classes from Google's android-maps-utils library.
| Namespace | What it does |
|---|---|
GoogleMapsUtils.spherical |
Distances, headings, offsets, interpolation, areas, and bounding boxes on a sphere |
GoogleMapsUtils.poly |
Polyline encoding/decoding, Douglas-Peucker simplification, point-in-polygon, and on-path/on-edge tests |
GoogleMapsUtils.math |
Low-level haversine, Mercator projections, clamping, and wrapping helpers |
All calculations use the same spherical algorithms as the original Android Java/Kotlin library — results are identical.
Add to your pubspec.yaml:
dependencies:
google_maps_utils: ^5.0.0Then run:
dart pub getimport 'package:google_maps_utils/google_maps_utils.dart';Use the GoogleMapsUtils class as the unified namespace for all operations:
// Spherical geometry
final distance = GoogleMapsUtils.spherical.computeDistanceBetween(from, to);
final heading = GoogleMapsUtils.spherical.computeHeading(from, to);
// Polyline utilities
final path = GoogleMapsUtils.poly.decode(encodedString);
final simplified = GoogleMapsUtils.poly.simplify(path, 5000.0);
// Math helpers
final wrapped = GoogleMapsUtils.math.wrap(190.0, -180.0, 180.0); // -170.0
// Bounding boxes return a lightweight record: (ne: Point, sw: Point)
final bounds = GoogleMapsUtils.spherical.toBounds(-23.55, -46.63, 1000.0);
print(bounds.ne); // north-east corner
print(bounds.sw); // south-west cornerfinal from = Point<double>(0.0, 0.0);
final to = Point<double>(10.0, 5.0);
final distance = GoogleMapsUtils.spherical.computeDistanceBetween(from, to);
// 1241932.5985192063 metersfinal heading = GoogleMapsUtils.spherical.computeHeading(from, to);
// 26.302486345342523 degrees (clockwise from North)final angle = GoogleMapsUtils.spherical.computeAngleBetween(from, to);
// 0.19493500057547358 radians (unit-sphere arc distance)// Move 1000 meters east (heading 90°) from the origin
final origin = Point<double>(0.0, 0.0);
final destination = GoogleMapsUtils.spherical.computeOffset(origin, 1000.0, 90.0);
// Returns the Point at the new location// Given a destination, distance, and heading — find where you started
final origin = GoogleMapsUtils.spherical.computeOffsetOrigin(destination, 1000.0, 90.0);
// Returns Point? (null if no solution exists)// Find the point 25% of the way between two locations
final quarter = GoogleMapsUtils.spherical.interpolate(from, to, 0.25);final path = <Point<double>>[
Point(0.0, 0.0),
Point(1.0, 1.0),
Point(2.0, 0.0),
];
final length = GoogleMapsUtils.spherical.computeLength(path);
// Total distance in meters along the pathfinal cardinal = GoogleMapsUtils.spherical.getCardinal(45.0);
// 'NE'
final cardinal2 = GoogleMapsUtils.spherical.getCardinal(200.0);
// 'SSW'// Decode a Google Encoded Polyline string into coordinates
final path = GoogleMapsUtils.poly.decode(
'wjiaFz`hgQs}GmmBok@}vX|cOzKvvT`uNutJz|UgqAglAjr@ijBz]opA');
// Returns List<Point<double>> with 17 points// Encode coordinates back to a compact string
final encoded = GoogleMapsUtils.poly.encode(path);// Reduce points while preserving shape (tolerance in meters)
final simplified = GoogleMapsUtils.poly.simplify(path, 5000.0);
// 17 points → 6 pointsfinal point = Point<double>(-31.623060, -60.686690);
final polygon = <Point<double>>[
Point(-31.624115, -60.688734),
Point(-31.624115, -60.684657),
Point(-31.621594, -60.686717),
Point(-31.624115, -60.688734),
];
final isInside = GoogleMapsUtils.poly.containsLocationPoly(point, polygon);
// true
// Also supports geodesic (great-circle) edges:
final isInsideGeodesic = GoogleMapsUtils.poly.containsLocationPoly(
point, polygon, geodesic: true,
);final route = <Point<double>>[
Point(0.0, 0.0),
Point(0.0, 10.0),
];
// Is the point on or near the polyline? (default tolerance: 0.1m)
final isOnPath = GoogleMapsUtils.poly.isLocationOnPath(
Point(0.0, 5.0), route, true, // geodesic
);
// true
// Get which segment the point falls on
final segmentIndex = GoogleMapsUtils.poly.locationIndexOnPath(
Point(0.0, 5.0), route, true,
);
// 0 (between route[0] and route[1])final square = <Point<double>>[
Point(0.0, 0.0),
Point(0.0, 10.0),
Point(10.0, 10.0),
Point(10.0, 0.0),
];
// Includes the closing segment (last→first)
final isOnEdge = GoogleMapsUtils.poly.isLocationOnEdge(
Point(5.0, 0.0), square, true,
);
// truefinal p = Point<double>(-23.54545, -23.898098);
final start = Point<double>(0.0, 0.0);
final end = Point<double>(10.0, 5.0);
final dist = GoogleMapsUtils.poly.distanceToLine(p, start, end);
// 3675538.019968191 metersfinal area = GoogleMapsUtils.spherical.computeArea(polygon);
// Area in square meters
final signedArea = GoogleMapsUtils.spherical.computeSignedArea(polygon);
// Positive = counter-clockwise, negative = clockwise// Create bounds from a center point + radius
// Returns a LatLngBounds record: (ne: Point<double>, sw: Point<double>)
final bounds = GoogleMapsUtils.spherical.toBounds(-23.55, -46.63, 1000.0);
print(bounds.ne); // north-east corner
print(bounds.sw); // south-west corner
// Create bounds from a list of points
final bounds = GoogleMapsUtils.spherical.toBoundsFromPoints(path);
// Get the center of a bounds
final center = GoogleMapsUtils.spherical.centerFromLatLngBounds(bounds);
// Split bounds into a grid (division² sub-bounds)
final tiles = GoogleMapsUtils.spherical.toSubBounds(bounds, division: 3);
// Returns 9 sub-boundsGoogleMapsUtils.poly.isClosedPolygon(polygon); // true if first == last point// Default tolerance is 0.1 meters. Use custom tolerance for proximity:
final isNear = GoogleMapsUtils.poly.isLocationOnPathTolerance(
point, polyline, true, 50.0, // 50 meters tolerance
);
// Same for edges:
final isNearEdge = GoogleMapsUtils.poly.isLocationOnEdgeTolerance(
point, polygon, true, 50.0,
);
// With index:
final idx = GoogleMapsUtils.poly.locationIndexOnPathTolerance(
point, polyline, true, 50.0,
);// Clamp a value to a range
GoogleMapsUtils.math.clamp(15.0, 0.0, 10.0); // 10.0
// Wrap a value into [min, max) — useful for longitude
GoogleMapsUtils.math.wrap(190.0, -180.0, 180.0); // -170.0
// Non-negative modulo
GoogleMapsUtils.math.mod(-1.0, 360.0); // 359.0
// Mercator projection (radians)
final y = GoogleMapsUtils.math.mercator(latInRadians);
final lat = GoogleMapsUtils.math.inverseMercator(y);
// Haversine / inverse haversine
final h = GoogleMapsUtils.math.hav(angleInRadians);
final angle = GoogleMapsUtils.math.arcHav(h);
// Haversine distance on unit sphere
final havDist = GoogleMapsUtils.math.havDistance(lat1, lat2, dLng);GoogleMapsUtils.spherical.toRadians(90.0); // 1.5707963... (π/2)
GoogleMapsUtils.spherical.toDegrees(pi); // 180.0See example/example.dart for a complete runnable demo covering all features.
Click to expand example output
Heading: 26.302486345342523 degrees
Angle: 0.19493500057547358 radians
Distance to Line: 3675538.019968191 meters
path size length: 17
simplified path: wjiaFz`hgQcjIke\t{d@|aOutJz|UokC}xWomJdjM
path size simplified length: 6
Distance: 1241932.5985192063 meters
Distance: 1066.7243813716539 meters of polygon
point is inside polygon?: true
| Method | Description |
|---|---|
computeDistanceBetween(from, to) |
Distance in meters between two points |
computeHeading(from, to) |
Heading in degrees [-180, 180) |
computeAngleBetween(from, to) |
Angle in radians (unit-sphere distance) |
computeOffset(from, distance, heading) |
Point at a given distance/heading |
computeOffsetOrigin(to, distance, heading) |
Inverse of computeOffset |
interpolate(from, to, fraction) |
Spherical interpolation (SLERP) |
computeLength(path) |
Total path length in meters |
computeArea(path) |
Enclosed area in m² |
computeSignedArea(path) |
Signed area (determines winding) |
distanceRadians(lat1, lng1, lat2, lng2) |
Arc distance on unit sphere (radians in, radians out) |
toBounds(lat, lng, radius) |
Bounding box from center + radius |
toBoundsFromPoints(points) |
Bounding box from point list |
centerFromLatLngBounds(bounds) |
Center point of a bounds |
toSubBounds(bounds, division:) |
Split bounds into a grid |
getCardinal(angle) |
16-point compass abbreviation |
toRadians(deg) |
Degrees to radians |
toDegrees(rad) |
Radians to degrees |
| Method | Description |
|---|---|
decode(encoded) |
Decode a polyline string to points |
encode(path) |
Encode points to a polyline string |
simplify(poly, tolerance) |
Douglas-Peucker simplification |
containsLocationPoly(point, polygon) |
Spherical point-in-polygon test |
isLocationOnPath(point, polyline, geodesic) |
Point-on-polyline test (0.1m tolerance) |
isLocationOnPathTolerance(point, polyline, geodesic, tolerance) |
Point-on-polyline with custom tolerance |
isLocationOnEdge(point, polygon, geodesic) |
Point-on-polygon-edge test (0.1m tolerance) |
isLocationOnEdgeTolerance(point, polygon, geodesic, tolerance) |
Point-on-edge with custom tolerance |
locationIndexOnPath(point, polyline, geodesic) |
Segment index or -1 |
locationIndexOnPathTolerance(point, poly, geodesic, tolerance) |
Segment index with custom tolerance |
locationIndexOnEdgeOrPath(point, poly, closed, geodesic, tolerance) |
Full control: closed/open + tolerance |
distanceToLine(point, start, end) |
Distance to a line segment |
isClosedPolygon(poly) |
Whether first == last point |
| Method | Description |
|---|---|
earthRadius |
Earth's mean radius in meters (6371008.7714) |
clamp(x, low, high) |
Restrict value to range |
wrap(n, min, max) |
Wrap into [min, max) |
mod(x, m) |
Non-negative modulo |
hav(x) |
Haversine |
arcHav(x) |
Inverse haversine |
sinFromHav(h) |
sin(|x|) given hav(x) |
havFromSin(x) |
hav(asin(x)) |
sinSumFromHav(x, y) |
sin(arcHav(x) + arcHav(y)) |
mercator(lat) |
Latitude (radians) → Mercator Y |
inverseMercator(y) |
Mercator Y → latitude (radians) |
havDistance(lat1, lat2, dLng) |
Haversine distance on unit sphere |
3 of 78 classes from android-maps-utils have been converted — these are the most widely used. Need another class? Open an issue and I'll prioritize it.
This package is proudly sponsored by bus2.me.
If this package saves you time, consider supporting its development.
- 📦 pub.dev package
- 📖 API documentation
- 🐛 Issue tracker
- 🤖 android-maps-utils (upstream)
- 💬 Stack Overflow
- 🦋 Flutter community request
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes with tests
- Ensure
dart analyze --fatal-infosanddart testpass - Open a Pull Request
This project is licensed under the Apache License 2.0 — see the LICENSE file for details.
Original Java source: Copyright 2008, 2013 Google Inc.