Skip to content

Commit

Permalink
Add fitCoordinates method to map controller
Browse files Browse the repository at this point in the history
  • Loading branch information
jjoelson committed Jun 8, 2023
1 parent 79b54e4 commit 8b221f3
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/src/gestures/map_events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum MapEventSource {
doubleTapZoomAnimationController,
interactiveFlagsChanged,
fitBounds,
fitCoordinates,
custom,
scrollWheel,
nonRotatedSizeChange,
Expand Down
32 changes: 32 additions & 0 deletions lib/src/map/controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,22 @@ abstract class MapController {
FitBoundsOptions? options,
});

/// Move and zoom the map to perfectly fit [coordinates], with additional
/// configurable [options]
///
/// For information about return value meaning and emitted events, see [move]'s
/// documentation.
bool fitCoordinates(List<LatLng> coordinates, {FitBoundsOptions? options});

/// Calculates the appropriate center and zoom level for the map to perfectly
/// fit [coordinates], with additional configurable [options]
///
/// Does not move/zoom the map: see [fitBounds].
CenterZoom centerZoomFitCoordinates(
List<LatLng> coordinates, {
FitBoundsOptions? options,
});

/// Convert a screen point (x/y) to its corresponding map coordinate (lat/lng),
/// based on the map's current properties
LatLng pointToLatLng(CustomPoint screenPoint);
Expand Down Expand Up @@ -241,6 +257,22 @@ class MapControllerImpl implements MapController {
}) =>
_state.centerZoomFitBounds(bounds, options!);

@override
bool fitCoordinates(
List<LatLng> coordinates, {
FitBoundsOptions? options =
const FitBoundsOptions(padding: EdgeInsets.all(12)),
}) =>
_state.fitCoordinates(coordinates, options!);

@override
CenterZoom centerZoomFitCoordinates(
List<LatLng> coordinates, {
FitBoundsOptions? options =
const FitBoundsOptions(padding: EdgeInsets.all(12)),
}) =>
_state.getCoordinatesCenterZoom(coordinates, options!);

@override
LatLng pointToLatLng(CustomPoint localPoint) =>
_state.pointToLatLng(localPoint);
Expand Down
88 changes: 88 additions & 0 deletions lib/src/map/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,25 @@ class FlutterMapState extends MapGestureMixin
return zoom;
}

bool fitCoordinates(
List<LatLng> coordinates,
FitBoundsOptions options, {
Offset offset = Offset.zero,
}) {
final target = getCoordinatesCenterZoom(coordinates, options);
return move(
target.center,
target.zoom,
offset: offset,
source: MapEventSource.fitCoordinates,
);
}

CenterZoom centerZoomFitCoordinates(
List<LatLng> coordinates, FitBoundsOptions options) {
return getCoordinatesCenterZoom(coordinates, options);
}

bool fitBounds(
LatLngBounds bounds,
FitBoundsOptions options, {
Expand Down Expand Up @@ -545,6 +564,75 @@ class FlutterMapState extends MapGestureMixin
return math.max(min, math.min(max, zoom));
}

CenterZoom getCoordinatesCenterZoom(
List<LatLng> coordinates, FitBoundsOptions options) {
final paddingTL =
CustomPoint<double>(options.padding.left, options.padding.top);
final paddingBR =
CustomPoint<double>(options.padding.right, options.padding.bottom);

final paddingTotalXY = paddingTL + paddingBR;

var newZoom = getCoordinatesZoom(
coordinates,
paddingTotalXY,
inside: options.inside,
forceIntegerZoomLevel: options.forceIntegerZoomLevel,
);
newZoom = math.min(options.maxZoom, newZoom);

final projectedPoints = [
for (final coord in coordinates) project(coord, newZoom)
];

final rotatedPoints =
projectedPoints.map((point) => point.rotate(-rotationRad));

final rotatedBounds = Bounds.containing(rotatedPoints);
final rotatedBoundsCenter = rotatedBounds.center;

final derotatedCenter = rotatedBoundsCenter.rotate(rotationRad);

final paddingOffset = (paddingBR - paddingTL) / 2;
final newCenter = unproject(derotatedCenter + paddingOffset, newZoom);

return CenterZoom(
center: newCenter,
zoom: newZoom,
);
}

double getCoordinatesZoom(
List<LatLng> coordinates, CustomPoint<double> padding,
{bool inside = false, bool forceIntegerZoomLevel = false}) {
final min = options.minZoom ?? 0.0;
final max = options.maxZoom ?? double.infinity;
var size = nonrotatedSize - padding;
// Prevent negative size which results in NaN zoom value later on in the calculation
size = CustomPoint(math.max(0, size.x), math.max(0, size.y));

final projectedPoints = [
for (final coord in coordinates) project(coord, zoom)
];

final rotatedPoints =
projectedPoints.map((point) => point.rotate(-rotationRad));
final rotatedBounds = Bounds.containing(rotatedPoints);

final boundsSize = rotatedBounds.size;

final scaleX = size.x / boundsSize.x;
final scaleY = size.y / boundsSize.y;
final scale = inside ? math.max(scaleX, scaleY) : math.min(scaleX, scaleY);

var newZoom = getScaleZoom(scale, zoom);
if (forceIntegerZoomLevel) {
newZoom = inside ? newZoom.ceilToDouble() : newZoom.floorToDouble();
}

return math.max(min, math.min(max, newZoom));
}

CustomPoint<double> project(LatLng latlng, [double? zoom]) {
zoom ??= _zoom;
return options.crs.latLngToPoint(latlng, zoom);
Expand Down
29 changes: 29 additions & 0 deletions lib/src/misc/private/bounds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,35 @@ class Bounds<T extends num> {
return bounds2.extend(b);
}

static Bounds<double> containing(Iterable<CustomPoint<double>> points) {
var maxX = double.negativeInfinity;
var maxY = double.negativeInfinity;
var minX = double.infinity;
var minY = double.infinity;

for (final point in points) {
if (point.x > maxX) {
maxX = point.x;
}
if (point.x < minX) {
minX = point.x;
}
if (point.y > maxY) {
maxY = point.y;
}
if (point.y < minY) {
minY = point.y;
}
}

final bounds = Bounds._(
CustomPoint(minX, minY),
CustomPoint(maxX, maxY),
);

return bounds;
}

const Bounds._(this.min, this.max);

/// Creates a new [Bounds] obtained by expanding the current ones with a new
Expand Down

0 comments on commit 8b221f3

Please sign in to comment.