From 4f25fae6c8285075ffffc66e4247b23e6a833a4e Mon Sep 17 00:00:00 2001 From: david kiania Date: Mon, 8 Jun 2026 15:59:47 +0300 Subject: [PATCH] feat(map): exempt specialist vehicles from clustering Crane/motorbike/pick-up are held out of the supercluster index and always rendered as individual markers at every zoom, so they never fold into a cluster bubble and always stand out. The rest of the fleet clusters as before. KPIs and legend are unaffected (still computed from the full filtered set). Co-Authored-By: Claude Opus 4.8 --- index.html | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) 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.