Geofences
Geofences are circular zones defined by a center point and radius. When a GPS point is inserted, the API checks all active geofences and emits WebSocket events when a device enters or exits a zone.
Geofence Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Geofence ID |
name | string | Display name |
description | string? | Optional description |
latitude | float | Center latitude |
longitude | float | Center longitude |
radius_km | float | Trigger radius in kilometers |
is_active | bool | Inactive geofences are not checked |
metadata | JSONB? | Arbitrary module-specific data |
REST Endpoints
| Method | Path | Permission | Description |
|---|---|---|---|
| GET | /v1/geofences | geofences:read | List geofences |
| GET | /v1/geofences/{id} | geofences:read | Get geofence |
| POST | /v1/geofences | geofences:write | Create geofence |
| PATCH | /v1/geofences/{id} | geofences:write | Update geofence |
| DELETE | /v1/geofences/{id} | geofences:delete | Delete geofence |
GraphQL
geofences(page, limit)→PaginatedGeofencesgeofence(id)→GqlGeofencecreateGeofence(input)→GqlGeofenceupdateGeofence(id, input)→GqlGeofencedeleteGeofence(id)→Boolean
Create Geofence
POST /v1/geofences
{
"name": "Schleuse Feudenheim",
"latitude": 49.4875,
"longitude": 8.5370,
"radius_km": 0.5,
"metadata": { "type": "lock", "waterway": "Neckar" }
}
Proximity Check
On every POST /v1/gps insert, the API automatically runs a geofence proximity check as a side-effect in the create_gps_data handler (see check_geofences in crates/heimdall-rest/src/handlers/gps.rs). It computes the Haversine distance from the new GPS point to each active geofence center. Geofences within radius_km trigger an event.
The API tracks the last-known inside/outside state per (device_id, geofence_id) pair in memory using a GeofenceState (RwLock<HashMap>). This allows detecting enter (was outside, now inside) and exit (was inside, now outside) transitions rather than firing on every point.
GeofenceState is held in application memory and is not persisted. On API restart, all geofence state is lost. The first GPS point received after a restart will not detect enter/exit transitions because there is no previous state to compare against -- the state defaults to "outside" for all pairs. Transitions resume correctly from the second point onward once state is re-established.
WebSocket Events
Events are broadcast on the "geofence" channel.
interface GeofenceEventMessage {
type: 'geofenceEvent';
data: {
geofence_id: string;
geofence_name: string;
event: 'enter' | 'exit';
device_id: string | null;
distance_km: number;
metadata: any;
};
}
Subscribe to channel "geofence" alongside "gps" to receive both streams.
RBAC Permissions
| Permission | Description |
|---|---|
geofences:read | View geofences |
geofences:write | Create and update geofences |
geofences:delete | Delete geofences |
Scope
The current implementation covers CRUD and proximity checks. Polygon geofences and OBS overlay integrations (Schleusen-Alert) are out of scope for this release.