This document describes a practical, offline‑first architecture to integrate an on‑truck edge stack with either ArcGIS (ArcGIS Online/Enterprise) or QGIS (PostGIS/QGIS Server/QField/Mergin). It covers components, data flow, schemas, sync patterns, security, and example payloads.
The edge device (Pi‑5/Jetson) hosts sensors, a lightweight message broker (MQTT), a geofence engine, and a sync worker. It renders offline maps (tiles) for the driver tablet and publishes events (points + media) to GIS. The architecture is dual‑path so you can deploy either ArcGIS‑first or QGIS‑first without re‑wiring sensors.
truck/<route_id>/events/#.
[Sensors: camera, GNSS, temp/TVOC, etc.]
│
(MQTT topics)
│
[Edge Geo‑Service] ──► writes ► [GeoPackage (local)] + [Outbox (SQLite)]
│ │
│ └─► [Offline tiles (MBTiles)] for cab map
└─► [Geofence engine] compares GNSS to local polygons (schools/noise zones)
[Outbox] ─(batch/secure HTTPS)─► [ArcGIS Feature Service (points/lines)]
└─────────► [ArcGIS Add Attachment] (images/video)
▲
[Field Maps] ◄─┴─ syncs offline areas + shows events
[Outbox] ─(batch/HTTPS)─► [Sync API] ─► [PostGIS]
└──► [Object store] (S3) for media
QField (tablet) ◄──── WFS‑T / Mergin sync ───► PostGIS
Use normalized layers with small, stable fields. Store heavy media as attachments/objects; keep only URLs in features.
truck_events (point)| Field | Type | Example | Notes |
|---|---|---|---|
| event_id | GUID/String | 6f8c… | Edge‑generated UUID. |
| ts_utc | Datetime | 2025‑10‑01T13:22:06Z | ISO‑8601. |
| route_id | String | R‑12A | Route/day key. |
| event_type | String | contam_flag | See taxonomy below. |
| severity | Short | 2 | 0–3. |
| lat, lon | Double | 25.774,‑80.193 | WGS84. |
| azimuth | Short | 182 | Vehicle heading. |
| image_url / attach_id | String | …/img/abc.jpg | URL (QGIS) or Attachment Id (ArcGIS). |
| notes | Text | “Plastic bag in organics” | Short free text. |
truck_tracks (line or point‑time series)route_id, ts_utc, speed_kph, idle_flag, fuel_mode, soc_pct (if EV).contam_flag (hopper camera)bin_lift, overage, geofence_enter/exitnoise_peak, tvoc_peak, battery_riskincident_flag (safety/near‑miss)event_id uniqueness./healthz endpoint; MQTT LWT topic for device heartbeat.
POST https://services.arcgis.com/<orgid>/arcgis/rest/services/truck_events/FeatureServer/0/addFeatures
Content-Type: application/x-www-form-urlencoded
f=json&token=<ACCESS_TOKEN>&features=[{
"attributes": {
"event_id":"6f8c9…","ts_utc":1730409726,"route_id":"R-12A","event_type":"contam_flag","severity":2
},
"geometry": {"x": -122.3352,"y": 47.6080,"spatialReference":{"wkid":4326}}
}]
POST .../FeatureServer/0/{objectId}/addAttachment
Headers: Authorization: Bearer <ACCESS_TOKEN>
Form-Data: attachment=@frame_2025-11-01_132206.jpg
POST /geoserver/wfs
Content-Type: text/xml
<wfs:Transaction service="WFS" version="1.1.0">
<wfs:Insert>
<feature:truck_events>
<feature:event_id>6f8c9…</feature:event_id>
<feature:geom><gml:Point srsName="EPSG:4326"><gml:pos>47.6080 -122.3352</gml:pos></gml:Point></feature:geom>
<feature:event_type>contam_flag</feature:event_type>
</feature:truck_events>
</wfs:Insert>
</wfs:Transaction>
{
"type":"Feature",
"geometry":{"type":"Point","coordinates":[-122.3352,47.6080]},
"properties":{"event_id":"6f8c9…","ts_utc":"2025-11-01T13:22:06Z","route_id":"R-12A",
"event_type":"contam_flag","severity":2,"image_url":"file://.../frame.jpg"}
}
mqtt:
broker: mqtt://localhost:1883
topics:
events: truck/+/events/#
outbox:
db: /var/lib/ontruck/outbox.sqlite
batch_size: 100
retry_backoff_min: 120s
geofence:
gpkg: /opt/maps/geofences.gpkg
layers: ["schools","noise_zones"]
sync:
mode: arcgis # or "qgis"
arcgis:
feature_url: https://services.arcgis.com/<org>/.../FeatureServer/0
token_url: https://www.arcgis.com/sharing/rest/oauth2/token
client_id: ABC... # stored securely
qgis:
api_base: https://gis.example.com/api
media_bucket: s3://ontruck-media/