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
- Apollon POSTs a GPS payload to
/v1/gpswith an API key in theAuthorizationheader. - The API resolves a device from the request (header, API key, or NULL).
- Auto trip_id resolution: if a device was resolved, the API queries for an active trip (
status = 'active'andgps_logging_enabled = true) linked to that device and automatically setstrip_idon the GPS point. - The point is inserted into TimescaleDB.
- Redis caching: the API updates three caches -- per-device (
gps:latest:{device_id}), global (gps:latest:global), and appends to the trip GPS cache iftrip_idis set. All caches use a 5-minute TTL. - WebSocket broadcast: a
GpsUpdatemessage is sent to all clients subscribed to the"gps"channel. - Geofence check: all active geofences are evaluated for proximity (see Geofences: Proximity Check).
GPS Data Fields
| Field | Type | Nullable | Description |
|---|---|---|---|
id | UUID | no | Point ID |
device_id | string | yes | Resolved device (see Device resolution) |
trip_id | UUID | yes | Active trip at time of insert |
latitude | float | no | Degrees (WGS-84) |
longitude | float | no | Degrees (WGS-84) |
altitude | float | yes | Meters |
heading | float | yes | Degrees 0–360 |
speed_kmh | float | no | km/h |
speed_mps | float | no | m/s (raw from Apollon) |
speed_mph | float | no | mph |
speed_knots | float | no | Nautical knots |
pdop | float | yes | Position dilution of precision |
hdop | float | yes | Horizontal dilution of precision |
vdop | float | yes | Vertical dilution of precision |
timestamp | timestamptz | no | ISO 8601 timestamp |
created_at | timestamp | no | Server insert time |
REST Endpoints
| Method | Path | Permission | Description |
|---|---|---|---|
| POST | /v1/gps | API key | Ingest GPS point |
| GET | /v1/gps | gps:read | List GPS data (paginated) |
| GET | /v1/gps/current | gps:read | Latest GPS point (optionally per device) |
| GET | /v1/gps/current/devices | gps:read | Latest GPS point per device |
| GET | /v1/gps/{id} | gps:read | Get single point |
| DELETE | /v1/gps/{id} | gps:delete | Delete 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 Key | Content | TTL |
|---|---|---|
gps:latest:global | Full GPS point JSON | 5 min |
gps:latest:{device_id} | Full GPS point JSON (per device) | 5 min |
| Trip GPS cache (append) | Appended GPS point for the resolved trip_id | managed 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.
Related
- 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