Skip to main content

GPS Tracking Overview

GPS tracking receives position data from an Apollon forwarding integration, stores it in TimescaleDB, and broadcasts live updates via WebSocket.

Data Flow

Apollon device → POST /v1/gps (with API key) → TimescaleDB → WebSocket "gps" channel
  1. Apollon POSTs a GPS payload to /v1/gps with an API key in the Authorization header.
  2. The API resolves a device from the request (header, API key, or NULL).
  3. Auto trip_id resolution: if a device was resolved, the API queries for an active trip (status = 'active' and gps_logging_enabled = true) linked to that device and automatically sets trip_id on the GPS point.
  4. The point is inserted into TimescaleDB.
  5. Redis caching: the API updates three caches -- per-device (gps:latest:{device_id}), global (gps:latest:global), and appends to the trip GPS cache if trip_id is set. All caches use a 5-minute TTL.
  6. WebSocket broadcast: a GpsUpdate message is sent to all clients subscribed to the "gps" channel.
  7. Geofence check: all active geofences are evaluated for proximity (see Geofences: Proximity Check).

GPS Data Fields

FieldTypeNullableDescription
idUUIDnoPoint ID
device_idstringyesResolved device (see Device resolution)
trip_idUUIDyesActive trip at time of insert
latitudefloatnoDegrees (WGS-84)
longitudefloatnoDegrees (WGS-84)
altitudefloatyesMeters
headingfloatyesDegrees 0–360
speed_kmhfloatnokm/h
speed_mpsfloatnom/s (raw from Apollon)
speed_mphfloatnomph
speed_knotsfloatnoNautical knots
pdopfloatyesPosition dilution of precision
hdopfloatyesHorizontal dilution of precision
vdopfloatyesVertical dilution of precision
timestamptimestamptznoISO 8601 timestamp
created_attimestampnoServer insert time

REST Endpoints

MethodPathPermissionDescription
POST/v1/gpsAPI keyIngest GPS point
GET/v1/gpsgps:readList GPS data (paginated)
GET/v1/gps/currentgps:readLatest GPS point (optionally per device)
GET/v1/gps/current/devicesgps:readLatest GPS point per device
GET/v1/gps/{id}gps:readGet single point
DELETE/v1/gps/{id}gps:deleteDelete point (blocked if linked to active trip)

Ingest Payload

POST /v1/gps
Authorization: Bearer <api-key>
X-Device-ID: <device-uuid> // optional

{
"latitude": 49.4875,
"longitude": 8.4660,
"altitude": 102.3,
"heading": 270.0,
"timestamp": 1750000000000,
"speed_kmh": 72.4,
"speed_mps": 20.1,
"speed_mph": 45.0,
"speed_knots": 39.1,
"pdop": 1.2,
"hdop": 0.9,
"vdop": 0.8
}

Querying Current Position

Single Device or Global

GET /v1/gps/current?device_id=my-tracker-1

Returns the latest GPS point for a specific device. Omit device_id to get the globally latest point across all devices. Results are cached in Redis (gps:latest:{deviceId} / gps:latest:global, TTL 5 min).

GraphQL:

query { currentGps(deviceId: "my-tracker-1") { latitude longitude heading speedKmh timestamp } }

All Devices

GET /v1/gps/current/devices

Returns the latest GPS point per device using DISTINCT ON (device_id). Useful for multi-device live maps.

GraphQL:

query { currentGpsPerDevice { deviceId latitude longitude heading speedKmh timestamp } }

WebSocket Live Updates

Subscribe to channel "gps" to receive GpsUpdate messages after every insert.

interface GpsUpdate {
type: 'gpsUpdate';
data: {
id: string;
deviceId: string | null;
tripId: string | null;
latitude: number;
longitude: number;
altitude: number | null;
heading: number | null;
speedKmh: number;
speedMps: number;
speedMph: number;
speedKnots: number;
pdop: number | null;
hdop: number | null;
vdop: number | null;
timestamp: number;
};
}

See the WebSocket API for connection details.

Redis Caching on Ingest

Every POST /v1/gps updates three Redis caches after the TimescaleDB insert:

Cache KeyContentTTL
gps:latest:globalFull GPS point JSON5 min
gps:latest:{device_id}Full GPS point JSON (per device)5 min
Trip GPS cache (append)Appended GPS point for the resolved trip_idmanaged by trip lifecycle

The GET /v1/gps/current and GET /v1/gps/current/devices endpoints read from these caches before falling back to TimescaleDB.

Auto Trip Resolution

When a GPS point is ingested and a device_id is resolved, the API automatically queries for an active trip linked to that device:

SELECT id FROM "Trip"
WHERE device_id = $1 AND status = 'active' AND gps_logging_enabled = true
LIMIT 1

If found, the GPS point's trip_id is set to that trip. This means GPS data is automatically associated with trips without the sender needing to know about trips -- the device link is sufficient.

Deletion Protection

A GPS point with a trip_id referencing a non-completed trip cannot be deleted. The DELETE endpoint returns 409 Conflict in that case. Complete the trip first, then delete the point.

  • Devices — link API keys to named devices
  • Geofences — proximity alerts on GPS insert
  • Settings — speed unit, coordinate format, retention
  • Trips: Overview — GPS logging for transport operations