> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.stereolabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.stereolabs.com/_mcp/server.

# Tutorial - Global Localization

This tutorial shows how to use the [Global Localization](/docs/development/zed-sdk/modules/global-localization/) (GNSS Fusion) API to fuse GNSS coordinates with the camera's visual-inertial odometry, in order to compute the camera's global position (latitude, longitude, altitude) using the [Fusion API](/docs/development/zed-sdk/modules/fusion/). To keep this tutorial self-contained and easy to run without any external hardware, the GNSS data is randomly generated rather than read from a real GNSS receiver. We assume that you have followed the [Positional Tracking](/docs/tutorials/positional-tracking/) tutorial before.

> **Note**
>
> This module requires a stereo camera equipped with an inertial sensor (IMU), such as the ZED 2/2i, ZED Mini, ZED X, ZED X Mini, ZED X Nano. The original ZED (no built-in IMU) is not supported.

## Getting Started

* First, download the latest version of the [ZED SDK](https://www.stereolabs.com/developers/).
* Download the [Global Localization](https://github.com/stereolabs/zed-sdk/tree/master/tutorials/tutorial%209%20-%20global%20localization) sample code in C++ or Python.

## Code Overview

### Open the camera and enable positional tracking

As in the previous tutorials, we create, configure and open the ZED. Two positional tracking parameters are mandatory here: `enable_imu_fusion` (to get the gravity direction from the IMU) and `set_gravity_as_origin` (to align the camera and GNSS reference frames).

**`C++`**

```cpp C++
// Open camera
sl::InitParameters init_params;
init_params.depth_mode = sl::DEPTH_MODE::NEURAL;
init_params.coordinate_system = sl::COORDINATE_SYSTEM::IMAGE;
init_params.coordinate_units = sl::UNIT::METER;
init_params.camera_resolution = sl::RESOLUTION::AUTO;
init_params.camera_fps = 60;
sl::Camera zed;
sl::ERROR_CODE camera_open_error = zed.open(init_params);
if (camera_open_error > sl::ERROR_CODE::SUCCESS) {
    std::cerr << "[ZED][ERROR] Can't open ZED camera" << std::endl;
    return EXIT_FAILURE;
}

// Enable positional tracking
sl::PositionalTrackingParameters ptp;
ptp.initial_world_transform = sl::Transform::identity();
ptp.enable_imu_fusion = true;     // Enable IMU (for having the gravity direction)
ptp.set_gravity_as_origin = true; // Set gravity as origin for allowing GNSS to Camera initialization
auto positional_init = zed.enablePositionalTracking(ptp);
if (positional_init > sl::ERROR_CODE::SUCCESS) {
    std::cerr << "[ZED][ERROR] Can't start tracking of camera" << std::endl;
    return EXIT_FAILURE;
}
```

**`Python`**

```python Python
# Open camera
init_params = sl.InitParameters(camera_resolution=sl.RESOLUTION.AUTO,
                                 coordinate_units=sl.UNIT.METER,
                                 coordinate_system=sl.COORDINATE_SYSTEM.RIGHT_HANDED_Y_UP)
zed = sl.Camera()
status = zed.open(init_params)
if status > sl.ERROR_CODE.SUCCESS:
    print("Camera Open: " + repr(status) + ". Exit program.")
    exit()

# Enable positional tracking
tracking_params = sl.PositionalTrackingParameters()
# These parameters are mandatory to initialize the transformation between GNSS and ZED reference frames.
tracking_params.enable_imu_fusion = True
tracking_params.set_gravity_as_origin = True
err = zed.enable_positional_tracking(tracking_params)
if err > sl.ERROR_CODE.SUCCESS:
    print("Camera positional tracking: " + repr(err) + ". Exit program.")
    exit()
```

### Publish the camera for Fusion

The Fusion module works on a *Publish/Subscribe* pattern: the camera publishes its data, and a `Fusion` object subscribes to it. Here, camera and Fusion run in the same process, communicating over shared memory.

**`C++`**

```cpp C++
// Enable camera publishing for fusion
sl::CommunicationParameters communication_parameters;
communication_parameters.setForSharedMemory();
zed.startPublishing(communication_parameters);

// Run a first grab to start sending data
while (zed.grab() > sl::ERROR_CODE::SUCCESS)
    ;
```

**`Python`**

```python Python
# Set up communication parameters and start publishing
communication_parameters = sl.CommunicationParameters()
communication_parameters.set_for_shared_memory()
zed.start_publishing(communication_parameters)

# Warmup for camera
status = zed.grab()
if status > sl.ERROR_CODE.SUCCESS:
    print("Camera grab: " + repr(status) + ". Exit program.")
    exit()
```

### Set up Fusion and subscribe the camera

We create the `Fusion` object, initialize it, enable its own positional tracking (with GNSS fusion), and subscribe our camera to it using its serial number as an identifier.

**`C++`**

```cpp C++
// Create fusion object
sl::InitFusionParameters init_multi_cam_parameters;
init_multi_cam_parameters.coordinate_units = sl::UNIT::METER;
init_multi_cam_parameters.coordinate_system = sl::COORDINATE_SYSTEM::IMAGE;
init_multi_cam_parameters.output_performance_metrics = true;
init_multi_cam_parameters.verbose = true;
sl::Fusion fusion;
sl::FUSION_ERROR_CODE fusion_init_code = fusion.init(init_multi_cam_parameters);
if (fusion_init_code != sl::FUSION_ERROR_CODE::SUCCESS) {
    std::cerr << "[Fusion][ERROR] Failed to initialize fusion, error: " << fusion_init_code << std::endl;
    return EXIT_FAILURE;
}

// Subscribe to camera
sl::CameraIdentifier uuid(zed.getCameraInformation().serial_number);
fusion.subscribe(uuid, communication_parameters, sl::Transform::identity());

// Enable positional tracking, with GNSS fusion
sl::PositionalTrackingFusionParameters ptfp;
ptfp.enable_GNSS_fusion = true;
fusion.enablePositionalTracking(ptfp);
```

**`Python`**

```python Python
# Init the fusion module that will input both the camera and the GNSS
fusion = sl.Fusion()
init_fusion_parameters = sl.InitFusionParameters()
init_fusion_parameters.coordinate_system = sl.COORDINATE_SYSTEM.RIGHT_HANDED_Y_UP
init_fusion_parameters.coordinate_units = sl.UNIT.METER
fusion.init(init_fusion_parameters)

# Enable positional tracking, with GNSS fusion
positional_tracking_fusion_parameters = sl.PositionalTrackingFusionParameters()
positional_tracking_fusion_parameters.enable_GNSS_fusion = True
fusion.enable_positionnal_tracking(positional_tracking_fusion_parameters)

# Subscribe to camera
camera_info = zed.get_camera_information()
uuid = sl.CameraIdentifier(camera_info.serial_number)
status = fusion.subscribe(uuid, communication_parameters, sl.Transform(0, 0, 0))
if status != sl.FUSION_ERROR_CODE.SUCCESS:
    print("Failed to subscribe to", uuid.serial_number, status)
    exit(1)
```

> **Note**
>
> The `python` snippet above calls `enable_positionnal_tracking` (with the extra "n"): this is the actual name exposed by the Fusion Python bindings.

### Ingest GNSS data and retrieve the fused position

At each `grab()`, we get the camera-only pose, generate a fake GNSS position and ingest it into `fusion`, then call `fusion.process()` to retrieve both the fused camera pose and the geographic position (latitude, longitude, altitude) once the GNSS-to-camera alignment has converged.

**`C++`**

```cpp C++
unsigned number_detection = 0;
while (number_detection < 200) {
    // Grab camera
    if (zed.grab() <= sl::ERROR_CODE::SUCCESS) {
        sl::Pose zed_pose;
        zed.getPosition(zed_pose, sl::REFERENCE_FRAME::WORLD);
    }

    // Ingest (fake) GNSS data, timestamped with the current camera timestamp
    sl::GNSSData input_gnss = getGNSSData();
    input_gnss.ts = zed.getTimestamp(sl::TIME_REFERENCE::IMAGE);
    if (input_gnss.ts != sl::Timestamp(0))
        fusion.ingestGNSSData(input_gnss);

    // Process fusion
    if (fusion.process() == sl::FUSION_ERROR_CODE::SUCCESS) {
        // Fused camera pose
        sl::Pose fused_position;
        sl::POSITIONAL_TRACKING_STATE current_state = fusion.getPosition(fused_position);

        // Global position (GNSS coordinate system)
        sl::GeoPose current_geopose;
        sl::GNSS_FUSION_STATUS current_geopose_status = fusion.getGeoPose(current_geopose);
        if (current_geopose_status == sl::GNSS_FUSION_STATUS::OK) {
            number_detection++;
            double latitude, longitude, altitude;
            current_geopose.latlng_coordinates.getCoordinates(latitude, longitude, altitude, false);
            std::cout << "latitude = " << latitude << ", longitude = " << longitude << ", altitude = " << altitude << std::endl;
        }
        // Otherwise, the GNSS-to-ZED coordinate system alignment hasn't converged yet:
        // it is an optimization problem that fits the ZED computed path to the GNSS computed path,
        // so keep moving the camera until it converges.
    }
}
```

**`Python`**

```python Python
odometry_pose = sl.Pose()
camera_pose = sl.Pose()
py_translation = sl.Translation()
x = 0

i = 0
while i < 200:
    # Get the odometry information
    if zed.grab() <= sl.ERROR_CODE.SUCCESS:
        zed.get_position(odometry_pose, sl.REFERENCE_FRAME.WORLD)

    # Fake GNSS value, timestamped with the current system time
    x = x + 0.000000001
    gnss_data = sl.GNSSData()
    gnss_data.ts = sl.get_current_timestamp()
    gnss_data.set_coordinates(x, 0, 0)  # latitude, longitude, altitude
    fusion.ingest_gnss_data(gnss_data)

    # Get the fused position
    if fusion.process() == sl.FUSION_ERROR_CODE.SUCCESS:
        fused_tracking_state = fusion.get_position(camera_pose, sl.REFERENCE_FRAME.WORLD)
        if fused_tracking_state == sl.POSITIONAL_TRACKING_STATE.OK:
            translation = camera_pose.get_translation(py_translation)
            print("get position translation = ", translation.get())
    i = i + 1
```

> **Note**
>
> Because this tutorial uses synthetic GNSS data, only the fusion mechanics are demonstrated. With a real GNSS receiver, you must provide the full `GNSSData` (coordinates, timestamp, position covariance, latitude/longitude/altitude standard deviation) for accurate results.

### Close Fusion and the camera

**`C++`**

```cpp C++
fusion.close();
zed.close();
```

**`Python`**

```python Python
fusion.close()
zed.close()
```

For more information on the Fusion workflow, coordinate frames and calibration, read the [Global Localization](/docs/development/zed-sdk/modules/global-localization/) module documentation.