Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate LatLngBounds to nullsafety #1431

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
107 changes: 46 additions & 61 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,59 +118,56 @@ 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;
}

/// Expands bounds by decimal degrees unlike [extend] or [extendBounds]
void pad(double bufferRatio) {
final heightBuffer = (_sw!.latitude - _ne!.latitude).abs() * bufferRatio;
final widthBuffer = (_sw!.longitude - _ne!.longitude).abs() * bufferRatio;
final heightBuffer = (_sw.latitude - _ne.latitude).abs() * bufferRatio;
final widthBuffer = (_sw.longitude - _ne.longitude).abs() * bufferRatio;

_sw.latitude = _sw.latitude - heightBuffer;
_sw.longitude = _sw.longitude - widthBuffer;

_sw = LatLng(_sw!.latitude - heightBuffer, _sw!.longitude - widthBuffer);
_ne = LatLng(_ne!.latitude + heightBuffer, _ne!.longitude + widthBuffer);
_ne.latitude = _ne.latitude + heightBuffer;
_ne.longitude = _ne.longitude + widthBuffer;
}

@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);
});
});
});
}