Skip to content

Commit

Permalink
Migrate LatLngBounds to nullsafety (#1431)
Browse files Browse the repository at this point in the history
* removed nullable values

* completed tests

* added test

* finalized changes

---------

Co-authored-by: Luka S <[email protected]>
  • Loading branch information
TesteurManiak and JaffaKetchup authored Feb 5, 2023
1 parent b2ae947 commit f558e43
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 91 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,6 @@ pubspec.lock
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3

# Coverage files
coverage/
25 changes: 14 additions & 11 deletions example/lib/pages/animated_map_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
// See https://github.com/flutter/flutter/issues/14317#issuecomment-361085869
// This project didn't require that change, so YMMV.

static LatLng london = LatLng(51.5, -0.09);
static LatLng paris = LatLng(48.8566, 2.3522);
static LatLng dublin = LatLng(53.3498, -6.2603);
static final london = LatLng(51.5, -0.09);
static final paris = LatLng(48.8566, 2.3522);
static final dublin = LatLng(53.3498, -6.2603);

late final MapController mapController;

Expand Down Expand Up @@ -142,10 +142,12 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
children: <Widget>[
MaterialButton(
onPressed: () {
final bounds = LatLngBounds();
bounds.extend(dublin);
bounds.extend(paris);
bounds.extend(london);
final bounds = LatLngBounds.fromPoints([
dublin,
paris,
london,
]);

mapController.fitBounds(
bounds,
options: const FitBoundsOptions(
Expand All @@ -157,10 +159,11 @@ class AnimatedMapControllerPageState extends State<AnimatedMapControllerPage>
),
MaterialButton(
onPressed: () {
final bounds = LatLngBounds();
bounds.extend(dublin);
bounds.extend(paris);
bounds.extend(london);
final bounds = LatLngBounds.fromPoints([
dublin,
paris,
london,
]);

final centerZoom =
mapController.centerZoomFitBounds(bounds);
Expand Down
10 changes: 6 additions & 4 deletions example/lib/pages/map_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ class MapControllerPageState extends State<MapControllerPage> {
children: <Widget>[
MaterialButton(
onPressed: () {
final bounds = LatLngBounds();
bounds.extend(dublin);
bounds.extend(paris);
bounds.extend(london);
final bounds = LatLngBounds.fromPoints([
dublin,
paris,
london,
]);

_mapController.fitBounds(
bounds,
options: const FitBoundsOptions(
Expand Down
98 changes: 40 additions & 58 deletions lib/src/geo/latlng_bounds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@ import 'package:latlong2/latlong.dart';
/// Data structure representing rectangular bounding box constrained by its
/// northwest and southeast corners
class LatLngBounds {
LatLng? _sw;
LatLng? _ne;
late final LatLng _sw;
late final LatLng _ne;

LatLngBounds([LatLng? corner1, LatLng? corner2]) {
extend(corner1);
extend(corner2);
}

LatLngBounds.fromPoints(List<LatLng> points) {
if (points.isEmpty) {
return;
}
LatLngBounds(
LatLng corner1,
LatLng corner2,
) : this.fromPoints([corner1, corner2]);

LatLngBounds.fromPoints(List<LatLng> points) : assert(points.isNotEmpty) {
double minX = 180;
double maxX = -180;
double minY = 90;
Expand Down Expand Up @@ -50,10 +46,7 @@ class LatLngBounds {

/// Expands bounding box by [latlng] coordinate point. This method mutates
/// the bounds object on which it is called.
void extend(LatLng? latlng) {
if (latlng == null) {
return;
}
void extend(LatLng latlng) {
_extend(latlng, latlng);
}

Expand All @@ -64,35 +57,30 @@ class LatLngBounds {
_extend(bounds._sw, bounds._ne);
}

void _extend(LatLng? sw2, LatLng? ne2) {
if (_sw == null && _ne == null) {
_sw = LatLng(sw2!.latitude, sw2.longitude);
_ne = LatLng(ne2!.latitude, ne2.longitude);
} else {
_sw!.latitude = math.min(sw2!.latitude, _sw!.latitude);
_sw!.longitude = math.min(sw2.longitude, _sw!.longitude);
_ne!.latitude = math.max(ne2!.latitude, _ne!.latitude);
_ne!.longitude = math.max(ne2.longitude, _ne!.longitude);
}
void _extend(LatLng sw2, LatLng ne2) {
_sw.latitude = math.min(sw2.latitude, _sw.latitude);
_sw.longitude = math.min(sw2.longitude, _sw.longitude);
_ne.latitude = math.max(ne2.latitude, _ne.latitude);
_ne.longitude = math.max(ne2.longitude, _ne.longitude);
}

/// Obtain west edge of the bounds
double get west => southWest!.longitude;
double get west => southWest.longitude;

/// Obtain south edge of the bounds
double get south => southWest!.latitude;
double get south => southWest.latitude;

/// Obtain east edge of the bounds
double get east => northEast!.longitude;
double get east => northEast.longitude;

/// Obtain north edge of the bounds
double get north => northEast!.latitude;
double get north => northEast.latitude;

/// Obtain coordinates of southwest corner of the bounds
LatLng? get southWest => _sw;
LatLng get southWest => _sw;

/// Obtain coordinates of northeast corner of the bounds
LatLng? get northEast => _ne;
LatLng get northEast => _ne;

/// Obtain coordinates of northwest corner of the bounds
LatLng get northWest => LatLng(north, west);
Expand All @@ -112,12 +100,12 @@ class LatLngBounds {
lambda: lng
*/

final phi1 = southWest!.latitudeInRad;
final lambda1 = southWest!.longitudeInRad;
final phi2 = northEast!.latitudeInRad;
final phi1 = southWest.latitudeInRad;
final lambda1 = southWest.longitudeInRad;
final phi2 = northEast.latitudeInRad;

final dLambda = degToRadian(northEast!.longitude -
southWest!.longitude); // delta lambda = lambda2-lambda1
final dLambda = degToRadian(northEast.longitude -
southWest.longitude); // delta lambda = lambda2-lambda1

final bx = math.cos(phi2) * math.cos(dLambda);
final by = math.cos(phi2) * math.sin(dLambda);
Expand All @@ -130,50 +118,44 @@ class LatLngBounds {
}

/// Checks whether bound object is valid
bool get isValid {
return _sw != null && _ne != null;
}
/// TODO: remove this property in the next major release.
@Deprecated('This method is unnecessary and will be removed in the future.')
bool get isValid => true;

/// Checks whether [point] is inside bounds
bool contains(LatLng? point) {
if (!isValid) {
return false;
}
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 sw2 = bounds._sw;
final ne2 = bounds._ne;
return (sw2.latitude >= _sw!.latitude) &&
(ne2!.latitude <= _ne!.latitude) &&
(sw2.longitude >= _sw!.longitude) &&
(ne2.longitude <= _ne!.longitude);
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) {
if (!isValid) {
return false;
}
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) {
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;
}

@override
int get hashCode => _sw.hashCode + _ne.hashCode;
int get hashCode => Object.hash(_sw, _ne);

@override
bool operator ==(Object other) =>
Expand Down
4 changes: 2 additions & 2 deletions lib/src/layer/tile_layer/tile_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,8 @@ class _TileLayerState extends State<TileLayer> with TickerProviderStateMixin {

Bounds _latLngBoundsToPixelBounds(
FlutterMapState map, LatLngBounds bounds, double thisZoom) {
final swPixel = map.project(bounds.southWest!, thisZoom).floor();
final nePixel = map.project(bounds.northEast!, thisZoom).ceil();
final swPixel = map.project(bounds.southWest, thisZoom).floor();
final nePixel = map.project(bounds.northEast, thisZoom).ceil();
final pxBounds = Bounds(swPixel, nePixel);
return pxBounds;
}
Expand Down
24 changes: 8 additions & 16 deletions lib/src/map/flutter_map_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ class FlutterMapState extends MapGestureMixin
newZoom = fitZoomToBounds(newZoom);
final mapMoved = newCenter != _center || newZoom != _zoom;

if (!mapMoved || !_calculateBounds().isValid) {
if (!mapMoved) {
return false;
}

Expand Down Expand Up @@ -483,18 +483,12 @@ class FlutterMapState extends MapGestureMixin
}

void fitBounds(LatLngBounds bounds, FitBoundsOptions options) {
if (!bounds.isValid) {
throw Exception('Bounds are not valid.');
}
final target = getBoundsCenterZoom(bounds, options);
move(target.center, target.zoom, source: MapEventSource.fitBounds);
}

CenterZoom centerZoomFitBounds(
LatLngBounds bounds, FitBoundsOptions options) {
if (!bounds.isValid) {
throw Exception('Bounds are not valid.');
}
return getBoundsCenterZoom(bounds, options);
}

Expand Down Expand Up @@ -523,8 +517,8 @@ class FlutterMapState extends MapGestureMixin
zoom = math.min(options.maxZoom, zoom);

final paddingOffset = (paddingBR - paddingTL) / 2;
final swPoint = project(bounds.southWest!, zoom);
final nePoint = project(bounds.northEast!, zoom);
final swPoint = project(bounds.southWest, zoom);
final nePoint = project(bounds.northEast, zoom);
final center = unproject((swPoint + nePoint) / 2 + paddingOffset, zoom);
return CenterZoom(
center: center,
Expand Down Expand Up @@ -608,8 +602,8 @@ class FlutterMapState extends MapGestureMixin
LatLng testCenter, double testZoom, LatLngBounds maxBounds) {
LatLng? newCenter;

final swPixel = project(maxBounds.southWest!, testZoom);
final nePixel = project(maxBounds.northEast!, testZoom);
final swPixel = project(maxBounds.southWest, testZoom);
final nePixel = project(maxBounds.northEast, testZoom);

final centerPix = project(testCenter, testZoom);

Expand Down Expand Up @@ -729,14 +723,12 @@ class FlutterMapState extends MapGestureMixin
double? _safeAreaZoom;

//if there is a pan boundary, do not cross
bool isOutOfBounds(LatLng? center) {
bool isOutOfBounds(LatLng center) {
if (options.adaptiveBoundaries) {
return !_safeArea!.contains(center);
}
if (options.swPanBoundary != null && options.nePanBoundary != null) {
if (center == null) {
return true;
} else if (center.latitude < options.swPanBoundary!.latitude ||
if (center.latitude < options.swPanBoundary!.latitude ||
center.latitude > options.nePanBoundary!.latitude) {
return true;
} else if (center.longitude < options.swPanBoundary!.longitude ||
Expand Down Expand Up @@ -817,7 +809,7 @@ class _SafeArea {
isLatitudeBlocked = southWest.latitude > northEast.latitude,
isLongitudeBlocked = southWest.longitude > northEast.longitude;

bool contains(LatLng? point) =>
bool contains(LatLng point) =>
isLatitudeBlocked || isLongitudeBlocked ? false : bounds.contains(point);

LatLng containPoint(LatLng point, LatLng fallback) => LatLng(
Expand Down
51 changes: 51 additions & 0 deletions test/geo/latlng_bounds_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter_map/src/geo/latlng_bounds.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:latlong2/latlong.dart';

void main() {
group('LatLngBounds', () {
final london = LatLng(51.5, -0.09);
final paris = LatLng(48.8566, 2.3522);
final dublin = LatLng(53.3498, -6.2603);

group('LatLngBounds constructor', () {
test('with dublin, paris', () {
final bounds = LatLngBounds(dublin, paris);

expect(bounds, LatLngBounds.fromPoints([dublin, paris]));
});
});

group('LatLngBounds.fromPoints', () {
test('throw AssertionError if points is empty', () {
expect(() => LatLngBounds.fromPoints([]), throwsAssertionError);
});

test('with dublin, paris, london', () {
final bounds = LatLngBounds.fromPoints([
dublin,
paris,
london,
]);

final sw = bounds.southWest;
final ne = bounds.northEast;

expect(sw.latitude, 48.8566);
expect(sw.longitude, -6.2603);
expect(ne.latitude, 53.3498);
expect(ne.longitude, 2.3522);
});
});

group('hashCode', () {
test('with dublin, paris', () {
final bounds1 = LatLngBounds(dublin, paris);
final bounds2 = LatLngBounds(dublin, paris);

expect(bounds1 == bounds2, isTrue);
expect(bounds1.hashCode, bounds2.hashCode);
});
});
});
}

0 comments on commit f558e43

Please sign in to comment.