feat(monitor): match motion to report intervals
This commit is contained in:
@@ -19,8 +19,10 @@ import type { MonitorViewport } from '../hooks/useMonitorData';
|
||||
|
||||
const COLORS = ['#12a46f', '#9aa6b7', '#1677ff', '#f59e0b', '#ef4444'];
|
||||
const CLUSTER_VISUAL_CACHE_LIMIT = 256;
|
||||
const POINT_MOVE_DURATION_MS = 5_000;
|
||||
const POINT_MOVE_FRAME_MS = 50;
|
||||
const DEFAULT_POINT_MOVE_DURATION_MS = 15_000;
|
||||
const MIN_POINT_MOVE_DURATION_MS = 1_000;
|
||||
const MAX_POINT_MOVE_DURATION_MS = 120_000;
|
||||
const POINT_MOVE_FRAME_MS = 80;
|
||||
const MAP_INTERACTION_PAN_DURATION_MS = 650;
|
||||
const clusterVisualCache = new Map<number, { diameter: number; url: string }>();
|
||||
|
||||
@@ -154,6 +156,7 @@ export function massPointFingerprint(
|
||||
fingerprint.add(point.latitude);
|
||||
fingerprint.add(point.status);
|
||||
fingerprint.add(point.plate || '');
|
||||
fingerprint.add(point.reportIntervalMs ?? '');
|
||||
}
|
||||
} else {
|
||||
fingerprint.add(points.length);
|
||||
@@ -163,6 +166,7 @@ export function massPointFingerprint(
|
||||
fingerprint.add(point.latitude);
|
||||
fingerprint.add(vehicleStatus(point));
|
||||
fingerprint.add(point.plate || '');
|
||||
fingerprint.add(point.reportIntervalMs ?? '');
|
||||
}
|
||||
}
|
||||
return fingerprint.value();
|
||||
@@ -172,25 +176,38 @@ function labelMarkerSignature(point: PlateLabelPoint, selected: boolean, placeme
|
||||
return `${point.plate || point.vin}:${selected ? 1 : 0}:${placement?.direction ?? 'right'}:${placement?.textOffset.join(',') ?? '8,0'}`;
|
||||
}
|
||||
|
||||
function interpolateMassPointsFrom(previousByID: Map<string, AMapMassPoint>, target: AMapMassPoint[], progress: number, eligibleIDs?: Set<string>) {
|
||||
const bounded = Math.min(1, Math.max(0, progress));
|
||||
const eased = 1 - (1 - bounded) ** 3;
|
||||
function interpolateMassPointsFrom(previousByID: Map<string, AMapMassPoint>, target: AMapMassPoint[], progressFor: (id: string) => number, eligibleIDs?: Set<string>) {
|
||||
return target.map((point) => {
|
||||
if (eligibleIDs && !eligibleIDs.has(point.id)) return point;
|
||||
const start = previousByID.get(point.id);
|
||||
if (!start || start.lnglat[0] === point.lnglat[0] && start.lnglat[1] === point.lnglat[1]) return point;
|
||||
const progress = Math.min(1, Math.max(0, progressFor(point.id)));
|
||||
return {
|
||||
...point,
|
||||
lnglat: [
|
||||
start.lnglat[0] + (point.lnglat[0] - start.lnglat[0]) * eased,
|
||||
start.lnglat[1] + (point.lnglat[1] - start.lnglat[1]) * eased
|
||||
start.lnglat[0] + (point.lnglat[0] - start.lnglat[0]) * progress,
|
||||
start.lnglat[1] + (point.lnglat[1] - start.lnglat[1]) * progress
|
||||
] as [number, number]
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function interpolateMassPoints(previous: AMapMassPoint[], target: AMapMassPoint[], progress: number) {
|
||||
return interpolateMassPointsFrom(new Map(previous.map((point) => [point.id, point])), target, progress);
|
||||
return interpolateMassPointsFrom(new Map(previous.map((point) => [point.id, point])), target, () => progress);
|
||||
}
|
||||
|
||||
export function pointMoveDurationMs(reportIntervalMs?: number) {
|
||||
if (!Number.isFinite(reportIntervalMs) || Number(reportIntervalMs) <= 0) return DEFAULT_POINT_MOVE_DURATION_MS;
|
||||
return Math.min(MAX_POINT_MOVE_DURATION_MS, Math.max(MIN_POINT_MOVE_DURATION_MS, Math.round(Number(reportIntervalMs))));
|
||||
}
|
||||
|
||||
export function interpolateMassPointsByElapsed(previous: AMapMassPoint[], target: AMapMassPoint[], elapsedMs: number, durationByID: Map<string, number>, eligibleIDs?: Set<string>) {
|
||||
return interpolateMassPointsFrom(
|
||||
new Map(previous.map((point) => [point.id, point])),
|
||||
target,
|
||||
(id) => elapsedMs / (durationByID.get(id) ?? DEFAULT_POINT_MOVE_DURATION_MS),
|
||||
eligibleIDs
|
||||
);
|
||||
}
|
||||
|
||||
function movingPointIDs(previous: AMapMassPoint[], target: AMapMassPoint[]) {
|
||||
@@ -449,6 +466,8 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
] : fallbackPoints.map((vehicle) => ({
|
||||
lnglat: wgs84ToGcj02(vehicle.longitude, vehicle.latitude), style: styleIndex(vehicle), id: vehicle.vin, label: vehicle.plate || vehicle.vin
|
||||
}));
|
||||
const moveDurationByID = new Map((monitorMode ? mapPoints : fallbackPoints)
|
||||
.map((point) => [point.vin, pointMoveDurationMs(point.reportIntervalMs)]));
|
||||
massDataSignatureRef.current = dataSignature;
|
||||
if (pointAnimationFrameRef.current !== undefined) window.cancelAnimationFrame(pointAnimationFrameRef.current);
|
||||
pointAnimationFrameRef.current = undefined;
|
||||
@@ -465,6 +484,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
}
|
||||
let startedAt: number | undefined;
|
||||
let lastPaintAt = -Infinity;
|
||||
const longestMoveDuration = Math.max(...[...movingIDs].map((id) => moveDurationByID.get(id) ?? DEFAULT_POINT_MOVE_DURATION_MS));
|
||||
const paint = (frameData: AMapMassPoint[]) => {
|
||||
mass.setData(frameData);
|
||||
renderedMassDataRef.current = frameData;
|
||||
@@ -478,12 +498,13 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
};
|
||||
const animate = (timestamp: number) => {
|
||||
startedAt ??= timestamp;
|
||||
const progress = Math.min(1, (timestamp - startedAt) / POINT_MOVE_DURATION_MS);
|
||||
if (timestamp - lastPaintAt >= POINT_MOVE_FRAME_MS || progress === 1) {
|
||||
paint(interpolateMassPointsFrom(previousDataByID, data, progress, movingIDs));
|
||||
const elapsed = timestamp - startedAt;
|
||||
const complete = elapsed >= longestMoveDuration;
|
||||
if (timestamp - lastPaintAt >= POINT_MOVE_FRAME_MS || complete) {
|
||||
paint(interpolateMassPointsFrom(previousDataByID, data, (id) => elapsed / (moveDurationByID.get(id) ?? DEFAULT_POINT_MOVE_DURATION_MS), movingIDs));
|
||||
lastPaintAt = timestamp;
|
||||
}
|
||||
if (progress < 1) pointAnimationFrameRef.current = window.requestAnimationFrame(animate);
|
||||
if (!complete) pointAnimationFrameRef.current = window.requestAnimationFrame(animate);
|
||||
else {
|
||||
pointAnimationFrameRef.current = undefined;
|
||||
movingPointIDsRef.current.clear();
|
||||
@@ -599,7 +620,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
else mapRef.current.panTo?.(mapPosition, MAP_INTERACTION_PAN_DURATION_MS);
|
||||
centeredVinRef.current = selectedVin;
|
||||
} else if (selectionPositionRef.current && selectionPositionRef.current !== positionKey && followSelectedRef.current) {
|
||||
mapRef.current.panTo?.(mapPosition, POINT_MOVE_DURATION_MS);
|
||||
mapRef.current.panTo?.(mapPosition, pointMoveDurationMs(target.reportIntervalMs));
|
||||
}
|
||||
const previousPositionKey = selectionPositionRef.current;
|
||||
selectionPositionRef.current = positionKey;
|
||||
|
||||
Reference in New Issue
Block a user