summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMinh Nguyễn <mxn@1ec5.org>2015-12-05 22:22:46 -0800
committerMinh Nguyễn <mxn@1ec5.org>2015-12-13 17:26:53 -0800
commit0fb2b855a4539803f42a4048257da606f66d9f4e (patch)
tree7be98f423e47a40aa6d011c5e52751d0896125e0
parent3f2fae521cde0cc07796f65b33a227875466dc8d (diff)
downloadqtlocation-mapboxgl-0fb2b855a4539803f42a4048257da606f66d9f4e.tar.gz
[osx] Map camera
Shared MGLMapCamera between iOS and OS X. Unfortunately -camera and -setCamera: implementations need to be copy-pasted for now.
-rw-r--r--gyp/platform-ios.gypi4
-rw-r--r--gyp/platform-osx.gypi1
-rw-r--r--include/mbgl/darwin/MGLMapCamera.h (renamed from include/mbgl/ios/MGLMapCamera.h)0
-rw-r--r--include/mbgl/osx/MGLMapView.h26
-rw-r--r--platform/darwin/MGLMapCamera.mm (renamed from platform/ios/MGLMapCamera.mm)0
-rw-r--r--platform/osx/sdk/Mapbox.h1
-rw-r--r--platform/osx/sdk/Mapbox.m1
-rw-r--r--platform/osx/src/MGLMapView+IBAdditions.m4
-rw-r--r--platform/osx/src/MGLMapView.mm160
-rw-r--r--platform/osx/src/MGLMapView_Private.h6
10 files changed, 197 insertions, 6 deletions
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index d7e85a9516..9ae44b2d45 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -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',
diff --git a/gyp/platform-osx.gypi b/gyp/platform-osx.gypi
index 281a8a5738..6ad948c27f 100644
--- a/gyp/platform-osx.gypi
+++ b/gyp/platform-osx.gypi
@@ -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',
diff --git a/include/mbgl/ios/MGLMapCamera.h b/include/mbgl/darwin/MGLMapCamera.h
index 7c00ad8665..7c00ad8665 100644
--- a/include/mbgl/ios/MGLMapCamera.h
+++ b/include/mbgl/darwin/MGLMapCamera.h
diff --git a/include/mbgl/osx/MGLMapView.h b/include/mbgl/osx/MGLMapView.h
index 0a6a43ed12..41f7e02c84 100644
--- a/include/mbgl/osx/MGLMapView.h
+++ b/include/mbgl/osx/MGLMapView.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
diff --git a/platform/ios/MGLMapCamera.mm b/platform/darwin/MGLMapCamera.mm
index d04e46fa90..d04e46fa90 100644
--- a/platform/ios/MGLMapCamera.mm
+++ b/platform/darwin/MGLMapCamera.mm
diff --git a/platform/osx/sdk/Mapbox.h b/platform/osx/sdk/Mapbox.h
index fdc667b56a..ca09fbd641 100644
--- a/platform/osx/sdk/Mapbox.h
+++ b/platform/osx/sdk/Mapbox.h
@@ -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>
diff --git a/platform/osx/sdk/Mapbox.m b/platform/osx/sdk/Mapbox.m
index 1ac5a2d81e..a96ec6df2d 100644
--- a/platform/osx/sdk/Mapbox.m
+++ b/platform/osx/sdk/Mapbox.m
@@ -19,6 +19,7 @@ static void InitializeMapbox() {
[MGLAccountManager class];
[MGLAnnotationImage class];
+ [MGLMapCamera class];
[MGLMapView class];
[MGLMultiPoint class];
[MGLPointAnnotation class];
diff --git a/platform/osx/src/MGLMapView+IBAdditions.m b/platform/osx/src/MGLMapView+IBAdditions.m
index aaa4ddf1bb..2d37d29037 100644
--- a/platform/osx/src/MGLMapView+IBAdditions.m
+++ b/platform/osx/src/MGLMapView+IBAdditions.m
@@ -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 {
diff --git a/platform/osx/src/MGLMapView.mm b/platform/osx/src/MGLMapView.mm
index 83b9bf009b..0399e7fec8 100644
--- a/platform/osx/src/MGLMapView.mm
+++ b/platform/osx/src/MGLMapView.mm
@@ -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];
}
diff --git a/platform/osx/src/MGLMapView_Private.h b/platform/osx/src/MGLMapView_Private.h
index d9d42c1b35..77f2aab323 100644
--- a/platform/osx/src/MGLMapView_Private.h
+++ b/platform/osx/src/MGLMapView_Private.h
@@ -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.