This is a ticket to track the discussion around implementing a new camera system for the various platforms.
The goal is to provide a camera system that allows independent animations to run on each of the map camera parameters. It should use a familiar API's for each of the platforms. This will give developers and designers the ability to have total control of the experience. It will also allow for the easy use of plug-in camera animation libraries and physics simulations for those who want to use certain styles of animation.
Further down the line, we should be able to introduce design tools for crafting animations and sharing those creations.
Some background and prior thinking:
“Fresh new camera”: mapbox/mapbox-gl-js#3583
“Concurrent Transition Animations”: https://github.com/mapbox/mapbox-gl-native/issues/3625
“Reimplement camera transitions with Core Animation”: https://github.com/mapbox/mapbox-gl-native/issues/8176
The new camera system will be able to animate each of the following parameters on independent timelines:
Animations will be handled on the various platforms and committed to the core with the frequency (or frame rate) controlled by the platform.
cc @mapbox/gl @pveugen
@jfirebaugh Can we figure out a way to prefetch tiles using the platform based animations?
mbgl currently treats anchor as a frame of reference for zoom and angle, but not as a degree of freedom in its own right. (Similarly, padding affects the frame of reference for center.) Would you suggest turning it into an animatable degree of freedom? What are the tradeoffs?
I'm thinking of anchor as an animatable parameter. Setting this explicitly has been the easiest way to control the effect. I haven't given much thought to the tradeoffs of setting this implicitly.
Currently, to change the camera’s padding (as the user perceives it), you’d need to reapply the current center coordinate with the new padding. The anchor is often provided by gesture recognizers (to be under the mouse cursor) when specifying a camera to animate to, but if you ask the map for its current anchor, you’ll always get an unset value. In this sense, anchor could just as well have been a member of AnimationOptions instead of CameraOptions.
If we were to keep the current anchor implementation but allow for concurrent animations, the developer would for instance ease to a camera with a new anchor value, and any subsequent animation frames would have to account for that anchor when computing the center coordinate. On the other hand, if we turn anchor into its own degree of freedom, then there could be conflicting anchors when (say) the user makes a rotation gesture in the middle of a programmatic rotation.
That's a good point about the anchor moving to the point between touches on rotate. I'm very much thinking about this in the specific context of navigation where any touch/move on the map should cancel all animation. Any call to transition to the following mode (by tapping resume or re-center or using a timeout) should result in the anchor point moving to somewhere in the lower third of the screen. Calls for transition to the overhead view should move the anchor to the center of the defined viewport.
@d-prukop see https://github.com/mapbox/mapbox-gl-native/issues/2006 for a discussion on tile prefetching.
See https://github.com/mapbox/mapbox-gl-native/issues/9813 for cache eviction strategy when prefetching tiles.
Capturing chat today (cc: @tobrun). Looks like ObjectAnimator is a good starting point to achieve this on the Android side.
Had a chat with @1ec5 yesterday. The plan is to experiment with using CoreAnimation to do camera animations without changing anything on core.
The plan is to experiment with using CoreAnimation to do camera animations without changing anything on core.
This experiment would be focused specifically on one use case, a turn-by-turn navigation map view that only needs short-distance easing animations. The hope is that we can at least experiment with Core Animation integration purely at the SDK level. However, we’ll eventually need more from mbgl when we generalize this work to account for long-distance animations or flight animations.
I did a quick test on Android. In gif below you can see two Android SDK animators running together to animate bearing and tilt independently.

Above is created using the Android SDK animator framework:
final NativeCameraPosition cameraPosition = mapView.getCameraPosition();
// Animate tilt
ValueAnimator tiltAnimator = ValueAnimator.ofFloat(0f, 60.0f);
tiltAnimator.setStartDelay(1000);
tiltAnimator.setDuration(4000);
tiltAnimator.setInterpolator(new FastOutSlowInInterpolator());
tiltAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
cameraPosition.setTilt(((Float) animation.getAnimatedValue()).doubleValue());
}
});
// Animate bearing
ValueAnimator bearingAnimator = ValueAnimator.ofFloat(0.0f, 160.0f);
bearingAnimator.setDuration(6000);
bearingAnimator.setInterpolator(new FastOutLinearInInterpolator());
bearingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
cameraPosition.setBearing(((Float)animation.getAnimatedValue()).doubleValue());
}
});
// Combine animations and start
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setStartDelay(1500);
animatorSet.play(tiltAnimator);
animatorSet.play(bearingAnimator);
animatorSet.start();
Note that I didn't change anything to core but I had to rework a couple of things in the android binding to make this work. Bringing a feature like this would mean increasing the API surface on the Android SDK.
However, we’ll eventually need more from mbgl when we generalize this work to account for long-distance animations or flight animations.
From core GL, we’ll mainly need to use mbgl::Projection, which is already conveniently in a standalone namespace, ready to use within MGLMapView’s implementation. mapbox/mapbox-gl-js#3112 illustrates the problems we’d run into without projecting and unprojecting at the right times. You wouldn’t notice the problem unless the map is tilted or the animation spans a long distance.
Awesome to see this moving, tagging myself to follow along.
Here's a version of the camera for iOS.

I was able to get this working with UIView animations. I'd like to eventually get it working with the UIViewPropertyAnimator.
Here's a look at the use of the UIKit API to animate our custom camera.
[UIView animateWithDuration:1.0 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.camera.lng = -122.400067;
self.camera.lat = 37.788336;
} completion:^(BOOL finished) {
}];
[UIView animateWithDuration:1.5 delay:0.6 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.camera.zoomLevel = 800;
} completion:^(BOOL finished) {
}];
[UIView animateWithDuration:1.0 delay:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.camera.bearing = 135;
} completion:^(BOOL finished) {
}];
[UIView animateWithDuration:1.0 delay:1.5 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.camera.pitch = 60;
} completion:^(BOOL finished) {
}];
PR unblocking this feature for the Android binding in #10001.
Removing android label, API for that landed.
Tagging myself and @fabian-guerra just for reference.
This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions.
This issue has been automatically detected as stale because it has not had recent activity and will be archived. Thank you for your contributions.
Most helpful comment
Here's a version of the camera for iOS.

I was able to get this working with UIView animations. I'd like to eventually get it working with the UIViewPropertyAnimator.
Here's a look at the use of the UIKit API to animate our custom camera.