From 815e0f9a4d08a6cafefece0ba5a46fd0d1cb925e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguy=E1=BB=85n?= Date: Thu, 30 Jul 2015 15:34:49 -0700 Subject: [PATCH] CameraOptions Plumbed camera options all the way through to MGLMapView. Added a method that lets you specify a direction in addition to center point and zoom level. Cleanup shall commence shortly. Added Map::jumpTo() for parity with mapbox-gl-js. Replaced usage of Map::setLatLng() and Map::setLatLngZoom() with Map::jumpTo() or Map::easeTo() within MGLMapView. ref #1834 --- include/mbgl/ios/MGLMapCamera.h | 13 ++ include/mbgl/ios/MGLMapView.h | 2 + include/mbgl/map/camera.hpp | 22 +++ include/mbgl/map/map.hpp | 4 + {src => include}/mbgl/util/optional.hpp | 0 platform/ios/MGLMapCamera.mm | 38 +++++ platform/ios/MGLMapView.mm | 47 +++++-- src/mbgl/map/camera.cpp | 1 + src/mbgl/map/map.cpp | 36 ++++- src/mbgl/map/transform.cpp | 177 +++++++++++------------- src/mbgl/map/transform.hpp | 17 ++- 11 files changed, 236 insertions(+), 121 deletions(-) create mode 100644 include/mbgl/ios/MGLMapCamera.h create mode 100644 include/mbgl/map/camera.hpp rename {src => include}/mbgl/util/optional.hpp (100%) create mode 100644 platform/ios/MGLMapCamera.mm create mode 100644 src/mbgl/map/camera.cpp diff --git a/include/mbgl/ios/MGLMapCamera.h b/include/mbgl/ios/MGLMapCamera.h new file mode 100644 index 00000000000..e3747e953cc --- /dev/null +++ b/include/mbgl/ios/MGLMapCamera.h @@ -0,0 +1,13 @@ +// +// MGLMapCamera.h +// mbgl +// +// Created by Minh Nguyen on 2015-08-05. +// +// + +#import + +@interface MGLMapCamera : NSObject + +@end diff --git a/include/mbgl/ios/MGLMapView.h b/include/mbgl/ios/MGLMapView.h index 2b9a3d29138..17d7046d2aa 100644 --- a/include/mbgl/ios/MGLMapView.h +++ b/include/mbgl/ios/MGLMapView.h @@ -128,6 +128,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. */ diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp new file mode 100644 index 00000000000..61934defb90 --- /dev/null +++ b/include/mbgl/map/camera.hpp @@ -0,0 +1,22 @@ +#ifndef MBGL_MAP_CAMERA +#define MBGL_MAP_CAMERA + +#include +#include +#include + +#include + +namespace mbgl { + +struct CameraOptions { + mapbox::util::optional center; + mapbox::util::optional zoom; + mapbox::util::optional angle; + mapbox::util::optional duration; + mapbox::util::optional > easing; +}; + +} + +#endif /* MBGL_MAP_CAMERA */ diff --git a/include/mbgl/map/map.hpp b/include/mbgl/map/map.hpp index bd8847d420f..9b766ffcc1b 100644 --- a/include/mbgl/map/map.hpp +++ b/include/mbgl/map/map.hpp @@ -2,6 +2,7 @@ #define MBGL_MAP_MAP #include +#include #include #include #include @@ -97,6 +98,9 @@ class Map : private util::noncopyable { void cancelTransitions(); void setGestureInProgress(bool); + 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()); diff --git a/src/mbgl/util/optional.hpp b/include/mbgl/util/optional.hpp similarity index 100% rename from src/mbgl/util/optional.hpp rename to include/mbgl/util/optional.hpp diff --git a/platform/ios/MGLMapCamera.mm b/platform/ios/MGLMapCamera.mm new file mode 100644 index 00000000000..e5570b1706b --- /dev/null +++ b/platform/ios/MGLMapCamera.mm @@ -0,0 +1,38 @@ +#import "MGLMapCamera.h" + +@implementation MGLMapCamera + ++ (nonnull instancetype)camera +{ + return [[self alloc] init]; +} + ++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + fromEyeCoordinate:(CLLocationCoordinate2D)eyeCoordinate + eyeAltitude:(CLLocationDistance)eyeAltitude +{ + CLLocationDirection heading = + return [[self alloc] initWithCenterCoordinate:centerCoordinate altitude:eyeAltitude heading:heading]; +} + ++ (instancetype)cameraLookingAtCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + fromDistance:(CLLocationDistance)distance + heading:(CLLocationDirection)heading +{ + return [[self alloc] initWithCenterCoordinate:centerCoordinate altitude:distance heading:heading]; +} + +- (instancetype)initWithCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate + altitude:(CLLocationDistance)altitude + heading:(CLLocationDirection)heading +{ + if (self = [super init]) + { + _centerCoordinate = centerCoordinate; + _altitude = altitude; + _heading = heading; + } + return self; +} + +@end diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm index cdd3044dcf1..61e4859e63b 100644 --- a/platform/ios/MGLMapView.mm +++ b/platform/ios/MGLMapView.mm @@ -358,7 +358,10 @@ - (void)commonInit // 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; @@ -1476,11 +1479,14 @@ - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)an - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated { - CGFloat duration = (animated ? MGLAnimationDuration : 0); - - _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(coordinate), - fmaxf(_mbglMap->getZoom(), self.currentMinimumZoom), - secondsAsDuration(duration)); + mbgl::CameraOptions options; + options.center = MGLLatLngFromLocationCoordinate2D(coordinate); + options.zoom = fmaxf(_mbglMap->getZoom(), self.currentMinimumZoom); + if (animated) + { + options.duration = secondsAsDuration(MGLAnimationDuration); + } + _mbglMap->easeTo(options); [self notifyMapChange:(animated ? mbgl::MapChangeRegionDidChangeAnimated : mbgl::MapChangeRegionDidChange)]; } @@ -1497,11 +1503,22 @@ - (CLLocationCoordinate2D)centerCoordinate - (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel animated:(BOOL)animated { - self.userTrackingMode = MGLUserTrackingModeNone; + [self setCenterCoordinate:centerCoordinate zoomLevel:zoomLevel direction:self.direction animated:animated]; +} - CGFloat duration = (animated ? MGLAnimationDuration : 0); +- (void)setCenterCoordinate:(CLLocationCoordinate2D)centerCoordinate zoomLevel:(double)zoomLevel direction:(CLLocationDirection)direction animated:(BOOL)animated +{ + self.userTrackingMode = MGLUserTrackingModeNone; - _mbglMap->setLatLngZoom(MGLLatLngFromLocationCoordinate2D(centerCoordinate), zoomLevel, secondsAsDuration(duration)); + mbgl::CameraOptions options; + options.center = MGLLatLngFromLocationCoordinate2D(centerCoordinate); + options.zoom = zoomLevel; + options.angle = direction; + if (animated) + { + options.duration = secondsAsDuration(MGLAnimationDuration); + } + _mbglMap->easeTo(options); [self unrotateIfNeededAnimated:animated]; @@ -1517,11 +1534,13 @@ - (void)setZoomLevel:(double)zoomLevel animated:(BOOL)animated { self.userTrackingMode = MGLUserTrackingModeNone; - CGFloat duration = (animated ? MGLAnimationDuration : 0); - - _mbglMap->setLatLngZoom(_mbglMap->getLatLng(), - fmaxf(zoomLevel, self.currentMinimumZoom), - secondsAsDuration(duration)); + mbgl::CameraOptions options; + options.zoom = fmaxf(zoomLevel, self.currentMinimumZoom); + if (animated) + { + options.duration = secondsAsDuration(MGLAnimationDuration); + } + _mbglMap->easeTo(options); [self unrotateIfNeededAnimated:animated]; diff --git a/src/mbgl/map/camera.cpp b/src/mbgl/map/camera.cpp new file mode 100644 index 00000000000..4a45e904f84 --- /dev/null +++ b/src/mbgl/map/camera.cpp @@ -0,0 +1 @@ +#include diff --git a/src/mbgl/map/map.cpp b/src/mbgl/map/map.cpp index 065c33ce5f3..d70e864e686 100644 --- a/src/mbgl/map/map.cpp +++ b/src/mbgl/map/map.cpp @@ -115,6 +115,18 @@ void Map::setGestureInProgress(bool inProgress) { update(Update::Repaint); } +#pragma mark - + +void Map::jumpTo(CameraOptions options) { + transform->jumpTo(options); + update(); +} + +void Map::easeTo(CameraOptions options) { + transform->easeTo(options); + update(Update::Repaint); +} + #pragma mark - Position void Map::moveBy(double dx, double dy, const Duration& duration) { @@ -123,7 +135,9 @@ void Map::moveBy(double dx, double dy, const Duration& duration) { } void Map::setLatLng(LatLng latLng, const Duration& duration) { - transform->setLatLng(latLng, duration); + CameraOptions options; + options.duration = duration; + transform->setLatLng(latLng, options); update(Update::Repaint); } @@ -132,9 +146,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); } @@ -165,7 +181,9 @@ double Map::getZoom() const { } void Map::setLatLngZoom(LatLng latLng, double zoom, const Duration& duration) { - transform->setLatLngZoom(latLng, zoom, duration); + CameraOptions options; + options.duration = duration; + transform->setLatLngZoom(latLng, zoom, options); update(Update::Zoom); } @@ -250,7 +268,9 @@ void Map::rotateBy(double sx, double sy, double ex, double ey, const Duration& d } void Map::setBearing(double degrees, const Duration& duration) { - transform->setAngle(-degrees * M_PI / 180, duration); + CameraOptions options; + options.duration = duration; + transform->setAngle(-degrees * M_PI / 180, options); update(Update::Repaint); } @@ -264,7 +284,9 @@ double Map::getBearing() const { } void Map::resetNorth() { - transform->setAngle(0, std::chrono::milliseconds(500)); + CameraOptions options; + options.duration = std::chrono::milliseconds(500); + transform->setAngle(0, options); update(Update::Repaint); } diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 7ccc2ad4cc9..12fa14d64d7 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -53,6 +54,35 @@ bool Transform::resize(const std::array size) { #pragma mark - Position +void Transform::jumpTo(const CameraOptions options) { + CameraOptions jumpOptions = options; + jumpOptions.duration.reset(); + easeTo(jumpOptions); +} + +void Transform::easeTo(const 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)); + + _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 +92,26 @@ 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); +void Transform::setLatLng(const LatLng latLng, CameraOptions options) { + options.center = latLng; + 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); +void Transform::setLatLngZoom(const LatLng latLng, const double zoom, CameraOptions options) { + options.center = latLng; + options.zoom = zoom; + easeTo(options); } @@ -206,13 +191,26 @@ 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::Zoom : Update::Repaint; 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); - 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,18 +219,26 @@ 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; view.notifyMapChange(MapChangeRegionDidChange); } else { view.notifyMapChange(MapChangeRegionWillChangeAnimated); const double startS = state.scale; + const double startA = state.angle; 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(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); @@ -240,14 +246,16 @@ 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 = util::wrap(util::interpolate(startA, angle, t), -M_PI, M_PI); view.notifyMapChange(MapChangeRegionIsChanging); - return Update::Zoom; + return update; }, [=] { state.panning = false; state.scaling = false; + state.rotating = false; view.notifyMapChange(MapChangeRegionDidChangeAnimated); - }, duration); + }, *options.duration); } } @@ -283,15 +291,17 @@ void Transform::rotateBy(const double start_x, const double start_y, const doubl const double ang = state.angle + util::angle_between(first_x, first_y, second_x, second_y); - _setAngle(ang, duration); + CameraOptions options; + options.duration = duration; + _setAngle(ang, options); } -void Transform::setAngle(const double new_angle, const Duration& duration) { +void Transform::setAngle(const double new_angle, CameraOptions options) { if (std::isnan(new_angle)) { return; } - _setAngle(new_angle, duration); + _setAngle(new_angle, options); } void Transform::setAngle(const double new_angle, const double cx, const double cy) { @@ -307,40 +317,17 @@ void Transform::setAngle(const double new_angle, const double cx, const double c _moveBy(dx, dy, Duration::zero()); } - _setAngle(new_angle, Duration::zero()); + CameraOptions options; + _setAngle(new_angle, options); if (cx >= 0 && cy >= 0) { _moveBy(-dx, -dy, Duration::zero()); } } -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); - } +void Transform::_setAngle(double new_angle, CameraOptions options) { + options.angle = new_angle; + easeTo(options); } double Transform::getAngle() const { @@ -359,7 +346,8 @@ double Transform::getPitch() const { #pragma mark - Transition -void Transform::startTransition(std::function frame, +void Transform::startTransition(std::function easing, + std::function frame, std::function finish, const Duration& duration) { if (transitionFinishFn) { @@ -369,7 +357,7 @@ void Transform::startTransition(std::function frame, transitionStart = Clock::now(); transitionDuration = duration; - transitionFrameFn = [frame, this](const TimePoint now) { + transitionFrameFn = [easing, frame, this](const TimePoint now) { float t = std::chrono::duration(now - transitionStart) / transitionDuration; if (t >= 1.0) { Update result = frame(1.0); @@ -378,8 +366,7 @@ void Transform::startTransition(std::function 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 5d5a72d6d32..d88cbe0a58c 100644 --- a/src/mbgl/map/transform.hpp +++ b/src/mbgl/map/transform.hpp @@ -2,6 +2,7 @@ #define MBGL_MAP_TRANSFORM #include +#include #include #include #include @@ -22,10 +23,13 @@ class Transform : private util::noncopyable { // Map view bool resize(std::array 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()); - void setLatLngZoom(LatLng latLng, double zoom, const Duration& = Duration::zero()); + void setLatLng(LatLng latLng, CameraOptions options); + void setLatLngZoom(LatLng latLng, double zoom, CameraOptions options); inline const LatLng getLatLng() const { return state.getLatLng(); } // Zoom @@ -37,7 +41,7 @@ class Transform : private util::noncopyable { // Angle void rotateBy(double sx, double sy, double ex, double ey, const Duration& = Duration::zero()); - void setAngle(double angle, const Duration& = Duration::zero()); + void setAngle(double angle, CameraOptions options); void setAngle(double angle, double cx, double cy); double getAngle() const; @@ -59,13 +63,16 @@ class Transform : private util::noncopyable { 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 _setAngle(double angle, 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, CameraOptions options); View &view; TransformState state; - void startTransition(std::function frame, + void startTransition(std::function easing, + std::function frame, std::function finish, const Duration& duration);