From f70a084c858f0dbe70de04816cbe201ca4c5b902 Mon Sep 17 00:00:00 2001 From: Peter Lundkvist Date: Sun, 26 Apr 2026 18:20:37 +0200 Subject: [PATCH 1/2] Add constructor for bounds that can span the antimeridian --- lib/src/geo/latlng_bounds.dart | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/src/geo/latlng_bounds.dart b/lib/src/geo/latlng_bounds.dart index dcc908e1c..31426ff49 100644 --- a/lib/src/geo/latlng_bounds.dart +++ b/lib/src/geo/latlng_bounds.dart @@ -96,6 +96,40 @@ class LatLngBounds { east = min(longitudeCenter + longitudeWidth / 2, maxLongitude), west = max(longitudeCenter - longitudeWidth / 2, minLongitude); + /// Creates bounds that can span the antimeridian. If this is the case, + /// [west] will be larger than [east]. + LatLngBounds.unconstrainedLng({ + required this.north, + required this.south, + required this.longitudeCenter, + required this.longitudeWidth, + }) : assert(north <= maxLatitude, + "The north latitude can't be bigger than $maxLatitude: $north"), + assert(north >= minLatitude, + "The north latitude can't be smaller than $minLatitude: $north"), + assert(south <= maxLatitude, + "The south latitude can't be bigger than $maxLatitude: $south"), + assert(south >= minLatitude, + "The south latitude can't be smaller than $minLatitude: $south"), + assert(longitudeCenter <= maxLongitude, + "The longitude center can't be bigger than $maxLongitude: $longitudeCenter"), + assert(longitudeCenter >= minLongitude, + "The longitude center can't be smaller than $minLongitude: $longitudeCenter"), + assert(longitudeWidth >= 0, 'The longitude width must be positive'), + assert( + north >= south, + "The north latitude ($north) can't be smaller than the " + 'south latitude ($south)', + ), + east = _wrapLng(longitudeCenter + longitudeWidth / 2), + west = _wrapLng(longitudeCenter - longitudeWidth / 2); + + static double _wrapLng(double longitude) => longitude > maxLongitude + ? longitude - 360 + : longitude < minLongitude + ? longitude + 360 + : longitude; + /// Create a [LatLngBounds] instance from raw edge values. /// /// Potentially throws assertion errors if the coordinates exceed their max From c02814d9aa98f5f1d0c6ee9682a403bd3a41dc78 Mon Sep 17 00:00:00 2001 From: Peter Lundkvist Date: Sun, 26 Apr 2026 18:21:27 +0200 Subject: [PATCH 2/2] Allow fit to bounds spanning the antimeridian --- lib/src/map/camera/camera_fit.dart | 70 +++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 15 deletions(-) diff --git a/lib/src/map/camera/camera_fit.dart b/lib/src/map/camera/camera_fit.dart index 4fcb84aaf..a684e8c47 100644 --- a/lib/src/map/camera/camera_fit.dart +++ b/lib/src/map/camera/camera_fit.dart @@ -126,7 +126,11 @@ class FitBounds extends CameraFit { projectedCenter = (swPoint + nePoint) / 2 + paddingOffset; } - final center = camera.unprojectAtZoom(projectedCenter, newZoom); + final center = _correctBoundsCenterForAntimeridianSpan( + camera.unprojectAtZoom(projectedCenter, newZoom), + bounds, + ); + return camera.withPosition( center: center, zoom: newZoom, @@ -137,16 +141,13 @@ class FitBounds extends CameraFit { MapCamera camera, Offset pixelPadding, ) { - final nw = bounds.northWest; - final se = bounds.southEast; var size = camera.nonRotatedSize - pixelPadding as Size; // Prevent negative size which results in NaN zoom value later on in the calculation size = Size(math.max(0, size.width), math.max(0, size.height)); - var boundsSize = Rect.fromPoints( - camera.projectAtZoom(se, camera.zoom), - camera.projectAtZoom(nw, camera.zoom), - ).size; + + Size boundsSize = _boundsSizeWithPossibleAntimeridianSpan(bounds, camera); + if (camera.rotation != 0.0) { final cosAngle = math.cos(camera.rotationRad).abs(); final sinAngle = math.sin(camera.rotationRad).abs(); @@ -225,10 +226,8 @@ class FitInsideBounds extends CameraFit { final cameraSize = camera.nonRotatedSize - paddingTotalXY as Size; - final projectedBoundsSize = Rect.fromPoints( - camera.projectAtZoom(bounds.southEast, camera.zoom), - camera.projectAtZoom(bounds.northWest, camera.zoom), - ).size; + final projectedBoundsSize = + _boundsSizeWithPossibleAntimeridianSpan(bounds, camera); final scale = _rectInRotRectScale( angleRad: camera.rotationRad, @@ -254,10 +253,13 @@ class FitInsideBounds extends CameraFit { ); newZoom = newZoom.clamp(min, max); - final newCenter = _getCenter( - camera, - newZoom: newZoom, - paddingOffset: paddingOffset, + final newCenter = _correctBoundsCenterForAntimeridianSpan( + _getCenter( + camera, + newZoom: newZoom, + paddingOffset: paddingOffset, + ), + bounds, ); return camera.withPosition( @@ -496,3 +498,41 @@ class FitCoordinates extends CameraFit { return newZoom.clamp(min, max); } } + +LatLng _correctBoundsCenterForAntimeridianSpan( + LatLng center, + LatLngBounds bounds, +) { + if (bounds.west > bounds.east) { + // Handle a bounding box that spans the antimeridian + var centerLng = (bounds.west + (bounds.east + 360)) / 2; + if (centerLng > 180) { + centerLng -= 360; + } + + return LatLng(center.latitude, centerLng); + } else { + return center; + } +} + +Size _boundsSizeWithPossibleAntimeridianSpan( + LatLngBounds bounds, + MapCamera camera, +) { + if (bounds.west > bounds.east) { + // Handle a bounding box that spans the antimeridian + return Rect.fromPoints( + camera.projectAtZoom( + LatLng(bounds.south, bounds.east + 360), + camera.zoom, + ), + camera.projectAtZoom(bounds.northWest, camera.zoom), + ).size; + } else { + return Rect.fromPoints( + camera.projectAtZoom(bounds.southEast, camera.zoom), + camera.projectAtZoom(bounds.northWest, camera.zoom), + ).size; + } +}