For standard operations like zooming and panning, the MapNavigator API is enough. See Scale, pan, rotate, and fit a map for an illustration. 2D and 3D maps offer you a lot of freedom, though, and sometimes you need full control over the position of your camera.

The Camera API offers such fine-grained camera control.

The Camera API operates at a lower level than the MapNavigator API. With the MapNavigator API, you can navigate both 2D and 3D maps in any reference, using the same API calls. The Camera API is specific to the camera type you are using though. You can choose either an OrthographicCamera or a PerspectiveCamera.

Changing the camera position

This example demonstrates how you use the Camera API to configure the point to look at, the camera distance, pitch, roll, and yaw:

Program: Changing the camera position
auto currentCamera = map.getCamera();
if (auto* perspectiveCamera = dynamic_cast<PerspectiveCamera*>(currentCamera.get())) {
auto cameraLookAt = Camera::LookAt(targetLocation, 200.0, 215.0, -30.0, 0.0);
auto newCamera = perspectiveCamera->asBuilder().lookAt(cameraLookAt).build();
map.setCamera(newCamera);
}

You can apply camera changes by setting a new camera on the Map.

It is important that you take into account the threading rules when you use the camera API.

Animating the camera

When you want to animate the camera, it is important to get a fluent motion. You can accomplish this by updating the camera right before each map repaint. There are 2 concepts in the API that help you accomplish this.

AnimationManager

When your animation has a fixed duration, you can let it run once or let it loop multiple times by submitting it to the Map’s AnimationManager. An example would be a zoom animation that takes half a second of time. The animation receives an onUpdate at the beginning of each paint call.

This example shows how you can submit a camera animation to the Map’s AnimationManager.

Program: Using the AnimationManager
// Start the animation that will apply the camera updates.
_map->getAnimationManager().startAnimation("MyAnimationKey", animation);
// Stop the camera animation
_map->getAnimationManager().stopAnimation("MyAnimationKey");

IRendererCallback

When you want to update the camera continuously, for example based on some other state, you can use an IRendererCallback. This is a callback that is called by the map renderer right before each repaint. Inside that callback, the camera can be updated. An example of such an animation would be a camera that follows a moving feature on the map.

This example shows how you can add an IRendererCallback to the Map.

Program: Adding and removing an IRendererCallback
// Start the render callback that will apply the camera updates
_cameraUpdater = std::make_shared<OrbitCameraUpdater>(_centerPoint, map->getReference(), _autoRotate);
map->addRendererCallback(_cameraUpdater);
// Stop the camera updater
map->removeRendererCallback(_cameraUpdater);

The camera sample demonstrates the usage of this API in more detail. This sample shows how to:

  • Implement a controller for orbiting the camera around a point

  • Implement a controller for navigating on the map using a typical first person control scheme

Constraining the camera

When you use the MapNavigator class, you can configure which camera constraints are used. When you manipulate the camera directly however, it’s the responsibility of the camera manipulation code to implement the desired camera constraints.

You can for example prevent the camera from moving under the terrain. You can accomplish this using custom code that uses utility methods in the API, for example a method to project a point onto the terrain. The camera sample demonstrates how this can be accomplished in the first person navigation controller:

Program: Keeping the camera above the terrain
// Keep the camera above the terrain
lookFromCamera.eye = AboveTerrainConstraint::moveAboveTerrain(map, lookFromCamera.eye, 5.0);
Coordinate AboveTerrainConstraint::moveAboveTerrain(const Map& map, const Coordinate& point, double minHeightAboveTerrain) {
auto projectedPoint = map.getRenderer().projectPointOnTerrain(point);
auto center = Coordinate(0.0, 0.0, 0.0);
auto distanceToPoint = MathUtil::distance(center, point);
auto distanceToProjectedPoint = MathUtil::distance(center, projectedPoint);
double distanceToMoveUp = distanceToProjectedPoint + minHeightAboveTerrain - distanceToPoint;
if (distanceToMoveUp > 0) {
return point + MathUtil::normalize(point) * (distanceToMoveUp + 1e-6);
} else {
return point;
}
}