diff --git a/index.html b/index.html
index d5992b2..69686d3 100644
--- a/index.html
+++ b/index.html
@@ -539,6 +539,7 @@ const liveMarkers = new Map(); // imei → maplibregl.Marker (individual vehicl
const clusterMarkers = new Map(); // cluster_id → maplibregl.Marker (count bubbles)
let liveFeatures = []; // deduped (one device per vehicle) — see dedupeLiveFeatures
let cluster = null; // Supercluster index of the currently-filtered fleet
+let liveSpecialists = []; // crane/motorbike/pick-up — drawn individually, never clustered
const CLUSTER_RADIUS = 60; // px cluster radius
const CLUSTER_MAXZOOM = 11; // above this, clusters disband into individual pins (~city zoom)
const VEHICLE_META = new Map(); // plate → {cost_centre, assigned_city}
@@ -735,12 +736,22 @@ function filteredLiveFeatures() {
});
}
+// A vehicle is exempt from clustering exactly when it carries a specialist icon
+// (crane / motorbike / pick-up) — keyed off the feed's vehicle_type / fleet_segment.
+function isSpecialist(p) {
+ return !!(p && (SPECIALIST_ICONS[p.vehicle_type] || p.fleet_segment === 'specialist'));
+}
+
// Load the filtered fleet into supercluster, redraw bubbles+pins, recompute KPIs.
function applyLiveFilters() {
if (mode !== 'live') return;
const filtered = filteredLiveFeatures();
+ // Specialists are never clustered — they always render as individual icons so
+ // they stand out. Only the rest of the fleet feeds supercluster.
+ liveSpecialists = filtered.filter(f => isSpecialist(f.properties));
+ const clusterable = filtered.filter(f => !isSpecialist(f.properties));
cluster = new Supercluster({ radius: CLUSTER_RADIUS, maxZoom: CLUSTER_MAXZOOM });
- cluster.load(filtered.map(f => ({
+ cluster.load(clusterable.map(f => ({
type: 'Feature',
properties: { ...f.properties },
geometry: { type: 'Point', coordinates: f.geometry.coordinates },
@@ -799,6 +810,12 @@ function renderClusters() {
upsertLiveMarker(it.properties, c, it);
}
});
+ // Specialists: always drawn individually, at every zoom (never clustered).
+ liveSpecialists.forEach(f => {
+ if (!Array.isArray(f.geometry && f.geometry.coordinates)) return;
+ seenVeh.add(f.properties.imei);
+ upsertLiveMarker(f.properties, f.geometry.coordinates, f);
+ });
for (const [imei, m] of liveMarkers) { if (!seenVeh.has(imei)) { m.remove(); liveMarkers.delete(imei); } }
for (const [id, m] of clusterMarkers) { if (!seenClu.has(id)) { m.remove(); clusterMarkers.delete(id); } }
// If the popped-open vehicle got absorbed into a cluster, close its popup.