fleetnow/README.md
david kiania 4eef23677b docs: document map-overlay layers + recent map features
README: add a "Map overlay layers" section (how the toggleable layer system
works + how to add a layer in ~2 min), document fleet segmentation / department
colours / legend / POIs, refresh the file tree (layers/), and correct the deploy
note (Coolify auto-deploys on push to main).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 22:01:52 +03:00

156 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# FleetNow
A single-file map console that **merges live vehicle positions and historical
trips** into one view for the Fireside Communications / Tracksolid fleet.
> **Status:** v2 — live at <https://fleetnow.rahamafresh.com>. Deployed from this
> repo via Coolify (Dockerfile → nginx). Reads the `dashboard_api` read-API at
> `fleetapi.rahamafresh.com`.
>
> **2026-06-08 additions:** fleet segmentation (specialist vehicle icons that
> never cluster), per-department colour coordination + collapsible **Key** legend,
> persistent POIs (Fireside HQ, Safaricom HQ), and a **toggleable map-overlay
> layer system** (first layer: 232 Shell fuel stations). See *Map overlay layers*.
>
> **Deploy auto-fires on push to `main`** via Coolify (~23 min build); if it
> lags, hit Redeploy in Coolify.
## What it does
- Land on the **live fleet** — markers carry the **department (cost-centre) colour**,
a heading arrow, the plate tail, and a hover popup (status, driver, reverse-geocoded
address, heading, odometer, last fix, source). Markers **scale with the zoom level**.
Shape encodes recency so stakeholders read activity at a glance:
- **● circle** (full colour + heading arrow) — moving right now
- **■ square** (pastel colour, no arrow, ~half the size of a moving marker) — active within the last 24h, now stopped
- **grey ●** — offline (no fix in 24h)
- **One pin per vehicle (tracker + camera dedup).** Every vehicle carries a GPS
**tracker** (X3 / GT06E / AT4) *and* a **dashcam** (JC400P) that share the same
number plate. FleetNow collapses the pair to a single marker/dropdown entry:
the **tracker is primary**; if the tracker isn't reporting (>24h), it **falls
back to the camera**; if both report, the tracker wins. Pairing is by
normalised plate, so a stray space (`KDS 453 Y` vs `KDS 453Y`) still merges.
- **Clustering (zoomed out).** Vehicles group into amber count-bubbles (Folium /
Leaflet.MarkerCluster style, via `supercluster`); click a bubble to zoom and
expand it. Clusters disband into individual pins at ~city zoom (z11). Clustering
honours the active filter and applies to the live view only (not trip routes).
- **Fleet segmentation & department colours.** Specialist vehicles (crane /
motorbike / pick-up) get their own marker icons and are **never clustered**
(always individually visible); every cost centre has a fixed, distinct colour,
with a collapsible **Key** legend (bottom-left) listing only the centres on
screen. Non-operational vehicles (personal / management / Uganda-MTN) are
filtered out upstream in the read-API, so the live map shows the operational
fleet only.
- **Persistent POIs** — Fireside HQ and Safaricom HQ (Waiyaki Way) as labelled
reference markers.
- **Filters** (bottom-right card) apply to the live map *instantly*:
- **Number plate** — multi-select, sorted A→Z; picking a vehicle auto-fills its
cost centre + city.
- **Cost centre** and **Assigned city** — narrow the live fleet (and the KPI bar
recomputes to match).
- **Time** (Today / 1 week / 1 month / Custom) — applies to **trips**, not live.
- Drill into history: click a vehicle's dot → **Show trips**, or set
plate / cost centre / city + period and hit **Show trips** for a fleet-wide pull.
The map switches to **seq-coloured trip routes** with start/end markers and a
click-to-animate replay; the **● Live** pill returns to the live snapshot.
- In trips view a **context bar** (below the header) summarises the active filter
(vehicle / cost centre / city) plus the **first trip** and **last trip**
each with its **reverse-geocoded location and timestamp** — alongside the KPI
totals (trips, km, driving/idle hours, vehicles, drivers, date range).
- **Full-width map + two-tier bottom dock (no floating/side panels).** All controls
live in a bottom dock with two tiers: a **filter tier** on top and a **trip-card
tier** beneath (selection → results hierarchy). In **live** mode the filter row
is expanded and there are no cards; in **trips** mode the filters **collapse to a
one-line summary** (`Filters: KCA 542Q · roll out · nairobi · Last 1 month`, with
**Edit** to expand) and the trip cards show beneath — keeping the map tall.
- **Plate picker is a searchable combobox** — type to filter, click to add a
removable chip (multi-select), instead of a tall scrolling list. Cost centre /
city / time stay single-line; date pickers appear only for a custom range.
Trip cards scroll horizontally; click a card to fit + animate that route.
Live: <https://fleetnow.rahamafresh.com>
## Architecture
The whole app is **one self-contained `index.html`** (inline CSS + JS; MapLibre
GL JS loaded from a CDN). It has no build step and no local assets. It reads from
the existing dashboard read-API — it does **not** talk to the database directly.
```
index.html → the entire SPA
layers/*.geojson → static overlay data (gas stations, …), served at /layers/
Dockerfile → bakes index.html + layers/ into an nginx:alpine image (port 80)
nginx.conf → static serve + /healthz + no-cache on index.html
```
### Backend it depends on
`const API_BASE = 'https://fleetapi.rahamafresh.com';` (top of the `<script>` in
`index.html`). That service (`dashboard_api_rev.py` in the `tracksolid` repo)
exposes:
| Endpoint | Use |
|---|---|
| `GET /webhook/live-positions` | live snapshot `{summary, geojson}` |
| `GET /webhook/live-positions/track?vehicle_number=&hours=` | 1 h trail |
| `GET /webhook/fleet-dashboard` | filter options (plates, cost centres) |
| `POST /webhook/fleet-dashboard` | trips for a selection `{summary, geojson}` |
**CORS:** the API must allow the `https://fleetnow.rahamafresh.com` origin
(`DASHBOARD_CORS_ORIGINS`). It is in the code default; make sure the deployed
`dashboard_api` container's env includes it, then restart that container.
## Map overlay layers
Toggleable reference overlays (gas stations, etc.) sit behind the **Layers**
control (top-right, collapsed, all **off** by default). Each is a static GeoJSON
in `layers/`, rendered as a MapLibre **symbol** layer that **auto-declutters**
(`icon-allow-overlap: false` — sparse when zoomed out, all points reveal as you
zoom in), with a zoom-scaled icon (~8→16 px) and a **hover** label (one reused
popup, so only ever one is visible). Overlays render *under* the vehicle markers.
Shipped layers:
| Layer | Data | Points |
|---|---|---|
| Shell stations | `layers/shell_stations.geojson` (OSM `kenya-260605`) | 232 |
**To add a layer (≈2 min):**
1. Drop its point GeoJSON in `layers/<name>.geojson`.
2. Add one entry to the `OVERLAYS` array near the top of the `<script>` in
`index.html`:
```js
{ id: '<name>', label: '<Label>', url: 'layers/<name>.geojson',
iconSvg: <40×40 SVG string>, nameKey: 'name', defaultOn: false }
```
`iconSvg` is registered as the marker image (reuse `SHELL_ICON_SVG` as a
template). Nothing else to wire — `addOverlay()` builds the source + symbol
layer, and the Layers control lists it automatically.
3. Commit + push. The `Dockerfile` already `COPY`s `layers/` into nginx.
> The Shell layer was extracted from a Kenya OSM `.pbf` — the reproducible
> workflow (filter `amenity=fuel`, `brand=Shell`) lives in the `tracksolid` repo:
> `scripts/export_osm_pois.py` + `docs/OSM_POI_EXPORT.md`.
## Deploy (Coolify, git-based)
1. In Coolify, create a new **Application** from this git repo
(`https://repo.rahamafresh.com/kianiadee/fleetnow.git`), branch `main`,
build pack **Dockerfile**.
2. Set the **port** to `80`.
3. Add the domain **`fleetnow.rahamafresh.com`** (HTTPS / Let's Encrypt). Coolify
wires Traefik on the `coolify` network automatically.
4. Point DNS `fleetnow.rahamafresh.com` → the VPS (`31.97.44.246`) if not already.
5. Deploy. Every push to `main` redeploys; `index.html` is served `no-cache` so
changes appear immediately.
## Local preview
```bash
docker build -t fleetnow . && docker run --rm -p 8080:80 fleetnow
# open http://localhost:8080
```
> Loading from `localhost` will be CORS-blocked by the live API unless that
> origin is allow-listed. For pure UI work, run a same-origin proxy that forwards
> `/webhook/*` to `fleetapi.rahamafresh.com`.