For standard operations like zooming and panning, the MapNavigatorMapNavigatorMapNavigator 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 CameraCameraCamera API is specific to the camera type you are using though. You can choose either an OrthographicCameraOrthographicCameraOrthographicCamera or a PerspectiveCameraPerspectiveCameraPerspectiveCamera.

Changing the camera position

This example demonstrates how you use the CameraCameraCamera 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);
}
var currentCamera = map.Camera;
if (currentCamera is PerspectiveCamera perspectiveCamera)
{
    var cameraLookAt = new Camera.LookAt(targetLocation, 200.0, 215.0, -30.0, 0.0);
    var newCamera = perspectiveCamera.AsBuilder().LookAt(cameraLookAt).Build();
    map.Camera = newCamera;
}
val currentCamera = map.camera
if (currentCamera is PerspectiveCamera) {
    val cameraLookAt = Camera.LookAt(targetLocation, 200.0, Azimuth(215.0), -30.0, 0.0)
    val newCamera = currentCamera.asBuilder().lookAt(cameraLookAt).build()
    map.camera = newCamera
}

You can apply camera changes by settingsettingsetting 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 repaintmap repaintmap repaint. There are 2 concepts in the API that help you accomplish this.

AnimationManager

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

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

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");
// Start the animation that will apply the camera updates.
map.AnimationManager.StartAnimation("MyAnimationKey", animation);

// Stop the camera animation
map.AnimationManager.StopAnimation("MyAnimationKey");
// 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 IRendererCallbackIRendererCallbackIRendererCallback. This is a callback that is called by the map renderer right before each repaint. Inside that callback, the camera can be updatedupdatedupdated. 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);
// Start the render callback that will apply the camera updates
_cameraUpdater = new OrbitCameraUpdater(_centerPoint, map.Reference, _autoRotate);
map.AddRendererCallback(_cameraUpdater);
// Stop the camera updater
map.RemoveRendererCallback(_cameraUpdater);
cameraUpdater = OrbitCameraUpdater(centerPoint, map.reference, autoRotate)
map.addRendererCallback(cameraUpdater)
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 terrainproject a point onto the terrainproject 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;
  }
}
// Keep the camera above the terrain
newEye = AboveTerrainConstraint.MoveAboveTerrain(map, newEye, 5.0);
public static Coordinate MoveAboveTerrain(Map map, Coordinate point, double minHeightAboveTerrain)
{
    var projectedPoint = map.GetRenderer().ProjectPointOnTerrain(point);
    var center = new Coordinate(0.0, 0.0);
    var distanceToPoint = MathUtil.Distance(center, point);
    var distanceToProjectedPoint = MathUtil.Distance(center, projectedPoint);

    var distanceToMoveUp = distanceToProjectedPoint + minHeightAboveTerrain - distanceToPoint;
    if (distanceToMoveUp > 0)
    {
        return point + MathUtil.Normalize(point) * (distanceToMoveUp + 1e-6);
    }
    return point;
}
      // Keep the camera above the terrain
      Coordinate eyeAboveTerrain = moveAboveTerrain(map, lookFromCamera.getEye(), 5.0);
      LookFrom aboveTerrainLookFrom = new LookFrom(eyeAboveTerrain,
                                                   lookFromCamera.getYaw(),
                                                   lookFromCamera.getPitch(),
                                                   lookFromCamera.getRoll());
private Coordinate moveAboveTerrain(Map map, Coordinate point, double minHeightAboveTerrain) {
  Coordinate projectedPoint = map.getRenderer().projectPointOnTerrain(point);
  Coordinate center = new Coordinate(0.0, 0.0, 0.0);
  double distanceToPoint = MathUtil.distance(center, point);
  double distanceToProjectedPoint = MathUtil.distance(center, projectedPoint);

  double distanceToMoveUp = distanceToProjectedPoint + minHeightAboveTerrain - distanceToPoint;
  if (distanceToMoveUp > 0) {
    Coordinate normalized = MathUtil.normalize(point);
    double x = point.getX() + normalized.getX() * (distanceToMoveUp + 1e-6);
    double y = point.getY() + normalized.getY() * (distanceToMoveUp + 1e-6);
    double z = point.getZ() + normalized.getZ() * (distanceToMoveUp + 1e-6);
    return new Coordinate(x, y, z);
  } else {
    return point;
  }
}