summaryrefslogtreecommitdiff
path: root/platform/ios/MGLMapView.mm
diff options
context:
space:
mode:
Diffstat (limited to 'platform/ios/MGLMapView.mm')
-rw-r--r--platform/ios/MGLMapView.mm287
1 files changed, 233 insertions, 54 deletions
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
index 959f294f07..ef6ae52c33 100644
--- a/platform/ios/MGLMapView.mm
+++ b/platform/ios/MGLMapView.mm
@@ -48,6 +48,9 @@ NSUInteger const MGLStyleVersion = 8;
const NSTimeInterval MGLAnimationDuration = 0.3;
const CGSize MGLAnnotationUpdateViewportOutset = {150, 150};
const CGFloat MGLMinimumZoom = 3;
+const CGFloat MGLMinimumPitch = 0;
+const CGFloat MGLMaximumPitch = 60;
+const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.;
NSString *const MGLAnnotationIDKey = @"MGLAnnotationIDKey";
NSString *const MGLAnnotationSymbolKey = @"MGLAnnotationSymbolKey";
@@ -67,6 +70,18 @@ CLLocationDegrees MGLDegreesFromRadians(CGFloat radians)
return radians * 180 / M_PI;
}
+mbgl::util::UnitBezier MGLUnitBezierForMediaTimingFunction(CAMediaTimingFunction *function)
+{
+ if ( ! function)
+ {
+ function = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
+ }
+ float p1[2], p2[2];
+ [function getControlPointAtIndex:0 values:p1];
+ [function getControlPointAtIndex:1 values:p2];
+ return { p1[0], p1[1], p2[0], p2[1] };
+}
+
#pragma mark - Private -
@interface MGLMapView () <UIGestureRecognizerDelegate, GLKViewDelegate, CLLocationManagerDelegate, UIActionSheetDelegate>
@@ -368,7 +383,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
// set initial position
//
- _mbglMap->setLatLngZoom(mbgl::LatLng(0, 0), _mbglMap->getMinZoom());
+ mbgl::CameraOptions options;
+ options.center = mbgl::LatLng(0, 0);
+ options.zoom = _mbglMap->getMinZoom();
+ _mbglMap->jumpTo(options);
_pendingLatitude = NAN;
_pendingLongitude = NAN;
@@ -1347,12 +1365,10 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
else if (twoFingerDrag.state == UIGestureRecognizerStateBegan || twoFingerDrag.state == UIGestureRecognizerStateChanged)
{
CGFloat gestureDistance = CGPoint([twoFingerDrag translationInView:twoFingerDrag.view]).y;
- double currentPitch = _mbglMap->getPitch();
- double minPitch = 0;
- double maxPitch = 60.0;
- double slowdown = 20.0;
+ CGFloat currentPitch = _mbglMap->getPitch();
+ CGFloat slowdown = 20.0;
- double pitchNew = fmax(fmin(currentPitch - (gestureDistance / slowdown), maxPitch), minPitch);
+ CGFloat pitchNew = mbgl::util::clamp(currentPitch - (gestureDistance / slowdown), MGLMinimumPitch, MGLMaximumPitch);
_mbglMap->setPitch(pitchNew);
}
@@ -1530,7 +1546,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate
{
- return [NSSet setWithObjects:@"latitude", @"longitude", nil];
+ return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil];
}
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated preservingTracking:(BOOL)tracking
@@ -1542,13 +1558,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
- (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated
{
- CGFloat duration = (animated ? MGLAnimationDuration : 0);
-
- _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(coordinate),
- fmaxf(_mbglMap->getZoom(), self.currentMinimumZoom),
- secondsAsDuration(duration));
-
- [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
+ [self setCenterCoordinate:coordinate zoomLevel:self.zoomLevel animated:animated];
}
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
@@ -1563,35 +1573,45 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated
{
+ [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated];
+}
+
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated
+{
self.userTrackingMode = MGLUserTrackingModeNone;
- CGFloat duration = (animated ? MGLAnimationDuration : 0);
+ [self _setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:direction animated:animated];
+}
- _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(centerCoordinate), zoomLevel, secondsAsDuration(duration));
+- (void)_setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated
+{
+ mbgl::CameraOptions options;
+ options.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate);
+ options.zoom = fmaxf(zoomLevel, self.currentMinimumZoom);
+ if (direction >= 0)
+ {
+ options.angle = MGLRadiansFromDegrees(-direction);
+ }
+ if (animated)
+ {
+ options.duration = secondsAsDuration(MGLAnimationDuration);
+ options.easing = MGLUnitBezierForMediaTimingFunction(nil);
+ }
+ _mbglMap->easeTo(options);
[self unrotateIfNeededAnimated:animated];
[self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
}
-- (double)zoomLevel
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomLevel
{
- return _mbglMap->getZoom();
+ return [NSSet setWithObject:@"camera"];
}
-- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated
+- (double)zoomLevel
{
- self.userTrackingMode = MGLUserTrackingModeNone;
-
- CGFloat duration = (animated ? MGLAnimationDuration : 0);
-
- _mbglMap->setLatLngZoom(_mbglMap->getLatLng(),
- fmaxf(zoomLevel, self.currentMinimumZoom),
- secondsAsDuration(duration));
-
- [self unrotateIfNeededAnimated:animated];
-
- [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
+ return _mbglMap->getZoom();
}
- (void)setZoomLevel:(double)zoomLevel
@@ -1599,6 +1619,11 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
[self setZoomLevel:zoomLevel animated:NO];
}
+- (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated
+{
+ [self setCenterCoordinate:self.centerCoordinate zoomLevel:zoomLevel animated:animated];
+}
+
MGLCoordinateBounds MGLCoordinateBoundsFromLatLngBounds(mbgl::LatLngBounds latLngBounds)
{
return MGLCoordinateBoundsMake(MGLLocationCoordinate2DFromLatLng(latLngBounds.sw),
@@ -1639,11 +1664,34 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
animated:animated];
}
+- (void)setVisibleCoordinateBounds:(MGLCoordinateBounds)bounds edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated
+{
+ CLLocationCoordinate2D coordinates[] = {
+ {bounds.ne.latitude, bounds.sw.longitude},
+ bounds.sw,
+ {bounds.sw.latitude, bounds.ne.longitude},
+ bounds.ne,
+ };
+ [self setVisibleCoordinates:coordinates
+ count:sizeof(coordinates) / sizeof(coordinates[0])
+ edgePadding:insets
+ direction:direction
+ animated:animated];
+}
+
- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets animated:(BOOL)animated
{
+ [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:self.direction animated:animated];
+}
+
+- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction animated:(BOOL)animated
+{
+ [self setVisibleCoordinates:coordinates count:count edgePadding:insets direction:direction duration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
+}
+
+- (void)setVisibleCoordinates:(CLLocationCoordinate2D *)coordinates count:(NSUInteger)count edgePadding:(UIEdgeInsets)insets direction:(CLLocationDirection)direction duration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function
+{
// NOTE: does not disrupt tracking mode
- CGFloat duration = animated ? MGLAnimationDuration : 0;
-
[self willChangeValueForKey:@"visibleCoordinateBounds"];
mbgl::EdgeInsets mbglInsets = {insets.top, insets.left, insets.bottom, insets.right};
mbgl::AnnotationSegment segment;
@@ -1652,12 +1700,27 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
{
segment.push_back({coordinates[i].latitude, coordinates[i].longitude});
}
- _mbglMap->fitBounds(segment, mbglInsets, secondsAsDuration(duration));
+ mbgl::CameraOptions options = _mbglMap->cameraForLatLngs(segment, mbglInsets);
+ if (direction >= 0)
+ {
+ options.angle = MGLRadiansFromDegrees(-direction);
+ }
+ if (duration > 0)
+ {
+ options.duration = secondsAsDuration(duration);
+ options.easing = MGLUnitBezierForMediaTimingFunction(function);
+ }
+ _mbglMap->easeTo(options);
[self didChangeValueForKey:@"visibleCoordinateBounds"];
- [self unrotateIfNeededAnimated:animated];
+ [self unrotateIfNeededAnimated:duration > 0];
- [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
+ [self notifyMapChange:(duration > 0 ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)];
+}
+
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingDirection
+{
+ return [NSSet setWithObject:@"camera"];
}
- (CLLocationDirection)direction
@@ -1683,23 +1746,139 @@ mbgl::LatLngBounds MGLLatLngBoundsFromCoordinateBounds(MGLCoordinateBounds coord
[self setDirection:direction animated:NO];
}
-- (double)pitch
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingPitch
{
- return _mbglMap->getPitch();
+ return [NSSet setWithObject:@"camera"];
}
-- (void)setPitch:(double)pitch
++ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCamera
{
- // constrain pitch to between 0º and 60º
- //
- _mbglMap->setPitch(fmax(fmin(pitch, 60), 0));
+ return [NSSet setWithObjects:@"longitude", @"latitude", @"centerCoordinate", @"zoomLevel", @"direction", nil];
+}
+
+- (MGLMapCamera *)camera
+{
+ CGRect frame = self.frame;
+ CGPoint edgePoint;
+ // Constrain by the shorter of the two axes.
+ if (frame.size.width > frame.size.height) // landscape
+ {
+ edgePoint = CGPointMake(0, frame.size.height / 2.);
+ }
+ else // portrait
+ {
+ edgePoint = CGPointMake(frame.size.width / 2., 0);
+ }
+ CLLocationCoordinate2D edgeCoordinate = [self convertPoint:edgePoint toCoordinateFromView:self];
+ mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(edgeCoordinate));
+
+ // Because we constrain the zoom level vertically in portrait orientation,
+ // the visible medial span is affected by pitch: the distance from the
+ // center point to the near edge is less than than distance from the center
+ // point to the far edge. Average the two distances.
+ mbgl::ProjectedMeters nearEdgeMeters;
+ if (frame.size.width > frame.size.height)
+ {
+ nearEdgeMeters = edgeMeters;
+ }
+ else
+ {
+ CGPoint nearEdgePoint = CGPointMake(frame.size.width / 2., frame.size.height);
+ CLLocationCoordinate2D nearEdgeCoordinate = [self convertPoint:nearEdgePoint toCoordinateFromView:self];
+ nearEdgeMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(nearEdgeCoordinate));
+ }
+
+ // The opposite side is the distance between the center and one edge.
+ CLLocationCoordinate2D centerCoordinate = self.centerCoordinate;
+ mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(MGLLatLngFromLocationCoordinate2D(centerCoordinate));
+ CLLocationDistance centerToEdge = std::hypot(centerMeters.easting - edgeMeters.easting,
+ centerMeters.northing - edgeMeters.northing);
+ CLLocationDistance centerToNearEdge = std::hypot(centerMeters.easting - nearEdgeMeters.easting,
+ centerMeters.northing - nearEdgeMeters.northing);
+ CLLocationDistance altitude = (centerToEdge + centerToNearEdge) / 2 / std::tan(MGLAngularFieldOfView / 2.);
- //[self notifyMapChange:(mbgl::MapChangeRegionDidChange)];
+ CGFloat pitch = _mbglMap->getPitch();
+
+ return [MGLMapCamera cameraLookingAtCenterCoordinate:centerCoordinate
+ fromDistance:altitude
+ pitch:pitch
+ heading:self.direction];
}
-- (void)resetPitch
+- (void)setCamera:(MGLMapCamera *)camera
{
- [self setPitch:0];
+ [self setCamera:camera animated:NO];
+}
+
+- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated
+{
+ [self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil];
+}
+
+- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(CAMediaTimingFunction *)function
+{
+ // The opposite side is the distance between the center and one edge.
+ mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(camera.centerCoordinate);
+ mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng);
+ CLLocationDistance centerToEdge = camera.altitude * std::tan(MGLAngularFieldOfView / 2.);
+
+ double angle = -1;
+ if (camera.heading >= 0)
+ {
+ angle = MGLRadiansFromDegrees(mbgl::util::wrap(-camera.heading, 0., 360.));
+ }
+ double pitch = -1;
+ if (camera.pitch >= 0)
+ {
+ pitch = MGLRadiansFromDegrees(mbgl::util::clamp(camera.pitch, MGLMinimumPitch, MGLMaximumPitch));
+ }
+
+ // Make a visible bounds that extends in the constrained direction (the
+ // shorter of the two axes).
+ CGRect frame = self.frame;
+ mbgl::LatLng sw, ne;
+ if (frame.size.width > frame.size.height) // landscape
+ {
+ sw = _mbglMap->latLngForProjectedMeters({
+ centerMeters.northing - centerToEdge * std::sin(angle),
+ centerMeters.easting - centerToEdge * std::cos(angle),
+ });
+ ne = _mbglMap->latLngForProjectedMeters({
+ centerMeters.northing + centerToEdge * std::sin(angle),
+ centerMeters.easting + centerToEdge * std::cos(angle),
+ });
+ }
+ else // portrait
+ {
+ sw = _mbglMap->latLngForProjectedMeters({
+ centerMeters.northing - centerToEdge * std::cos(angle) + centerToEdge * std::cos(angle) * std::sin(pitch) / 2,
+ centerMeters.easting - centerToEdge * std::sin(angle) + centerToEdge * std::sin(angle) * std::sin(pitch) / 2,
+ });
+ ne = _mbglMap->latLngForProjectedMeters({
+ centerMeters.northing + centerToEdge * std::cos(angle) - centerToEdge * std::cos(angle) * std::sin(pitch) / 2,
+ centerMeters.easting + centerToEdge * std::sin(angle) - centerToEdge * std::sin(angle) * std::sin(pitch) / 2,
+ });
+ }
+
+ // Fit the viewport to the bounds. Correct the center in case pitch should
+ // cause the visual center to lie above the screen center.
+ mbgl::CameraOptions options = _mbglMap->cameraForLatLngs({ sw, ne }, {});
+ options.center = centerLatLng;
+
+ if (angle >= 0)
+ {
+ options.angle = angle;
+ }
+ if (pitch >= 0)
+ {
+ options.pitch = pitch;
+ }
+ if (duration > 0)
+ {
+ options.duration = secondsAsDuration(duration);
+ options.easing = MGLUnitBezierForMediaTimingFunction(function);
+ }
+ _mbglMap->easeTo(options);
}
- (CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(nullable UIView *)view
@@ -2482,6 +2661,12 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
[self.delegate mapView:self didUpdateUserLocation:self.userLocation];
}
}
+
+ CLLocationDirection course = self.userLocation.location.course;
+ if (course < 0 || self.userTrackingMode != MGLUserTrackingModeFollowWithCourse)
+ {
+ course = -1;
+ }
if (self.userTrackingMode != MGLUserTrackingModeNone)
{
@@ -2496,7 +2681,7 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
{
// at sufficient detail, just re-center the map; don't zoom
//
- [self setCenterCoordinate:self.userLocation.location.coordinate animated:YES preservingTracking:YES];
+ [self _setCenterCoordinate:self.userLocation.location.coordinate zoomLevel:self.zoomLevel direction:course animated:YES];
}
else
{
@@ -2526,17 +2711,11 @@ CLLocationCoordinate2D MGLLocationCoordinate2DFromLatLng(mbgl::LatLng latLng)
desiredSouthWest.longitude != actualSouthWest.longitude)
{
// assumes we won't disrupt tracking mode
- [self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) animated:YES];
+ [self setVisibleCoordinateBounds:MGLCoordinateBoundsMake(desiredSouthWest, desiredNorthEast) edgePadding:UIEdgeInsetsZero direction:course animated:YES];
}
}
}
}
-
- CLLocationDirection course = self.userLocation.location.course;
- if (course >= 0 && self.userTrackingMode == MGLUserTrackingModeFollowWithCourse)
- {
- _mbglMap->setBearing(course);
- }
self.userLocationAnnotationView.haloLayer.hidden = ! CLLocationCoordinate2DIsValid(self.userLocation.coordinate) ||
newLocation.horizontalAccuracy > 10;
@@ -3088,7 +3267,7 @@ class MBGLView : public mbgl::View
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude
{
- return [NSSet setWithObject:@"centerCoordinate"];
+ return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
}
- (double)latitude
@@ -3114,7 +3293,7 @@ class MBGLView : public mbgl::View
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude
{
- return [NSSet setWithObject:@"centerCoordinate"];
+ return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
}
- (double)longitude