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 <noreply@anthropic.com>
This commit is contained in:
parent
fc5a7ed31b
commit
4f25fae6c8
1 changed files with 18 additions and 1 deletions
19
index.html
19
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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue