[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:
Minh Nguyễn
2015-12-05 22:22:46 -08:00
committed by Minh Nguyễn
parent 3f2fae521c
commit 0fb2b855a4
10 changed files with 197 additions and 6 deletions

View File

@@ -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',

View File

@@ -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',

View File

@@ -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

View File

@@ -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>

View File

@@ -19,6 +19,7 @@ static void InitializeMapbox() {
[MGLAccountManager class];
[MGLAnnotationImage class];
[MGLMapCamera class];
[MGLMapView class];
[MGLMultiPoint class];
[MGLPointAnnotation class];

View File

@@ -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 {

View File

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

View File

@@ -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.