diff --git a/CHANGELOG.md b/CHANGELOG.md index d8cfe553adc..fe9bcf339fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Known issues: - `MGLMapView` methods that alter the viewport now accept optional completion handlers. ([#3090](https://github.com/mapbox/mapbox-gl-native/pull/3090)) - Tapping now selects annotations more reliably. Tapping near the top of a large annotation image now selects that annotation. An annotation image’s alignment insets influence how far away the user can tap and still select the annotation. For example, if your annotation image has a large shadow, you can keep that shadow from being tappable by excluding it from the image’s alignment rect. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261)) +- A new method on MGLMapView, `-flyToCamera:withDuration:completionHandler:`, lets you transition between viewpoints along an arc as if by aircraft. ([#3171](https://github.com/mapbox/mapbox-gl-native/pull/3171), [#3301](https://github.com/mapbox/mapbox-gl-native/pull/3301)) - The user dot’s callout view is now centered above the user dot. It was previously offset slightly to the left. ([#3261](https://github.com/mapbox/mapbox-gl-native/pull/3261)) ## iOS 3.0.1 diff --git a/include/mbgl/map/camera.hpp b/include/mbgl/map/camera.hpp index 18e7dbf96c1..dd9e0a0f56e 100644 --- a/include/mbgl/map/camera.hpp +++ b/include/mbgl/map/camera.hpp @@ -17,6 +17,8 @@ struct CameraOptions { mapbox::util::optional angle; mapbox::util::optional pitch; mapbox::util::optional duration; + mapbox::util::optional speed; + mapbox::util::optional curve; mapbox::util::optional easing; std::function transitionFrameFn; std::function transitionFinishFn; diff --git a/src/mbgl/map/transform.cpp b/src/mbgl/map/transform.cpp index 4604fbb287f..d08972d53fb 100644 --- a/src/mbgl/map/transform.cpp +++ b/src/mbgl/map/transform.cpp @@ -319,6 +319,7 @@ void Transform::flyTo(const CameraOptions &options) { LatLng startLatLng = getLatLng(); double zoom = flyOptions.zoom ? *flyOptions.zoom : getZoom(); double angle = flyOptions.angle ? *flyOptions.angle : getAngle(); + double pitch = flyOptions.pitch ? *flyOptions.pitch : getPitch(); if (std::isnan(latLng.latitude) || std::isnan(latLng.longitude) || std::isnan(zoom)) { return; } @@ -337,13 +338,14 @@ void Transform::flyTo(const CameraOptions &options) { view.notifyMapChange(MapChangeRegionWillChangeAnimated); - const double startS = state.scale; + const double startZ = state.scaleZoom(state.scale); const double startA = state.angle; + const double startP = state.pitch; state.panning = true; state.scaling = true; state.rotating = true; - const double rho = 1.42; + double rho = flyOptions.curve ? *flyOptions.curve : 1.42; double w0 = std::max(state.width, state.height); double w1 = w0 / new_scale; double u1 = ::hypot(xn, yn); @@ -371,47 +373,65 @@ void Transform::flyTo(const CameraOptions &options) { double S = (is_close ? (std::abs(std::log(w1 / w0)) / rho) : ((r(1) - r0) / rho)); - if (!flyOptions.duration) { - flyOptions.duration = Duration::zero(); + Duration duration = flyOptions.duration ? *flyOptions.duration : Duration::zero(); + if (flyOptions.duration) { + duration = *flyOptions.duration; + } else { + double speed = flyOptions.speed ? *flyOptions.speed : 1.2; + duration = std::chrono::duration_cast( + std::chrono::duration(S / speed)); } startTransition( - [=](double t) { - util::UnitBezier ease = flyOptions.easing ? *flyOptions.easing : util::UnitBezier(0, 0, 0.25, 1); - return ease.solve(t, 0.001); - }, - [=](double k) { - double s = k * S; - double us = u(s); - - //First calculate the desired latlng - double desiredLat = startLatLng.latitude + (latLng.latitude - startLatLng.latitude) * us; - double desiredLng = startLatLng.longitude + (latLng.longitude - startLatLng.longitude) * us; - - //Now calculate desired zoom - state.scale = startS - w(s); - - //Now set values - const double new_scaled_tile_size = state.scale * util::tileSize; - state.Bc = new_scaled_tile_size / 360; - state.Cc = new_scaled_tile_size / util::M2PI; - - const double f2 = ::fmin(::fmax(std::sin(util::DEG2RAD * desiredLat), -m), m); - state.x = -desiredLng * state.Bc; - state.y = 0.5 * state.Cc * std::log((1 + f2) / (1 - f2)); - - if (angle != startA) { - state.angle = util::wrap(util::interpolate(startA, angle, k), -M_PI, M_PI); - } - - view.notifyMapChange(MapChangeRegionIsChanging); - return Update::Zoom; - }, - [=] { - state.panning = false; - state.scaling = false; - state.rotating = false; - view.notifyMapChange(MapChangeRegionDidChangeAnimated); - }, *flyOptions.duration); + [=](double t) { + util::UnitBezier ease = flyOptions.easing ? *flyOptions.easing : util::UnitBezier(0, 0, 0.25, 1); + return ease.solve(t, 0.001); + }, + [=](double k) { + double s = k * S; + double us = u(s); + + //First calculate the desired latlng + double desiredLat = startLatLng.latitude + (latLng.latitude - startLatLng.latitude) * us; + double desiredLng = startLatLng.longitude + (latLng.longitude - startLatLng.longitude) * us; + + //Now calculate desired zoom + double desiredZoom = startZ + state.scaleZoom(1 / w(s)); + double desiredScale = state.zoomScale(desiredZoom); + state.scale = ::fmax(::fmin(desiredScale, state.max_scale), state.min_scale); + + //Now set values + const double new_scaled_tile_size = state.scale * util::tileSize; + state.Bc = new_scaled_tile_size / 360; + state.Cc = new_scaled_tile_size / util::M2PI; + + const double f2 = ::fmin(::fmax(std::sin(util::DEG2RAD * desiredLat), -m), m); + state.x = -desiredLng * state.Bc; + state.y = 0.5 * state.Cc * std::log((1 + f2) / (1 - f2)); + + if (angle != startA) { + state.angle = util::wrap(util::interpolate(startA, angle, k), -M_PI, M_PI); + } + if (pitch != startP) { + state.pitch = util::clamp(util::interpolate(startP, pitch, k), 0., 60.); + } + // At k = 1.0, a DidChangeAnimated notification should be sent from finish(). + if (k < 1.0) { + if (options.transitionFrameFn) { + options.transitionFrameFn(k); + } + view.notifyMapChange(MapChangeRegionIsChanging); + } + return Update::Zoom; + }, + [=] { + state.panning = false; + state.scaling = false; + state.rotating = false; + if (options.transitionFinishFn) { + options.transitionFinishFn(); + } + view.notifyMapChange(MapChangeRegionDidChangeAnimated); + }, duration); }; #pragma mark - Angle diff --git a/src/mbgl/map/transform_state.cpp b/src/mbgl/map/transform_state.cpp index 7153c2524b8..a96f1825347 100644 --- a/src/mbgl/map/transform_state.cpp +++ b/src/mbgl/map/transform_state.cpp @@ -243,6 +243,10 @@ double TransformState::zoomScale(double zoom) const { return std::pow(2.0f, zoom); } +double TransformState::scaleZoom(double s) const { + return std::log2(s); +} + float TransformState::worldSize() const { return util::tileSize * scale; } diff --git a/src/mbgl/map/transform_state.hpp b/src/mbgl/map/transform_state.hpp index fd9a556eb91..fa6ed8b58b4 100644 --- a/src/mbgl/map/transform_state.hpp +++ b/src/mbgl/map/transform_state.hpp @@ -95,6 +95,7 @@ class TransformState { double lngX(double lon) const; double latY(double lat) const; double zoomScale(double zoom) const; + double scaleZoom(double scale) const; float worldSize() const; mat4 coordinatePointMatrix(double z) const;