> 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.

# Using GNSS Data in Your Application

Once your GNSS receiver is [set up](/docs/development/zed-sdk/modules/global-localization/gnss/setting-up-gnss-rtk), your application must read its measurements, convert them to `sl::GNSSData`, and ingest them into the Fusion module with `ingestGNSSData`, as shown in the [Global Localization overview](/docs/development/zed-sdk/modules/global-localization#retrieving-fused-data).

This page shows how to read GNSS data from `gpsd` on Linux. On Windows, see [Setting up GNSS / RTK on Windows](/docs/development/zed-sdk/modules/global-localization/gnss/setting-up-gnss-windows#reading-gnss-data-in-your-application).

## Reading data from gpsd

### Python

To access GNSS data in Python, you can use the [gpsdclient](https://github.com/tfeldmann/gpsdclient) library. It provides a Python interface to the `gpsd` daemon, giving access to latitude, longitude, altitude, speed, estimated errors, satellites in view, and more.

You can install `gpsdclient` with:

```bash
pip install gpsdclient
```

Here is an example of a simple script using `gpsdclient`:

**`Python`**

```python Python
from gpsdclient import GPSDClient

# get your data as json strings:
with GPSDClient(host="127.0.0.1") as client:
    for result in client.json_stream():
        print(result)

# or as python dicts (optionally convert time information to `datetime` objects)
with GPSDClient() as client:
    for result in client.dict_stream(convert_datetime=True, filter=["TPV"]):
        print("Latitude: %s" % result.get("lat", "n/a"))
        print("Longitude: %s" % result.get("lon", "n/a"))

# you can optionally filter by report class
with GPSDClient() as client:
    for result in client.dict_stream(filter=["TPV", "SKY"]):
        print(result)
```

The position is contained in the `TPV` (*Time Position Velocity*) reports. The `SKY` reports describe the satellites in view.

### C++

To access GNSS data in C++, you can use the [libgpsmm](https://gpsd.io/libgpsmm.html) library.

You can install `libgpsmm` with:

```bash
sudo apt install libgps-dev
```

Here is an example of a simple program using `libgpsmm`:

**`C++`**

```cpp C++
#include <libgpsmm.h>
#include <iostream>

int main() {
    gpsmm gps_data("localhost", DEFAULT_GPSD_PORT);

    if (gps_data.stream(WATCH_ENABLE | WATCH_JSON) == nullptr) {
        std::cerr << "Failed to open GPS connection." << std::endl;
        return 1;
    }

    while (true) {
        if (gps_data.waiting(500)) {
            struct gps_data_t* new_data = gps_data.read();
            if (new_data == nullptr) {
                std::cerr << "Error while reading GPS data." << std::endl;
            } else {
                std::cout << "Latitude: " << new_data->fix.latitude << ", Longitude: " << new_data->fix.longitude << std::endl;
            }
        }
    }

    gps_data.stream(WATCH_DISABLE);

    return 0;
}
```

> **Note**
>
> The `libgps-dev` package of your distribution may be older than the `gpsd` daemon you compiled from source. If you get connection or protocol errors, build your application against the headers and library installed with `gpsd` in `/usr/local`.

## Converting to sl::GNSSData

The `sl::GNSSData` structure ingested by the Fusion module contains:

| Field                 | Content                                                    | Source in gpsd                                           |
| --------------------- | ---------------------------------------------------------- | -------------------------------------------------------- |
| `setCoordinates()`    | Latitude and longitude in degrees, altitude in meters      | `lat`, `lon`, `altMSL`                                   |
| `position_covariance` | 3x3 covariance of the position in meters, in the ENU frame | `eph` (horizontal) and `epv` (vertical) estimated errors |
| `ts`                  | Timestamp of the measurement                               | `time`                                                   |
| `gnss_mode`           | No fix, 2D fix or 3D fix                                   | `mode`                                                   |
| `gnss_status`         | Single point, DGNSS, RTK float or RTK fixed                | `status`                                                 |

The covariance must reflect the actual uncertainty of each measurement. Set it from the errors estimated by the receiver: a constant value triggers the `GNSS_DATA_COVARIANCE_MUST_VARY` warning described in [Troubleshooting](/docs/development/zed-sdk/modules/global-localization/troubleshooting).

**`C++`**

```cpp C++
#include <libgpsmm.h>
#include <sl/Fusion.hpp>

// Convert a gpsd report with a valid fix (mode >= MODE_2D) to sl::GNSSData.
// Skip the reports where fix.eph or fix.epv is NaN before calling it.
sl::GNSSData toGNSSData(const gps_data_t* gpsd_data) {
    sl::GNSSData gnss_data;
    // Coordinates in degrees and meters
    gnss_data.setCoordinates(gpsd_data->fix.latitude, gpsd_data->fix.longitude, gpsd_data->fix.altMSL, false);

    // Covariance from the estimated horizontal and vertical errors, in meters
    const double eph = gpsd_data->fix.eph;
    const double epv = gpsd_data->fix.epv;
    gnss_data.position_covariance = {eph * eph, 0, 0,
                                     0, eph * eph, 0,
                                     0, 0, epv * epv};

    // Timestamp of the fix (GNSS time)
    gnss_data.ts.setMicroseconds(static_cast<uint64_t>(gpsd_data->fix.time.tv_sec) * 1000000ULL +
                                 gpsd_data->fix.time.tv_nsec / 1000);

    // Fix mode
    switch (gpsd_data->fix.mode) {
        case 1: gnss_data.gnss_mode = sl::GNSS_MODE::NO_FIX; break; // MODE_NO_FIX
        case 2: gnss_data.gnss_mode = sl::GNSS_MODE::FIX_2D; break; // MODE_2D
        case 3: gnss_data.gnss_mode = sl::GNSS_MODE::FIX_3D; break; // MODE_3D
        default: gnss_data.gnss_mode = sl::GNSS_MODE::UNKNOWN; break;
    }

    // Fix status (numeric values, as the constant names changed between gpsd versions)
    switch (gpsd_data->fix.status) {
        case 2: gnss_data.gnss_status = sl::GNSS_STATUS::DGNSS; break;     // DGPS
        case 3: gnss_data.gnss_status = sl::GNSS_STATUS::RTK_FIX; break;   // RTK fixed
        case 4: gnss_data.gnss_status = sl::GNSS_STATUS::RTK_FLOAT; break; // RTK float
        default: gnss_data.gnss_status = sl::GNSS_STATUS::SINGLE; break;   // GPS, DR, ...
    }

    return gnss_data;
}
```

**`Python`**

```python Python
import pyzed.sl as sl

GPSD_MODE = {1: sl.GNSS_MODE.NO_FIX, 2: sl.GNSS_MODE.FIX_2D, 3: sl.GNSS_MODE.FIX_3D}
GPSD_STATUS = {1: sl.GNSS_STATUS.SINGLE, 2: sl.GNSS_STATUS.DGNSS,
               3: sl.GNSS_STATUS.RTK_FIX, 4: sl.GNSS_STATUS.RTK_FLOAT}

# Convert a gpsd TPV report with a valid fix (mode >= 2), read with
# client.dict_stream(convert_datetime=True, filter=["TPV"]), to sl.GNSSData
def to_gnss_data(tpv):
    if any(key not in tpv for key in ("lat", "lon", "altMSL", "eph", "epv", "time")):
        return None  # incomplete report, skip it

    gnss_data = sl.GNSSData()
    # Coordinates in degrees and meters
    gnss_data.set_coordinates(tpv["lat"], tpv["lon"], tpv["altMSL"], False)

    # Covariance from the estimated horizontal and vertical errors, in meters
    eph = tpv["eph"]
    epv = tpv["epv"]
    gnss_data.position_covariances = [eph * eph, 0.0, 0.0,
                                      0.0, eph * eph, 0.0,
                                      0.0, 0.0, epv * epv]

    # Timestamp of the fix (GNSS time)
    ts = sl.Timestamp()
    ts.set_microseconds(int(tpv["time"].timestamp() * 1000000))
    gnss_data.ts = ts

    # Fix mode and status
    gnss_data.gnss_mode = GPSD_MODE.get(tpv["mode"], sl.GNSS_MODE.UNKNOWN).value
    gnss_data.gnss_status = GPSD_STATUS.get(tpv.get("status", 1), sl.GNSS_STATUS.SINGLE).value
    return gnss_data
```

When the `status` field is missing, and for the other `gpsd` statuses such as dead reckoning, the examples use `SINGLE`, as recommended for the `GNSS_DATA_NEED_FIX` warning. The estimated errors may also be unavailable for some reports, for example right after the first fix: `gpsd` then omits them from the JSON reports, and sets them to `NaN` in the C++ structure (check them with `std::isnan`). Skip these reports instead of ingesting a made-up covariance.

## Timestamps

The Fusion module matches GNSS data with camera images by timestamp, and camera images are timestamped with the **system clock** of the host. There are two ways to timestamp the GNSS data:

* **GNSS time**, as in the examples above. This is the most accurate option, as it gives the instant of the measurement, but it requires the system clock to be synchronized with UTC, for example with NTP, or better, with [PPS](/docs/development/zed-sdk/modules/global-localization/gnss/pps-time-synchronization).
* **Reception time**, with `sl::getCurrentTimeStamp()` when the data is read. It works with any system clock, but includes the latency of the receiver and of the serial link, typically a few tens of milliseconds.

If the system clock is not synchronized and the GNSS time is used, the Fusion module rejects the data with an `INVALID_TIMESTAMP` error. See [Data Synchronization](/docs/development/zed-sdk/modules/global-localization/data-synchronization) for details.

## Complete samples

The [Global Localization samples on GitHub](https://github.com/stereolabs/zed-sdk/tree/master/global%20localization) read GNSS data from `gpsd`, convert it to `sl::GNSSData` and ingest it into the Fusion module. They also show how to record GNSS data with an SVO file and replay it. See the [samples page](/docs/samples/global-localization) for a description of each sample.

## Using GNSS with ROS 2

The ZED ROS 2 Wrapper reads the GNSS data from a `sensor_msgs/msg/NavSatFix` topic and performs the fusion for you. See [Geo Tracking](/docs/integrations/ros-2/geo-tracking) for the compatible drivers and the configuration of the wrapper.