feat(live): zoom-relative marker sizing + live plate/cost-centre filtering
- Markers now scale with zoom (--veh-scale, ~0.42 at z5 → 1.20 at z14) via a transform on .veh-inner, so they no longer bloat at country zoom; pins stay anchored on their coordinate (verified 0px drift). - Selecting a plate or cost centre now filters the LIVE markers immediately and recomputes the header KPIs (previously the filter card only fed Show trips, so selections didn't reflect on the live map). Time period still applies to trips. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
50163536e3
commit
135253d37d
1 changed files with 44 additions and 2 deletions
46
index.html
46
index.html
|
|
@ -181,7 +181,12 @@
|
|||
positioning context is the inner wrapper below, so a class change can't
|
||||
reflow the markers. */
|
||||
.veh-marker { cursor: pointer; will-change: transform; }
|
||||
.veh-inner { position: relative; width: 32px; height: 32px; }
|
||||
/* Scaled by zoom via --veh-scale (set in updateVehScale). transform-origin
|
||||
centre keeps the pin anchored on its coordinate as it grows/shrinks. */
|
||||
.veh-inner {
|
||||
position: relative; width: 32px; height: 32px;
|
||||
transform: scale(var(--veh-scale, 1)); transform-origin: center center;
|
||||
}
|
||||
.veh-pin {
|
||||
width: 32px; height: 32px; border-radius: 50%;
|
||||
background: var(--c, var(--parked));
|
||||
|
|
@ -385,7 +390,8 @@ function ensureMap() {
|
|||
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right');
|
||||
popup = new maplibregl.Popup({ closeButton: true, closeOnClick: false, offset: 20 });
|
||||
popup.on('close', () => { openPopupImei = null; popupStuck = false; });
|
||||
map.on('load', () => POIS.forEach(addPoiMarker));
|
||||
map.on('load', () => { POIS.forEach(addPoiMarker); updateVehScale(); });
|
||||
map.on('zoom', updateVehScale);
|
||||
map.on('click', e => {
|
||||
if (mode !== 'live') return;
|
||||
if (!popupStuck) return;
|
||||
|
|
@ -496,6 +502,9 @@ function renderLive() {
|
|||
if (still) showLivePopup(still, true);
|
||||
else { popup.remove(); openPopupImei = null; popupStuck = false; }
|
||||
}
|
||||
// Honour the current plate/cost-centre filter + size markers for this zoom.
|
||||
applyLiveFilters();
|
||||
updateVehScale();
|
||||
};
|
||||
if (map.isStyleLoaded()) drawMarkers(); else map.once('load', drawMarkers);
|
||||
|
||||
|
|
@ -714,13 +723,46 @@ function applyVehicleAutoFilter() {
|
|||
const metas = selected.map(p => VEHICLE_META.get(p)).filter(Boolean);
|
||||
const sharedCC = collapse(metas.map(m => m.cost_centre));
|
||||
setSelectValue('f-cc', sharedCC ?? '');
|
||||
applyLiveFilters(); // reflect the plate selection on the live map immediately
|
||||
}
|
||||
function collapse(values) { if (!values.length) return null; const f = values[0]; return values.every(v => v === f) ? f : null; }
|
||||
function setSelectValue(id, value) { const el = document.getElementById(id); const opt = Array.from(el.options).find(o => o.value === value); el.value = opt ? value : ''; }
|
||||
|
||||
// Scale the live markers with the zoom level so they don't bloat at country
|
||||
// zoom or vanish when zoomed in. Linear from z5 → z14.
|
||||
function updateVehScale() {
|
||||
if (!map) return;
|
||||
const t = Math.max(0, Math.min(1, (map.getZoom() - 5) / 9));
|
||||
document.getElementById('map').style.setProperty('--veh-scale', (0.42 + t * 0.78).toFixed(3));
|
||||
}
|
||||
|
||||
// Filter the LIVE markers by the selected plate(s) + cost centre, and recompute
|
||||
// the header KPIs to match. Time period only applies to trips, not live.
|
||||
function applyLiveFilters() {
|
||||
if (mode !== 'live') return;
|
||||
const plates = new Set(Array.from(document.getElementById('f-vehicle').selectedOptions).map(o => o.value).filter(Boolean));
|
||||
const cc = document.getElementById('f-cc').value;
|
||||
let total = 0, moving = 0, parked = 0, offline = 0; const speeds = [];
|
||||
(lastLivePayload?.geojson?.features || []).forEach(f => {
|
||||
const p = f.properties; const m = liveMarkers.get(p.imei); if (!m) return;
|
||||
const pass = (plates.size === 0 || plates.has(p.vehicle_number)) && (!cc || p.cost_centre === cc);
|
||||
m.getElement().style.display = pass ? '' : 'none';
|
||||
if (!pass) return;
|
||||
total++;
|
||||
const st = vehicleState(p);
|
||||
if (st === 'offline') offline++;
|
||||
else if (st === 'active') { moving++; const sp = Number(p.speed || 0); if (sp > 0) speeds.push(sp); }
|
||||
else parked++;
|
||||
});
|
||||
speeds.sort((a, b) => a - b);
|
||||
renderLiveKPIs({ total, moving, parked, offline, median: speeds.length ? speeds[Math.floor(speeds.length / 2)] : null, last_batch_utc: lastLivePayload?.summary?.last_batch_utc });
|
||||
}
|
||||
|
||||
document.getElementById('f-period').addEventListener('change', e => {
|
||||
document.getElementById('custom').classList.toggle('show', e.target.value === 'custom');
|
||||
});
|
||||
// Selecting a cost centre filters the live map immediately.
|
||||
document.getElementById('f-cc').addEventListener('change', applyLiveFilters);
|
||||
function periodToRange(period, cs, ce) {
|
||||
const today = new Date(); const fmt = d => d.toISOString().slice(0, 10);
|
||||
const minus = n => { const x = new Date(today); x.setDate(x.getDate() - n); return x; };
|
||||
|
|
|
|||
Loading…
Reference in a new issue