summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gyp/platform-ios.gypi2
-rw-r--r--include/mbgl/ios/MGLMapCamera.h36
-rw-r--r--include/mbgl/ios/MGLMapView.h26
-rw-r--r--include/mbgl/ios/Mapbox.h1
-rw-r--r--include/mbgl/map/camera.hpp22
-rw-r--r--include/mbgl/map/map.hpp11
-rw-r--r--include/mbgl/util/optional.hpp (renamed from src/mbgl/util/optional.hpp)0
-rw-r--r--include/mbgl/util/unitbezier.hpp (renamed from src/mbgl/util/unitbezier.hpp)0
-rw-r--r--ios/app/MBXViewController.mm68
-rw-r--r--ios/app/mapboxgl-app.gypi1
-rw-r--r--platform/ios/MGLMapCamera.mm101
-rw-r--r--platform/ios/MGLMapView.mm287
-rw-r--r--src/mbgl/map/camera.cpp1
-rw-r--r--src/mbgl/map/map.cpp42
-rw-r--r--src/mbgl/map/transform.cpp182
-rw-r--r--src/mbgl/map/transform.hpp11
16 files changed, 588 insertions, 203 deletions
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index 516d5e8322..6234f4d885 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -20,6 +20,8 @@
'../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/include/mbgl/ios/MGLMapCamera.h b/include/mbgl/ios/MGLMapCamera.h
new file mode 100644
index 0000000000..68f3923fd3
--- /dev/null
+++ b/include/mbgl/ios/MGLMapCamera.h
@@ -0,0 +1,36 @@
+#import "Mapbox.h"
+
+#pragma once
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** An `MGLMapCamera` object represents a viewpoint from which the user observes some point on an `MGLMapView`. */
+@interface MGLMapCamera : NSObject <NSSecureCoding, NSCopying>
+
+/** Coordinate at the center of the map view. */
+@property (nonatomic) CLLocationCoordinate2D centerCoordinate;
+
+/** Heading measured in degrees clockwise from true north. */
+@property (nonatomic) CLLocationDirection heading;
+
+/** Pitch toward the horizon measured in degrees, with 0 degrees resulting in a two-dimensional map. */
+@property (nonatomic) CGFloat pitch;
+
+/** Meters above ground level. */
+@property (nonatomic) CLLocationDistance altitude;
+
+/** Returns a new camera with all properties set to 0. */
++ (instancetype)camera;
+
++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
+ eyeAltitude:(CLLocationDistance)eyeAltitude;
+
++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ fromDistance:(CLLocationDistance)distance
+ pitch:(CGFloat)pitch
+ heading:(CLLocationDirection)heading;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h
index ba332d9845..da500f1ef6 100644
--- a/include/mbgl/ios/MGLMapView.h
+++ b/include/mbgl/ios/MGLMapView.h
@@ -1,4 +1,5 @@
#import "MGLGeometry.h"
+#import "MGLMapCamera.h"
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
@@ -6,6 +7,7 @@
NS_ASSUME_NONNULL_BEGIN
@class MGLAnnotationImage;
+@class MGLMapCamera;
@class MGLUserLocation;
@class MGLPolyline;
@class MGLPolygon;
@@ -136,6 +138,8 @@ IB_DESIGNABLE
* @param animated Specify `YES` if you want the map view to animate scrolling and zooming to the new location or `NO` if you want the map to display the new location immediately. */
- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated;
+- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated;
+
/** The coordinate bounds visible in the receiver’s viewport.
*
* Changing the value of this property updates the receiver immediately. If you want to animate the change, call `setVisibleCoordinateBounds:animated:` instead. */
@@ -181,19 +185,19 @@ IB_DESIGNABLE
/** Resets the map rotation to a northern heading. */
- (IBAction)resetNorth;
-/** The pitch of the map (measured in degrees).
- *
- * The default value `0` shows a completely flat map. Maximum value is `60`. */
-@property (nonatomic) double pitch;
+/** A camera representing the current viewpoint of the map. */
+@property (nonatomic, copy) MGLMapCamera *camera;
-/** Changes the pitch of the map.
- * @param pitch The pitch of the map (measured in degrees) relative to top-down.
- *
- * Changing the pitch tilts the map without changing the current center coordinate or zoom level. */
-- (void)setPitch:(double)pitch;
+/** 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;
-/** Resets the map pitch to head-on. */
-- (IBAction)resetPitch;
+/** 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. */
+- (void)setCamera:(MGLMapCamera *)camera withDuration:(NSTimeInterval)duration animationTimingFunction:(nullable CAMediaTimingFunction *)function;
#pragma mark - Converting Map Coordinates
diff --git a/include/mbgl/ios/Mapbox.h b/include/mbgl/ios/Mapbox.h
index 401a62e82e..f05f0c8429 100644
--- a/include/mbgl/ios/Mapbox.h
+++ b/include/mbgl/ios/Mapbox.h
@@ -1,6 +1,7 @@
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLAnnotationImage.h"
+#import "MGLMapCamera.h"
#import "MGLGeometry.h"
#import "MGLMapView.h"
#import "MGLMultiPoint.h"
diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp
new file mode 100644
index 0000000000..bd0b353bae
--- /dev/null
+++ b/include/mbgl/map/camera.hpp
@@ -0,0 +1,22 @@
+#ifndef MBGL_MAP_CAMERA
+#define MBGL_MAP_CAMERA
+
+#include <mbgl/util/geo.hpp>
+#include <mbgl/util/optional.hpp>
+#include <mbgl/util/chrono.hpp>
+#include <mbgl/util/unitbezier.hpp>
+
+namespace mbgl {
+
+struct CameraOptions {
+ mapbox::util::optional<LatLng> center;
+ mapbox::util::optional<double> zoom;
+ mapbox::util::optional<double> angle;
+ mapbox::util::optional<double> pitch;
+ mapbox::util::optional<Duration> duration;
+ mapbox::util::optional<mbgl::util::UnitBezier> easing;
+};
+
+}
+
+#endif /* MBGL_MAP_CAMERA */
diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp
index ddf86045ee..4cd9293c9b 100644
--- a/include/mbgl/map/map.hpp
+++ b/include/mbgl/map/map.hpp
@@ -2,6 +2,7 @@
#define MBGL_MAP_MAP
#include <mbgl/util/chrono.hpp>
+#include <mbgl/map/camera.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/map/mode.hpp>
#include <mbgl/util/geo.hpp>
@@ -94,6 +95,10 @@ public:
void cancelTransitions();
void setGestureInProgress(bool);
+ // Camera
+ void jumpTo(CameraOptions options);
+ void easeTo(CameraOptions options);
+
// Position
void moveBy(double dx, double dy, const Duration& = Duration::zero());
void setLatLng(LatLng latLng, const Duration& = Duration::zero());
@@ -107,8 +112,8 @@ public:
void setZoom(double zoom, const Duration& = Duration::zero());
double getZoom() const;
void setLatLngZoom(LatLng latLng, double zoom, const Duration& = Duration::zero());
- void fitBounds(LatLngBounds bounds, EdgeInsets padding, const Duration& duration = Duration::zero());
- void fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duration& duration = Duration::zero());
+ CameraOptions cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding);
+ CameraOptions cameraForLatLngs(std::vector<LatLng> latLngs, EdgeInsets padding);
void resetZoom();
double getMinZoom() const;
double getMaxZoom() const;
@@ -121,7 +126,7 @@ public:
void resetNorth();
// Pitch
- void setPitch(double pitch);
+ void setPitch(double pitch, const Duration& = Duration::zero());
double getPitch() const;
// Size
diff --git a/src/mbgl/util/optional.hpp b/include/mbgl/util/optional.hpp
index 8d46eae857..8d46eae857 100644
--- a/src/mbgl/util/optional.hpp
+++ b/include/mbgl/util/optional.hpp
diff --git a/src/mbgl/util/unitbezier.hpp b/include/mbgl/util/unitbezier.hpp
index 095e15f809..095e15f809 100644
--- a/src/mbgl/util/unitbezier.hpp
+++ b/include/mbgl/util/unitbezier.hpp
diff --git a/ios/app/MBXViewController.mm b/ios/app/MBXViewController.mm
index af25fa4dc5..b323717702 100644
--- a/ios/app/MBXViewController.mm
+++ b/ios/app/MBXViewController.mm
@@ -2,8 +2,6 @@
#import <mbgl/ios/Mapbox.h>
-#import <mbgl/platform/darwin/settings_nsuserdefaults.hpp>
-
#import <CoreLocation/CoreLocation.h>
static UIColor *const kTintColor = [UIColor colorWithRed:0.120 green:0.550 blue:0.670 alpha:1.000];
@@ -26,10 +24,20 @@ static NSUInteger const kStyleVersion = 8;
@implementation MBXViewController
-mbgl::Settings_NSUserDefaults *settings = nullptr;
-
#pragma mark - Setup
++ (void)initialize
+{
+ if (self == [MBXViewController class])
+ {
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
+ @"userTrackingMode": @(MGLUserTrackingModeNone),
+ @"showsUserLocation": @NO,
+ @"debug": @NO,
+ }];
+ }
+}
+
- (id)init
{
self = [super init];
@@ -38,6 +46,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationWillTerminateNotification object:nil];
}
return self;
@@ -73,7 +82,6 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
target:self
action:@selector(locateUser)];
- settings = new mbgl::Settings_NSUserDefaults();
[self restoreState:nil];
if ( ! settings->showsUserLocation)
@@ -86,30 +94,37 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
- (void)saveState:(__unused NSNotification *)notification
{
- if (self.mapView && settings)
+ if (self.mapView)
{
- settings->longitude = self.mapView.centerCoordinate.longitude;
- settings->latitude = self.mapView.centerCoordinate.latitude;
- settings->zoom = self.mapView.zoomLevel;
- settings->bearing = self.mapView.direction;
- settings->pitch = self.mapView.pitch;
- settings->debug = self.mapView.isDebugActive;
- settings->userTrackingMode = self.mapView.userTrackingMode;
- settings->showsUserLocation = self.mapView.showsUserLocation;
- settings->save();
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSData *archivedCamera = [NSKeyedArchiver archivedDataWithRootObject:self.mapView.camera];
+ [defaults setObject:archivedCamera forKey:@"camera"];
+ [defaults setInteger:self.mapView.userTrackingMode forKey:@"userTrackingMode"];
+ [defaults setBool:self.mapView.showsUserLocation forKey:@"showsUserLocation"];
+ [defaults setBool:self.mapView.debugActive forKey:@"debug"];
+ [defaults synchronize];
}
}
- (void)restoreState:(__unused NSNotification *)notification
{
- if (self.mapView && settings) {
- settings->load();
- [self.mapView setCenterCoordinate:CLLocationCoordinate2DMake(settings->latitude, settings->longitude) zoomLevel:settings->zoom animated:NO];
- self.mapView.direction = settings->bearing;
- self.mapView.pitch = settings->pitch;
- self.mapView.userTrackingMode = settings->userTrackingMode;
- self.mapView.showsUserLocation = settings->showsUserLocation;
- [self.mapView setDebugActive:settings->debug];
+ if (self.mapView) {
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+ NSData *archivedCamera = [defaults objectForKey:@"camera"];
+ MGLMapCamera *camera = archivedCamera ? [NSKeyedUnarchiver unarchiveObjectWithData:archivedCamera] : nil;
+ if (camera)
+ {
+ self.mapView.camera = camera;
+ }
+ NSInteger uncheckedTrackingMode = [defaults integerForKey:@"userTrackingMode"];
+ if (uncheckedTrackingMode >= 0 &&
+ (NSUInteger)uncheckedTrackingMode >= MGLUserTrackingModeNone &&
+ (NSUInteger)uncheckedTrackingMode <= MGLUserTrackingModeFollowWithCourse)
+ {
+ self.mapView.userTrackingMode = (MGLUserTrackingMode)uncheckedTrackingMode;
+ }
+ self.mapView.showsUserLocation = [defaults boolForKey:@"showsUserLocation"];
+ self.mapView.debugActive = [defaults boolForKey:@"debug"];
}
}
@@ -339,12 +354,7 @@ mbgl::Settings_NSUserDefaults *settings = nullptr;
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
- if (settings)
- {
- [self saveState:nil];
- delete settings;
- settings = nullptr;
- }
+ [self saveState:nil];
}
#pragma mark - MGLMapViewDelegate
diff --git a/ios/app/mapboxgl-app.gypi b/ios/app/mapboxgl-app.gypi
index 34381ca158..8407ea7da2 100644
--- a/ios/app/mapboxgl-app.gypi
+++ b/ios/app/mapboxgl-app.gypi
@@ -32,7 +32,6 @@
'./MBXAppDelegate.m',
'./MBXViewController.h',
'./MBXViewController.mm',
- '../../platform/darwin/settings_nsuserdefaults.mm',
],
'xcode_settings': {
diff --git a/platform/ios/MGLMapCamera.mm b/platform/ios/MGLMapCamera.mm
new file mode 100644
index 0000000000..d04e46fa90
--- /dev/null
+++ b/platform/ios/MGLMapCamera.mm
@@ -0,0 +1,101 @@
+#import "MGLMapCamera.h"
+
+#include <mbgl/util/projection.hpp>
+
+@implementation MGLMapCamera
+
++ (BOOL)supportsSecureCoding
+{
+ return YES;
+}
+
++ (instancetype)camera
+{
+ return [[self alloc] init];
+}
+
++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate
+ eyeAltitude:(CLLocationDistance)eyeAltitude
+{
+ mbgl::LatLng centerLatLng = mbgl::LatLng(centerCoordinate.latitude, centerCoordinate.longitude);
+ mbgl::LatLng eyeLatLng = mbgl::LatLng(eyeCoordinate.latitude, eyeCoordinate.longitude);
+
+ mbgl::ProjectedMeters centerMeters = mbgl::Projection::projectedMetersForLatLng(centerLatLng);
+ mbgl::ProjectedMeters eyeMeters = mbgl::Projection::projectedMetersForLatLng(eyeLatLng);
+ CLLocationDirection heading = std::atan((centerMeters.northing - eyeMeters.northing) /
+ (centerMeters.easting - eyeMeters.easting));
+
+ double groundDistance = std::hypot(centerMeters.northing - eyeMeters.northing,
+ centerMeters.easting - eyeMeters.easting);
+ CGFloat pitch = std::atan(eyeAltitude / groundDistance);
+
+ return [[self alloc] initWithCenterCoordinate:centerCoordinate
+ altitude:eyeAltitude
+ pitch:pitch
+ heading:heading];
+}
+
++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ fromDistance:(CLLocationDistance)distance
+ pitch:(CGFloat)pitch
+ heading:(CLLocationDirection)heading
+{
+ return [[self alloc] initWithCenterCoordinate:centerCoordinate
+ altitude:distance
+ pitch:(CGFloat)pitch
+ heading:heading];
+}
+
+- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate
+ altitude:(CLLocationDistance)altitude
+ pitch:(CGFloat)pitch
+ heading:(CLLocationDirection)heading
+{
+ if (self = [super init])
+ {
+ _centerCoordinate = centerCoordinate;
+ _altitude = altitude;
+ _pitch = pitch;
+ _heading = heading;
+ }
+ return self;
+}
+
+- (nullable instancetype)initWithCoder:(NSCoder *)coder
+{
+ if (self = [super init])
+ {
+ _centerCoordinate = CLLocationCoordinate2DMake([coder decodeDoubleForKey:@"centerLatitude"],
+ [coder decodeDoubleForKey:@"centerLongitude"]);
+ _altitude = [coder decodeDoubleForKey:@"altitude"];
+ _pitch = [coder decodeDoubleForKey:@"pitch"];
+ _heading = [coder decodeDoubleForKey:@"heading"];
+ }
+ return self;
+}
+
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+ [coder encodeDouble:_centerCoordinate.latitude forKey:@"centerLatitude"];
+ [coder encodeDouble:_centerCoordinate.longitude forKey:@"centerLongitude"];
+ [coder encodeDouble:_altitude forKey:@"altitude"];
+ [coder encodeDouble:_pitch forKey:@"pitch"];
+ [coder encodeDouble:_heading forKey:@"heading"];
+}
+
+- (id)copyWithZone:(nullable NSZone *)zone
+{
+ return [[[self class] allocWithZone:zone] initWithCenterCoordinate:_centerCoordinate
+ altitude:_altitude
+ pitch:_pitch
+ heading:_heading];
+}
+
+- (NSString *)description
+{
+ return [NSString stringWithFormat:@"<MKMapCamera %p centerCoordinate:%f, %f altitude:%.0fm heading:%.0f° pitch:%.0f°>",
+ self, _centerCoordinate.latitude, _centerCoordinate.longitude, _altitude, _heading, _pitch];
+}
+
+@end
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
diff --git a/src/mbgl/map/camera.cpp b/src/mbgl/map/camera.cpp
new file mode 100644
index 0000000000..4a45e904f8
--- /dev/null
+++ b/src/mbgl/map/camera.cpp
@@ -0,0 +1 @@
+#include <mbgl/map/camera.hpp>
diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp
index 82ab72db2d..9195f6b583 100644
--- a/src/mbgl/map/map.cpp
+++ b/src/mbgl/map/map.cpp
@@ -9,6 +9,7 @@
#include <mbgl/util/projection.hpp>
#include <mbgl/util/thread.hpp>
+#include <mbgl/util/math.hpp>
namespace mbgl {
@@ -114,6 +115,18 @@ void Map::setGestureInProgress(bool inProgress) {
update(Update::Repaint);
}
+#pragma mark -
+
+void Map::jumpTo(CameraOptions options) {
+ transform->jumpTo(options);
+ update(Update::Repaint);
+}
+
+void Map::easeTo(CameraOptions options) {
+ transform->easeTo(options);
+ update(options.zoom ? Update::Zoom : Update::Repaint);
+}
+
#pragma mark - Position
void Map::moveBy(double dx, double dy, const Duration& duration) {
@@ -131,9 +144,11 @@ LatLng Map::getLatLng() const {
}
void Map::resetPosition() {
- transform->setAngle(0);
- transform->setLatLng(LatLng(0, 0));
- transform->setZoom(0);
+ CameraOptions options;
+ options.angle = 0;
+ options.center = LatLng(0, 0);
+ options.zoom = 0;
+ transform->jumpTo(options);
update(Update::Zoom);
}
@@ -168,25 +183,26 @@ void Map::setLatLngZoom(LatLng latLng, double zoom, const Duration& duration) {
update(Update::Zoom);
}
-void Map::fitBounds(LatLngBounds bounds, EdgeInsets padding, const Duration& duration) {
+CameraOptions Map::cameraForLatLngBounds(LatLngBounds bounds, EdgeInsets padding) {
AnnotationSegment segment = {
{bounds.ne.latitude, bounds.sw.longitude},
bounds.sw,
{bounds.sw.latitude, bounds.ne.longitude},
bounds.ne,
};
- fitBounds(segment, padding, duration);
+ return cameraForLatLngs(segment, padding);
}
-void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duration& duration) {
- if (segment.empty()) {
- return;
+CameraOptions Map::cameraForLatLngs(std::vector<LatLng> latLngs, EdgeInsets padding) {
+ CameraOptions options;
+ if (latLngs.empty()) {
+ return options;
}
// Calculate the bounds of the possibly rotated shape with respect to the viewport.
vec2<> nePixel = {-INFINITY, -INFINITY};
vec2<> swPixel = {INFINITY, INFINITY};
- for (LatLng latLng : segment) {
+ for (LatLng latLng : latLngs) {
vec2<> pixel = pixelForLatLng(latLng);
swPixel.x = std::min(swPixel.x, pixel.x);
nePixel.x = std::max(nePixel.x, pixel.x);
@@ -214,7 +230,9 @@ void Map::fitBounds(AnnotationSegment segment, EdgeInsets padding, const Duratio
vec2<> centerPixel = (paddedNEPixel + paddedSWPixel) * 0.5;
LatLng centerLatLng = latLngForPixel(centerPixel);
- setLatLngZoom(centerLatLng, zoom, duration);
+ options.center = centerLatLng;
+ options.zoom = zoom;
+ return options;
}
void Map::resetZoom() {
@@ -270,8 +288,8 @@ void Map::resetNorth() {
#pragma mark - Pitch
-void Map::setPitch(double pitch) {
- transform->setPitch(std::min(pitch, 60.0) * M_PI / 180);
+void Map::setPitch(double pitch, const Duration& duration) {
+ transform->setPitch(util::clamp(pitch, 0., 60.) * M_PI / 180, duration);
update(Update::Repaint);
}
diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp
index 60c55c3f0c..bb55909149 100644
--- a/src/mbgl/map/transform.cpp
+++ b/src/mbgl/map/transform.cpp
@@ -1,3 +1,4 @@
+#include <mbgl/map/camera.hpp>
#include <mbgl/map/transform.hpp>
#include <mbgl/map/view.hpp>
#include <mbgl/util/constants.hpp>
@@ -53,6 +54,38 @@ bool Transform::resize(const std::array<uint16_t, 2> size) {
#pragma mark - Position
+void Transform::jumpTo(const CameraOptions options) {
+ CameraOptions jumpOptions = options;
+ jumpOptions.duration.reset();
+ easeTo(jumpOptions);
+}
+
+void Transform::easeTo(CameraOptions options) {
+ LatLng latLng = options.center ? *options.center : getLatLng();
+ double zoom = options.zoom ? *options.zoom : getZoom();
+ double angle = options.angle ? *options.angle : getAngle();
+ if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) {
+ return;
+ }
+
+ double new_scale = std::pow(2.0, zoom);
+
+ const double s = new_scale * util::tileSize;
+ state.Bc = s / 360;
+ state.Cc = s / util::M2PI;
+
+ const double m = 1 - 1e-15;
+ const double f = std::fmin(std::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);
+
+ double xn = -latLng.longitude * state.Bc;
+ double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f));
+
+ options.center.reset();
+ options.zoom.reset();
+ options.angle.reset();
+ _easeTo(options, new_scale, angle, xn, yn);
+}
+
void Transform::moveBy(const double dx, const double dy, const Duration& duration) {
if (std::isnan(dx) || std::isnan(dy)) {
return;
@@ -62,71 +95,30 @@ void Transform::moveBy(const double dx, const double dy, const Duration& duratio
}
void Transform::_moveBy(const double dx, const double dy, const Duration& duration) {
+
double x = state.x + std::cos(state.angle) * dx + std::sin( state.angle) * dy;
double y = state.y + std::cos(state.angle) * dy + std::sin(-state.angle) * dx;
state.constrain(state.scale, y);
-
- if (duration == Duration::zero()) {
- view.notifyMapChange(MapChangeRegionWillChange);
-
- state.x = x;
- state.y = y;
-
- view.notifyMapChange(MapChangeRegionDidChange);
- } else {
- view.notifyMapChange(MapChangeRegionWillChangeAnimated);
-
- const double startX = state.x;
- const double startY = state.y;
- state.panning = true;
-
- startTransition(
- [=](double t) {
- state.x = util::interpolate(startX, x, t);
- state.y = util::interpolate(startY, y, t);
- view.notifyMapChange(MapChangeRegionIsChanging);
- return Update::Repaint;
- },
- [=] {
- state.panning = false;
- view.notifyMapChange(MapChangeRegionDidChangeAnimated);
- }, duration);
- }
+
+ CameraOptions options;
+ options.duration = duration;
+ _easeTo(options, state.scale, state.angle, x, y);
}
void Transform::setLatLng(const LatLng latLng, const Duration& duration) {
- if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude)) {
- return;
- }
-
- const double m = 1 - 1e-15;
- const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);
-
- double xn = -latLng.longitude * state.Bc;
- double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f));
-
- _setScaleXY(state.scale, xn, yn, duration);
+ CameraOptions options;
+ options.center = latLng;
+ options.duration = duration;
+ easeTo(options);
}
void Transform::setLatLngZoom(const LatLng latLng, const double zoom, const Duration& duration) {
- if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) {
- return;
- }
-
- double new_scale = std::pow(2.0, zoom);
-
- const double s = new_scale * util::tileSize;
- state.Bc = s / 360;
- state.Cc = s / util::M2PI;
-
- const double m = 1 - 1e-15;
- const double f = ::fmin(::fmax(std::sin(util::DEG2RAD * latLng.latitude), -m), m);
-
- double xn = -latLng.longitude * state.Bc;
- double yn = 0.5 * state.Cc * std::log((1 + f) / (1 - f));
-
- _setScaleXY(new_scale, xn, yn, duration);
+ CameraOptions options;
+ options.center = latLng;
+ options.zoom = zoom;
+ options.duration = duration;
+ easeTo(options);
}
@@ -206,13 +198,27 @@ void Transform::_setScale(double new_scale, double cx, double cy, const Duration
void Transform::_setScaleXY(const double new_scale, const double xn, const double yn,
const Duration& duration) {
+ CameraOptions options;
+ options.duration = duration;
+ _easeTo(options, new_scale, state.angle, xn, yn);
+}
+
+void Transform::_easeTo(CameraOptions options, const double new_scale, const double new_angle, const double xn, const double yn) {
+ Update update = state.scale == new_scale ? Update::Repaint : Update::Zoom;
double scale = new_scale;
double x = xn;
double y = yn;
state.constrain(scale, y);
+
+ double angle = _normalizeAngle(new_angle, state.angle);
+ state.angle = _normalizeAngle(state.angle, angle);
+ double pitch = options.pitch ? *options.pitch : state.pitch;
- if (duration == Duration::zero()) {
+ if (!options.duration) {
+ options.duration = Duration::zero();
+ }
+ if (!options.duration || *options.duration == Duration::zero()) {
view.notifyMapChange(MapChangeRegionWillChange);
state.scale = scale;
@@ -221,33 +227,46 @@ void Transform::_setScaleXY(const double new_scale, const double xn, const doubl
const double s = state.scale * util::tileSize;
state.Bc = s / 360;
state.Cc = s / util::M2PI;
+
+ state.angle = angle;
+ state.pitch = pitch;
view.notifyMapChange(MapChangeRegionDidChange);
} else {
view.notifyMapChange(MapChangeRegionWillChangeAnimated);
const double startS = state.scale;
+ const double startA = state.angle;
+ const double startP = state.pitch;
const double startX = state.x;
const double startY = state.y;
state.panning = true;
state.scaling = true;
+ state.rotating = true;
startTransition(
[=](double t) {
+ util::UnitBezier ease = options.easing ? *options.easing : util::UnitBezier(0, 0, 0.25, 1);
+ return ease.solve(t, 0.001);
+ },
+ [=](double t) {
state.scale = util::interpolate(startS, scale, t);
state.x = util::interpolate(startX, x, t);
state.y = util::interpolate(startY, y, t);
const double s = state.scale * util::tileSize;
state.Bc = s / 360;
state.Cc = s / util::M2PI;
+ state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI);
+ state.pitch = util::interpolate(startP, pitch, t);
view.notifyMapChange(MapChangeRegionIsChanging);
- return Update::Zoom;
+ return update;
},
[=] {
state.panning = false;
state.scaling = false;
+ state.rotating = false;
view.notifyMapChange(MapChangeRegionDidChangeAnimated);
- }, duration);
+ }, *options.duration);
}
}
@@ -307,7 +326,7 @@ void Transform::setAngle(const double new_angle, const double cx, const double c
_moveBy(dx, dy, Duration::zero());
}
- _setAngle(new_angle, Duration::zero());
+ _setAngle(new_angle);
if (cx >= 0 && cy >= 0) {
_moveBy(-dx, -dy, Duration::zero());
@@ -315,32 +334,10 @@ void Transform::setAngle(const double new_angle, const double cx, const double c
}
void Transform::_setAngle(double new_angle, const Duration& duration) {
- double angle = _normalizeAngle(new_angle, state.angle);
- state.angle = _normalizeAngle(state.angle, angle);
-
- if (duration == Duration::zero()) {
- view.notifyMapChange(MapChangeRegionWillChange);
-
- state.angle = angle;
-
- view.notifyMapChange(MapChangeRegionDidChange);
- } else {
- view.notifyMapChange(MapChangeRegionWillChangeAnimated);
-
- const double startA = state.angle;
- state.rotating = true;
-
- startTransition(
- [=](double t) {
- state.angle = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI);
- view.notifyMapChange(MapChangeRegionIsChanging);
- return Update::Repaint;
- },
- [=] {
- state.rotating = false;
- view.notifyMapChange(MapChangeRegionDidChangeAnimated);
- }, duration);
- }
+ CameraOptions options;
+ options.angle = new_angle;
+ options.duration = duration;
+ easeTo(options);
}
double Transform::getAngle() const {
@@ -349,8 +346,11 @@ double Transform::getAngle() const {
#pragma mark - Pitch
-void Transform::setPitch(double pitch) {
- state.pitch = pitch;
+void Transform::setPitch(double pitch, const Duration& duration) {
+ CameraOptions options;
+ options.pitch = pitch;
+ options.duration = duration;
+ easeTo(options);
}
double Transform::getPitch() const {
@@ -359,7 +359,8 @@ double Transform::getPitch() const {
#pragma mark - Transition
-void Transform::startTransition(std::function<Update(double)> frame,
+void Transform::startTransition(std::function<double(double)> easing,
+ std::function<Update(double)> frame,
std::function<void()> finish,
const Duration& duration) {
if (transitionFinishFn) {
@@ -369,7 +370,7 @@ void Transform::startTransition(std::function<Update(double)> frame,
transitionStart = Clock::now();
transitionDuration = duration;
- transitionFrameFn = [frame, this](const TimePoint now) {
+ transitionFrameFn = [easing, frame, this](const TimePoint now) {
float t = std::chrono::duration<float>(now - transitionStart) / transitionDuration;
if (t >= 1.0) {
Update result = frame(1.0);
@@ -378,8 +379,7 @@ void Transform::startTransition(std::function<Update(double)> frame,
transitionFinishFn = nullptr;
return result;
} else {
- util::UnitBezier ease(0, 0, 0.25, 1);
- return frame(ease.solve(t, 0.001));
+ return frame(easing(t));
}
};
diff --git a/src/mbgl/map/transform.hpp b/src/mbgl/map/transform.hpp
index 1671983449..56001fad81 100644
--- a/src/mbgl/map/transform.hpp
+++ b/src/mbgl/map/transform.hpp
@@ -2,6 +2,7 @@
#define MBGL_MAP_TRANSFORM
#include <mbgl/map/transform_state.hpp>
+#include <mbgl/map/camera.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/map/update.hpp>
#include <mbgl/util/geo.hpp>
@@ -22,6 +23,9 @@ public:
// Map view
bool resize(std::array<uint16_t, 2> size);
+ void jumpTo(const CameraOptions options);
+ void easeTo(const CameraOptions options);
+
// Position
void moveBy(double dx, double dy, const Duration& = Duration::zero());
void setLatLng(LatLng latLng, const Duration& = Duration::zero());
@@ -42,7 +46,7 @@ public:
double getAngle() const;
// Pitch
- void setPitch(double pitch);
+ void setPitch(double pitch, const Duration& = Duration::zero());
double getPitch() const;
// Transitions
@@ -60,13 +64,16 @@ private:
void _moveBy(double dx, double dy, const Duration& = Duration::zero());
void _setScale(double scale, double cx, double cy, const Duration& = Duration::zero());
void _setScaleXY(double new_scale, double xn, double yn, const Duration& = Duration::zero());
+ void _easeTo(CameraOptions options, const double new_scale, const double new_angle,
+ const double xn, const double yn);
void _setAngle(double angle, const Duration& = Duration::zero());
View &view;
TransformState state;
- void startTransition(std::function<Update(double)> frame,
+ void startTransition(std::function<double(double)> easing,
+ std::function<Update(double)> frame,
std::function<void()> finish,
const Duration& duration);