perf(web): reconcile fleet map incrementally
This commit is contained in:
@@ -85,6 +85,24 @@ type PlateLabelPoint = {
|
||||
latitude: number;
|
||||
};
|
||||
|
||||
type LabelMarkerEntry = {
|
||||
marker: AMapOverlay;
|
||||
signature: string;
|
||||
};
|
||||
|
||||
function massPointSignature(monitorMap: MonitorMapResponse | undefined, points: VehicleRealtimeRow[]) {
|
||||
if (monitorMap) return [
|
||||
`mode:${monitorMap.mode}`,
|
||||
...monitorMap.clusters.map((cluster) => `c:${cluster.id}:${cluster.longitude}:${cluster.latitude}:${cluster.count}`),
|
||||
...monitorMap.points.map((point) => `p:${point.vin}:${point.longitude}:${point.latitude}:${point.status}:${point.plate || ''}`)
|
||||
].join('|');
|
||||
return ['vehicles', ...points.map((point) => `p:${point.vin}:${point.longitude}:${point.latitude}:${vehicleStatus(point)}:${point.plate || ''}`)].join('|');
|
||||
}
|
||||
|
||||
function labelMarkerSignature(point: PlateLabelPoint, selected: boolean, placement?: { direction: 'left' | 'right'; textOffset: [number, number] }) {
|
||||
return `${point.longitude}:${point.latitude}:${point.plate || point.vin}:${selected ? 1 : 0}:${placement?.direction ?? 'right'}:${placement?.textOffset.join(',') ?? '8,0'}`;
|
||||
}
|
||||
|
||||
function densePlatePlacements(points: PlateLabelPoint[]) {
|
||||
const buckets = new Map<string, PlateLabelPoint[]>();
|
||||
for (const point of points) {
|
||||
@@ -128,6 +146,11 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
const labelsRef = useRef<AMapLabelsLayer | null>(null);
|
||||
const denseLabelsRef = useRef<AMapLabelsLayer | null>(null);
|
||||
const selectionRef = useRef<AMapOverlay | null>(null);
|
||||
const labelMarkersRef = useRef(new Map<string, LabelMarkerEntry>());
|
||||
const labelLayerModeRef = useRef<'normal' | 'dense' | ''>('');
|
||||
const labelLayerStateRef = useRef('');
|
||||
const massDataSignatureRef = useRef('__unset__');
|
||||
const massStyleSignatureRef = useRef('__unset__');
|
||||
const onSelectRef = useRef(onSelect);
|
||||
const onSelectVinRef = useRef(onSelectVin);
|
||||
const onViewportChangeRef = useRef(onViewportChange);
|
||||
@@ -262,6 +285,8 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
}
|
||||
if (stopFollowing) mapInstance?.off?.('dragstart', stopFollowing);
|
||||
massRef.current?.setMap(null);
|
||||
labelsRef.current?.clear();
|
||||
denseLabelsRef.current?.clear();
|
||||
labelsRef.current?.setMap(null);
|
||||
denseLabelsRef.current?.setMap(null);
|
||||
selectionRef.current?.setMap?.(null);
|
||||
@@ -270,6 +295,11 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
labelsRef.current = null;
|
||||
denseLabelsRef.current = null;
|
||||
selectionRef.current = null;
|
||||
labelMarkersRef.current.clear();
|
||||
labelLayerModeRef.current = '';
|
||||
labelLayerStateRef.current = '';
|
||||
massDataSignatureRef.current = '__unset__';
|
||||
massStyleSignatureRef.current = '__unset__';
|
||||
amapRef.current = null;
|
||||
mapRef.current = null;
|
||||
};
|
||||
@@ -280,13 +310,19 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
const AMap = amapRef.current;
|
||||
if (!mass || !AMap) return;
|
||||
clustersRef.current = new Map((monitorMap?.clusters ?? []).map((cluster) => [cluster.id, cluster]));
|
||||
const baseStyles = COLORS.map((color) => ({ url: dotDataUrl(color), anchor: new AMap.Pixel(9, 9), size: new AMap.Size(18, 18) }));
|
||||
const clusterCounts = [...new Set((monitorMap?.clusters ?? []).map((cluster) => cluster.count))].sort((a, b) => a - b);
|
||||
const clusterStyles = clusterCounts.map((count) => {
|
||||
const visual = clusterVisual(count);
|
||||
return { url: visual.url, anchor: new AMap.Pixel(visual.diameter / 2, visual.diameter / 2), size: new AMap.Size(visual.diameter, visual.diameter) };
|
||||
});
|
||||
mass.setStyle?.([...baseStyles, ...clusterStyles]);
|
||||
const styleSignature = clusterCounts.join(',');
|
||||
if (massStyleSignatureRef.current !== styleSignature) {
|
||||
const baseStyles = COLORS.map((color) => ({ url: dotDataUrl(color), anchor: new AMap.Pixel(9, 9), size: new AMap.Size(18, 18) }));
|
||||
const clusterStyles = clusterCounts.map((count) => {
|
||||
const visual = clusterVisual(count);
|
||||
return { url: visual.url, anchor: new AMap.Pixel(visual.diameter / 2, visual.diameter / 2), size: new AMap.Size(visual.diameter, visual.diameter) };
|
||||
});
|
||||
mass.setStyle?.([...baseStyles, ...clusterStyles]);
|
||||
massStyleSignatureRef.current = styleSignature;
|
||||
}
|
||||
const dataSignature = massPointSignature(monitorMap, points);
|
||||
if (massDataSignatureRef.current === dataSignature) return;
|
||||
const clusterStyleIndexes = new Map(clusterCounts.map((count, index) => [count, COLORS.length + index]));
|
||||
const data: AMapMassPoint[] = monitorMap ? [
|
||||
...monitorMap.clusters.map((cluster) => ({ lnglat: wgs84ToGcj02(cluster.longitude, cluster.latitude), style: clusterStyleIndexes.get(cluster.count) ?? COLORS.length, id: cluster.id, label: `${cluster.count} 辆` })),
|
||||
@@ -295,6 +331,7 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
lnglat: wgs84ToGcj02(vehicle.longitude, vehicle.latitude), style: styleIndex(vehicle), id: vehicle.vin, label: vehicle.plate || vehicle.vin
|
||||
}));
|
||||
mass.setData(data);
|
||||
massDataSignatureRef.current = dataSignature;
|
||||
}, [monitorMap, points, state]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -303,13 +340,6 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
const AMap = amapRef.current;
|
||||
const map = mapRef.current;
|
||||
if (!labels || !denseLabels || !AMap?.LabelMarker || !map) return;
|
||||
labels.clear();
|
||||
denseLabels.clear();
|
||||
if (!showLabels && !selectedVin) {
|
||||
labels.setMap(null);
|
||||
denseLabels.setMap(null);
|
||||
return;
|
||||
}
|
||||
const mapLabelPoints = (monitorMap
|
||||
? monitorMap.points
|
||||
: points.map((vehicle) => ({ ...vehicle, status: vehicleStatus(vehicle) })));
|
||||
@@ -318,13 +348,31 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
: mapLabelPoints;
|
||||
const labelPoints = showLabels ? allLabelPoints : allLabelPoints.filter((point) => point.vin === selectedVin);
|
||||
const showEveryPlate = mapZoom >= 19;
|
||||
const layerMode = showEveryPlate ? 'dense' : 'normal';
|
||||
const activeLabels = showEveryPlate ? denseLabels : labels;
|
||||
labels.setMap(showEveryPlate ? null : map);
|
||||
denseLabels.setMap(showEveryPlate ? map : null);
|
||||
if (labelLayerModeRef.current && labelLayerModeRef.current !== layerMode) {
|
||||
(labelLayerModeRef.current === 'dense' ? denseLabels : labels).clear();
|
||||
labelMarkersRef.current.clear();
|
||||
}
|
||||
labelLayerModeRef.current = layerMode;
|
||||
const layerState = `${layerMode}:${labelPoints.length > 0 ? 'visible' : 'hidden'}`;
|
||||
if (labelLayerStateRef.current !== layerState) {
|
||||
labels.setMap(layerMode === 'normal' && labelPoints.length ? map : null);
|
||||
denseLabels.setMap(layerMode === 'dense' && labelPoints.length ? map : null);
|
||||
labelLayerStateRef.current = layerState;
|
||||
}
|
||||
const densePlacements = showEveryPlate ? densePlatePlacements(labelPoints) : null;
|
||||
const markers = labelPoints.map((point) => {
|
||||
const nextMarkers = new Map<string, LabelMarkerEntry>();
|
||||
const markersToAdd: AMapOverlay[] = [];
|
||||
for (const point of labelPoints) {
|
||||
const placement = densePlacements?.get(point.vin);
|
||||
return new AMap.LabelMarker!({
|
||||
const signature = labelMarkerSignature(point, point.vin === selectedVin, placement);
|
||||
const existing = labelMarkersRef.current.get(point.vin);
|
||||
if (existing?.signature === signature) {
|
||||
nextMarkers.set(point.vin, existing);
|
||||
continue;
|
||||
}
|
||||
const marker = new AMap.LabelMarker!({
|
||||
name: point.plate || point.vin,
|
||||
position: wgs84ToGcj02(point.longitude, point.latitude),
|
||||
rank: point.vin === selectedVin ? 100 : 1,
|
||||
@@ -350,8 +398,15 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (markers.length) activeLabels.add(markers);
|
||||
nextMarkers.set(point.vin, { marker, signature });
|
||||
markersToAdd.push(marker);
|
||||
}
|
||||
const markersToRemove = [...labelMarkersRef.current.entries()]
|
||||
.filter(([vin, entry]) => nextMarkers.get(vin) !== entry)
|
||||
.map(([, entry]) => entry.marker);
|
||||
if (markersToRemove.length) activeLabels.remove(markersToRemove);
|
||||
if (markersToAdd.length) activeLabels.add(markersToAdd);
|
||||
labelMarkersRef.current = nextMarkers;
|
||||
}, [mapZoom, monitorMap, points, selectedTarget, selectedVin, showLabels, state]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -381,9 +436,10 @@ export function FleetMap({ vehicles, selectedVin, onSelect, monitorMap, onSelect
|
||||
} else if (selectionPositionRef.current && selectionPositionRef.current !== positionKey && followSelectedRef.current) {
|
||||
mapRef.current.panTo?.(mapPosition, 650);
|
||||
}
|
||||
const previousPositionKey = selectionPositionRef.current;
|
||||
selectionPositionRef.current = positionKey;
|
||||
if (selectionKeyRef.current === selectionKey && selectionRef.current) {
|
||||
selectionRef.current.setPosition?.(mapPosition);
|
||||
if (previousPositionKey !== positionKey) selectionRef.current.setPosition?.(mapPosition);
|
||||
return;
|
||||
}
|
||||
selectionRef.current?.setMap?.(null);
|
||||
|
||||
Reference in New Issue
Block a user