[osx] Map camera
Shared MGLMapCamera between iOS and OS X. Unfortunately -camera and -setCamera: implementations need to be copy-pasted for now.
This commit is contained in:
committed by
Minh Nguyễn
parent
3f2fae521c
commit
0fb2b855a4
@@ -43,11 +43,11 @@
|
||||
'../platform/darwin/MGLPolyline.mm',
|
||||
'../include/mbgl/darwin/MGLPolygon.h',
|
||||
'../platform/darwin/MGLPolygon.mm',
|
||||
'../include/mbgl/darwin/MGLMapCamera.h',
|
||||
'../platform/darwin/MGLMapCamera.mm',
|
||||
'../include/mbgl/ios/Mapbox.h',
|
||||
'../platform/ios/MGLMapboxEvents.h',
|
||||
'../platform/ios/MGLMapboxEvents.m',
|
||||
'../include/mbgl/ios/MGLMapCamera.h',
|
||||
'../platform/ios/MGLMapCamera.mm',
|
||||
'../include/mbgl/ios/MGLMapView.h',
|
||||
'../include/mbgl/ios/MGLMapView+IBAdditions.h',
|
||||
'../platform/ios/MGLMapView.mm',
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
'../platform/darwin/MGLPointAnnotation.m',
|
||||
'../platform/darwin/MGLPolyline.mm',
|
||||
'../platform/darwin/MGLPolygon.mm',
|
||||
'../platform/darwin/MGLMapCamera.mm',
|
||||
'../platform/osx/src/MGLAccountManager_Private.h',
|
||||
'../platform/osx/src/MGLAccountManager.m',
|
||||
'../platform/osx/src/MGLMapView_Private.h',
|
||||
|
||||
@@ -23,6 +23,7 @@ typedef NS_OPTIONS(NSUInteger, MGLMapDebugMaskOptions) {
|
||||
};
|
||||
|
||||
@class MGLAnnotationImage;
|
||||
@class MGLMapCamera;
|
||||
|
||||
@protocol MGLAnnotation;
|
||||
@protocol MGLMapViewDelegate;
|
||||
@@ -223,6 +224,31 @@ IB_DESIGNABLE
|
||||
coordinate or zoom level. */
|
||||
- (void)setDirection:(CLLocationDirection)direction animated:(BOOL)animated;
|
||||
|
||||
/** A camera representing the current viewpoint of the map. */
|
||||
@property (nonatomic, copy) MGLMapCamera *camera;
|
||||
|
||||
/** Moves the viewpoint to a different location with respect to the map with an
|
||||
optional transition animation.
|
||||
|
||||
@param camera The new viewpoint.
|
||||
@param animated Specify `YES` if you want the map view to animate the change
|
||||
to the new viewpoint or `NO` if you want the map to display the new
|
||||
viewpoint immediately. */
|
||||
- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated;
|
||||
|
||||
/** Moves the viewpoint to a different location with respect to the map with an
|
||||
optional transition duration and timing function.
|
||||
|
||||
@param camera The new viewpoint.
|
||||
@param duration The amount of time, measured in seconds, that the transition
|
||||
animation should take. Specify `0` to jump to the new viewpoint
|
||||
instantaneously.
|
||||
@param function A timing function used for the animation. Set this parameter
|
||||
to `nil` for a transition that matches most system animations. If the
|
||||
duration is `0`, this parameter is ignored.
|
||||
@param completion The block to execute after the animation finishes. */
|
||||
- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion;
|
||||
|
||||
/** The geographic coordinate bounds visible in the receiver’s viewport.
|
||||
|
||||
Changing the value of this property updates the receiver immediately. If you
|
||||
|
||||
@@ -10,6 +10,7 @@ FOUNDATION_EXPORT const unsigned char MapboxVersionString[];
|
||||
#import <Mapbox/MGLAnnotation.h>
|
||||
#import <Mapbox/MGLAnnotationImage.h>
|
||||
#import <Mapbox/MGLGeometry.h>
|
||||
#import <Mapbox/MGLMapCamera.h>
|
||||
#import <Mapbox/MGLMapView.h>
|
||||
#import <Mapbox/MGLMapView+IBAdditions.h>
|
||||
#import <Mapbox/MGLMapViewDelegate.h>
|
||||
|
||||
@@ -19,6 +19,7 @@ static void InitializeMapbox() {
|
||||
|
||||
[MGLAccountManager class];
|
||||
[MGLAnnotationImage class];
|
||||
[MGLMapCamera class];
|
||||
[MGLMapView class];
|
||||
[MGLMultiPoint class];
|
||||
[MGLPointAnnotation class];
|
||||
|
||||
@@ -26,7 +26,7 @@ void mgl_linkMapViewIBCategory() {}
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLatitude {
|
||||
return [NSSet setWithObjects:@"centerCoordinate", nil];
|
||||
return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
|
||||
}
|
||||
|
||||
- (double)latitude {
|
||||
@@ -48,7 +48,7 @@ void mgl_linkMapViewIBCategory() {}
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingLongitude {
|
||||
return [NSSet setWithObjects:@"centerCoordinate", nil];
|
||||
return [NSSet setWithObjects:@"centerCoordinate", @"camera", nil];
|
||||
}
|
||||
|
||||
- (double)longitude {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#import "../../darwin/MGLGeometry_Private.h"
|
||||
#import "../../darwin/MGLMultiPoint_Private.h"
|
||||
|
||||
#import <mbgl/darwin/MGLMapCamera.h>
|
||||
#import <mbgl/darwin/MGLPolygon.h>
|
||||
#import <mbgl/darwin/MGLPolyline.h>
|
||||
#import <mbgl/osx/MGLAnnotationImage.h>
|
||||
@@ -48,6 +49,15 @@ const CGFloat MGLOrnamentOpacity = 0.9;
|
||||
/// Default duration for programmatic animations.
|
||||
const NSTimeInterval MGLAnimationDuration = 0.3;
|
||||
|
||||
/// Angular field of view for determining altitude of viewpoint.
|
||||
const CLLocationDegrees MGLAngularFieldOfView = M_PI / 6.;
|
||||
|
||||
/// Minimum allowed pitch in degrees.
|
||||
const CGFloat MGLMinimumPitch = 0;
|
||||
|
||||
/// Maximum allowed pitch in degrees.
|
||||
const CGFloat MGLMaximumPitch = 60;
|
||||
|
||||
/// Distance in points that a single press of the panning keyboard shortcut pans the map by.
|
||||
const CGFloat MGLKeyPanningIncrement = 150;
|
||||
|
||||
@@ -101,6 +111,17 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(NSTimeInterval duration
|
||||
std::chrono::duration<NSTimeInterval, std::chrono::seconds::period>(duration));
|
||||
}
|
||||
|
||||
/// Converts a media timing function into a unit bezier object usable in mbgl.
|
||||
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] };
|
||||
}
|
||||
|
||||
/// Converts the given color into an mbgl::Color in calibrated RGB space.
|
||||
mbgl::Color MGLColorObjectFromNSColor(NSColor *color) {
|
||||
if (!color) {
|
||||
@@ -209,7 +230,7 @@ public:
|
||||
}
|
||||
|
||||
+ (NSArray *)restorableStateKeyPaths {
|
||||
return @[@"latitude", @"longitude", @"zoomLevel", @"direction"];
|
||||
return @[@"camera"];
|
||||
}
|
||||
|
||||
- (void)commonInit {
|
||||
@@ -773,7 +794,7 @@ public:
|
||||
#pragma mark Viewport
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCenterCoordinate {
|
||||
return [NSSet setWithObjects:@"latitude", @"longitude", nil];
|
||||
return [NSSet setWithObjects:@"latitude", @"longitude", @"camera", nil];
|
||||
}
|
||||
|
||||
- (CLLocationCoordinate2D)centerCoordinate {
|
||||
@@ -815,6 +836,10 @@ public:
|
||||
_pendingLongitude = pendingLongitude;
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingZoomLevel {
|
||||
return [NSSet setWithObject:@"camera"];
|
||||
}
|
||||
|
||||
- (double)zoomLevel {
|
||||
return _mbglMap->getZoom();
|
||||
}
|
||||
@@ -858,6 +883,10 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingDirection {
|
||||
return [NSSet setWithObject:@"camera"];
|
||||
}
|
||||
|
||||
- (CLLocationDirection)direction {
|
||||
return mbgl::util::wrap(_mbglMap->getBearing(), 0., 360.);
|
||||
}
|
||||
@@ -879,6 +908,133 @@ public:
|
||||
[self didChangeValueForKey:@"direction"];
|
||||
}
|
||||
|
||||
+ (NS_SET_OF(NSString *) *)keyPathsForValuesAffectingCamera {
|
||||
return [NSSet setWithObjects:@"latitude", @"longitude", @"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);
|
||||
}
|
||||
mbgl::LatLng edgeLatLng = [self convertPoint:edgePoint toLatLngFromView:self];
|
||||
mbgl::ProjectedMeters edgeMeters = _mbglMap->projectedMetersForLatLng(edgeLatLng);
|
||||
|
||||
// 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);
|
||||
mbgl::LatLng nearEdgeLatLng = [self convertPoint:nearEdgePoint toLatLngFromView:self];
|
||||
nearEdgeMeters = _mbglMap->projectedMetersForLatLng(nearEdgeLatLng);
|
||||
}
|
||||
|
||||
// The opposite side is the distance between the center and one edge.
|
||||
mbgl::LatLng centerLatLng = MGLLatLngFromLocationCoordinate2D(self.centerCoordinate);
|
||||
mbgl::ProjectedMeters centerMeters = _mbglMap->projectedMetersForLatLng(centerLatLng);
|
||||
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.);
|
||||
|
||||
CGFloat pitch = _mbglMap->getPitch();
|
||||
|
||||
return [MGLMapCamera cameraLookingAtCenterCoordinate:self.centerCoordinate
|
||||
fromDistance:altitude
|
||||
pitch:pitch
|
||||
heading:self.direction];
|
||||
}
|
||||
|
||||
- (void)setCamera:(MGLMapCamera *)camera {
|
||||
[self setCamera:camera animated:NO];
|
||||
}
|
||||
|
||||
- (void)setCamera:(MGLMapCamera *)camera animated:(BOOL)animated {
|
||||
[self setCamera:camera withDuration:animated ? MGLAnimationDuration : 0 animationTimingFunction:nil completionHandler:NULL];
|
||||
}
|
||||
|
||||
- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function completionHandler:(nullable void (^)(void))completion {
|
||||
_mbglMap->cancelTransitions();
|
||||
|
||||
// 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(-camera.heading);
|
||||
}
|
||||
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 (camera.heading >= 0) {
|
||||
options.angle = angle;
|
||||
}
|
||||
if (pitch >= 0) {
|
||||
options.pitch = pitch;
|
||||
}
|
||||
if (duration > 0) {
|
||||
options.duration = MGLDurationInSeconds(duration);
|
||||
options.easing = MGLUnitBezierForMediaTimingFunction(function);
|
||||
}
|
||||
if (completion) {
|
||||
options.transitionFinishFn = [completion]() {
|
||||
// Must run asynchronously after the transition is completely over.
|
||||
// Otherwise, a call to -setCamera: within the completion handler
|
||||
// would reenter the completion handler’s caller.
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
completion();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
[self willChangeValueForKey:@"camera"];
|
||||
_mbglMap->easeTo(options);
|
||||
[self didChangeValueForKey:@"camera"];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingVisibleCoordinateBounds {
|
||||
return [NSSet setWithObjects:@"centerCoordinate", @"zoomLevel", @"direction", @"bounds", nil];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,12 @@ void mgl_linkMapViewIBCategory();
|
||||
/// actively drawing.
|
||||
@property (nonatomic, readonly, getter=isDormant) BOOL dormant;
|
||||
|
||||
// These properties exist because initially, both the latitude and longitude are
|
||||
// NaN. You have to set both the latitude and longitude simultaneously. If you
|
||||
// set the latitude but reuse the current longitude, and the current longitude
|
||||
// happens to be NaN, there will be no change because the resulting coordinate
|
||||
// pair is invalid.
|
||||
|
||||
/// Center latitude set independently of the center longitude in an inspectable.
|
||||
@property (nonatomic) CLLocationDegrees pendingLatitude;
|
||||
/// Center longitude set independently of the center latitude in an inspectable.
|
||||
|
||||
Reference in New Issue
Block a user