diff --git a/lib/src/geo/latlng_bounds.dart b/lib/src/geo/latlng_bounds.dart index a9cdbae04..d227378a7 100644 --- a/lib/src/geo/latlng_bounds.dart +++ b/lib/src/geo/latlng_bounds.dart @@ -1,4 +1,4 @@ -import 'dart:math' as math; +import 'dart:math'; import 'package:latlong2/latlong.dart'; import 'package:vector_math/vector_math_64.dart'; @@ -6,154 +6,215 @@ import 'package:vector_math/vector_math_64.dart'; /// Data structure representing rectangular bounding box constrained by its /// northwest and southeast corners class LatLngBounds { - late LatLng _sw; - late LatLng _ne; + /// The latitude north edge of the bounds + double north; + + /// The latitude south edge of the bounds + double south; + + /// The longitude east edge of the bounds + double east; + + /// The longitude west edge of the bounds + double west; /// Create new [LatLngBounds] by providing two corners. Both corners have to /// be on opposite sites but it doesn't matter which opposite corners or in /// what order the corners are provided. - LatLngBounds( - LatLng corner1, - LatLng corner2, - ) : this.fromPoints([corner1, corner2]); + /// + /// If you want to create [LatLngBounds] with raw values, use the + /// [LatLngBounds.unsafe] constructor instead. + factory LatLngBounds(LatLng corner1, LatLng corner2) { + final double minX; + final double maxX; + final double minY; + final double maxY; + if (corner1.longitude >= corner2.longitude) { + maxX = corner1.longitude; + minX = corner2.longitude; + } else { + maxX = corner2.longitude; + minX = corner1.longitude; + } + if (corner1.latitude >= corner2.latitude) { + maxY = corner1.latitude; + minY = corner2.latitude; + } else { + maxY = corner2.latitude; + minY = corner1.latitude; + } + return LatLngBounds.unsafe( + north: maxY, + south: minY, + east: maxX, + west: minX, + ); + } + + /// Create a [LatLngBounds] instance from raw edge values. + /// + /// Potentially throws assertion errors if the coordinates exceed their max + /// or min values or if coordinates are meant to be smaller / bigger + /// but aren't. + LatLngBounds.unsafe({ + required this.north, + required this.south, + required this.east, + required this.west, + }) : assert( + north <= 90, "The north latitude can't be bigger than 90: $north"), + assert(north >= -90, + "The north latitude can't be smaller than -90: $north"), + assert( + south <= 90, "The south latitude can't be bigger than 90: $south"), + assert(south >= -90, + "The south latitude can't be smaller than -90: $south"), + assert( + east <= 180, "The east longitude can't be bigger than 180: $east"), + assert(east >= -180, + "The east longitude can't be smaller than -180: $east"), + assert( + west <= 180, "The west longitude can't be bigger than 180: $west"), + assert(west >= -180, + "The west longitude can't be smaller than -180: $west"), + assert(north >= south, + "The north latitude can't be smaller than the south latitude"), + assert(east >= west, + "The west longitude can't be smaller than the east longitude"); /// Create a new [LatLngBounds] from a list of [LatLng] points. This /// calculates the bounding box of the provided points. - LatLngBounds.fromPoints(List points) - : assert( - points.isNotEmpty, - 'LatLngBounds cannot be created with an empty List of LatLng', - ) { + factory LatLngBounds.fromPoints(List points) { + assert( + points.isNotEmpty, + 'LatLngBounds cannot be created with an empty List of LatLng', + ); + // initialize bounds with max values. double minX = 180; double maxX = -180; double minY = 90; double maxY = -90; - + // find the largest and smallest latitude and longitude for (final point in points) { - minX = math.min(minX, point.longitude); - minY = math.min(minY, point.latitude); - maxX = math.max(maxX, point.longitude); - maxY = math.max(maxY, point.latitude); + if (point.longitude < minX) minX = point.longitude; + if (point.longitude > maxX) maxX = point.longitude; + if (point.latitude < minY) minY = point.latitude; + if (point.latitude > maxY) maxY = point.latitude; } - - _sw = LatLng(minY, minX); - _ne = LatLng(maxY, maxX); + return LatLngBounds.unsafe( + north: maxY, + south: minY, + east: maxX, + west: minX, + ); } /// Expands bounding box by [latLng] coordinate point. This method mutates /// the bounds object on which it is called. void extend(LatLng latLng) { - _extend(latLng, latLng); + north = min(90, max(north, latLng.latitude)); + south = max(-90, min(south, latLng.latitude)); + east = min(180, max(east, latLng.longitude)); + west = max(-180, min(west, latLng.longitude)); } /// Expands bounding box by other [bounds] object. If provided [bounds] object /// is smaller than current one, it is not shrunk. This method mutates /// the bounds object on which it is called. void extendBounds(LatLngBounds bounds) { - _extend(bounds._sw, bounds._ne); + north = min(90, max(north, bounds.north)); + south = max(-90, min(south, bounds.south)); + east = min(180, max(east, bounds.east)); + west = max(-180, min(west, bounds.west)); } - void _extend(LatLng sw2, LatLng ne2) { - _sw = LatLng( - math.min(sw2.latitude, _sw.latitude), - math.min(sw2.longitude, _sw.longitude), - ); - _ne = LatLng( - math.max(ne2.latitude, _ne.latitude), - math.max(ne2.longitude, _ne.longitude), - ); - } - - /// Obtain west edge of the bounds - double get west => southWest.longitude; - - /// Obtain south edge of the bounds - double get south => southWest.latitude; - - /// Obtain east edge of the bounds - double get east => northEast.longitude; - - /// Obtain north edge of the bounds - double get north => northEast.latitude; - - /// Obtain coordinates of southwest corner of the bounds - LatLng get southWest => _sw; - - /// Obtain coordinates of northeast corner of the bounds - LatLng get northEast => _ne; - - /// Obtain coordinates of northwest corner of the bounds + /// Obtain coordinates of southwest corner of the bounds. + /// + /// Instead of using latitude or longitude of the corner, use [south] or + /// [west] instead! + LatLng get southWest => LatLng(south, west); + + /// Obtain coordinates of northeast corner of the bounds. + /// + /// Instead of using latitude or longitude of the corner, use [north] or + /// [east] instead! + LatLng get northEast => LatLng(north, east); + + /// Obtain coordinates of northwest corner of the bounds. + /// + /// Instead of using latitude or longitude of the corner, use [north] or + /// [west] instead! LatLng get northWest => LatLng(north, west); - /// Obtain coordinates of southeast corner of the bounds + /// Obtain coordinates of southeast corner of the bounds. + /// + /// Instead of using latitude or longitude of the corner, use [south] or + /// [east] instead! LatLng get southEast => LatLng(south, east); /// Obtain coordinates of the bounds center LatLng get center { - /* https://stackoverflow.com/a/4656937 - http://www.movable-type.co.uk/scripts/latlong.html - - coord 1: southWest - coord 2: northEast - - phi: lat - lambda: lng - */ - - final phi1 = southWest.latitudeInRad; - final lambda1 = southWest.longitudeInRad; - final phi2 = northEast.latitudeInRad; - - final dLambda = degrees2Radians * - (northEast.longitude - - southWest.longitude); // delta lambda = lambda2-lambda1 - - final bx = math.cos(phi2) * math.cos(dLambda); - final by = math.cos(phi2) * math.sin(dLambda); - final phi3 = math.atan2(math.sin(phi1) + math.sin(phi2), - math.sqrt((math.cos(phi1) + bx) * (math.cos(phi1) + bx) + by * by)); - final lambda3 = lambda1 + math.atan2(by, math.cos(phi1) + bx); + // https://stackoverflow.com/a/4656937 + // http://www.movable-type.co.uk/scripts/latlong.html + // coord 1: southWest + // coord 2: northEast + // phi: lat + // lambda: lng + + final phi1 = south * degrees2Radians; + final lambda1 = west * degrees2Radians; + final phi2 = north * degrees2Radians; + + // delta lambda = lambda2-lambda1 + final dLambda = degrees2Radians * (east - west); + + final bx = cos(phi2) * cos(dLambda); + final by = cos(phi2) * sin(dLambda); + final phi3 = atan2(sin(phi1) + sin(phi2), + sqrt((cos(phi1) + bx) * (cos(phi1) + bx) + by * by)); + final lambda3 = lambda1 + atan2(by, cos(phi1) + bx); // phi3 and lambda3 are actually in radians and LatLng wants degrees return LatLng(phi3 * radians2Degrees, lambda3 * radians2Degrees); } /// Checks whether [point] is inside bounds - bool contains(LatLng point) { - final sw2 = point; - final ne2 = point; - return containsBounds(LatLngBounds(sw2, ne2)); - } - - /// Checks whether [bounds] is contained inside bounds - bool containsBounds(LatLngBounds bounds) { - final sw2 = bounds._sw; - final ne2 = bounds._ne; - return (sw2.latitude >= _sw.latitude) && - (ne2.latitude <= _ne.latitude) && - (sw2.longitude >= _sw.longitude) && - (ne2.longitude <= _ne.longitude); - } - - /// Checks whether at least one edge of [bounds] is overlapping with some - /// other edge of bounds - bool isOverlapping(LatLngBounds bounds) { - /* check if bounding box rectangle is outside the other, if it is then it's - considered not overlapping - */ - if (_sw.latitude > bounds._ne.latitude || - _ne.latitude < bounds._sw.latitude || - _ne.longitude < bounds._sw.longitude || - _sw.longitude > bounds._ne.longitude) { - return false; - } - return true; - } + bool contains(LatLng point) => + point.longitude >= west && + point.longitude <= east && + point.latitude >= south && + point.latitude <= north; + + /// Checks whether the [other] bounding box is contained inside bounds. + bool containsBounds(LatLngBounds other) => + other.south >= south && + other.north <= north && + other.west >= west && + other.east <= east; + + /// Checks whether at least one edge of the [other] bounding box is + /// overlapping with this bounding box. + /// + /// Bounding boxes that touch each other but don't overlap are counted as + /// not overlapping. + bool isOverlapping(LatLngBounds other) => !(south > other.north || + north < other.south || + east < other.west || + west > other.east); @override - int get hashCode => Object.hash(_sw, _ne); + int get hashCode => Object.hash(south, north, east, west); @override bool operator ==(Object other) => - other is LatLngBounds && other._sw == _sw && other._ne == _ne; + identical(this, other) || + (other is LatLngBounds && + other.north == north && + other.south == south && + other.east == east && + other.west == west); + + @override + String toString() => + 'LatLngBounds(north: $north, south: $south, east: $east, west: $west)'; } diff --git a/lib/src/layer/polyline_layer/polyline_layer.dart b/lib/src/layer/polyline_layer/polyline_layer.dart index 6c4a0361c..a373ffbb9 100644 --- a/lib/src/layer/polyline_layer/polyline_layer.dart +++ b/lib/src/layer/polyline_layer/polyline_layer.dart @@ -167,15 +167,11 @@ class _PolylineLayerState extends State> { // The min(-90), max(180), ... are used to get around the limits of LatLng // the value cannot be greater or smaller than that - final boundsAdjusted = LatLngBounds( - LatLng( - math.max(-90, bounds.southWest.latitude - margin), - math.max(-180, bounds.southWest.longitude - margin), - ), - LatLng( - math.min(90, bounds.northEast.latitude + margin), - math.min(180, bounds.northEast.longitude + margin), - ), + final boundsAdjusted = LatLngBounds.unsafe( + west: math.max(-180, bounds.west - margin), + east: math.min(90, bounds.east + margin), + south: math.max(-90, bounds.south - margin), + north: math.min(180, bounds.north + margin), ); // segment is visible