diff options
Diffstat (limited to 'platform/ios/MGLMapView.mm')
-rw-r--r-- | platform/ios/MGLMapView.mm | 287 |
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 |