refactor(map3d): isolate ship hover overlay for icon flicker reduction
This commit is contained in:
부모
54d33a8670
커밋
30e6e584ee
@ -575,6 +575,7 @@ const FLAT_SHIP_ICON_SIZE_HIGHLIGHTED = 25;
|
||||
const FLAT_LEGACY_HALO_RADIUS = 14;
|
||||
const FLAT_LEGACY_HALO_RADIUS_SELECTED = 18;
|
||||
const FLAT_LEGACY_HALO_RADIUS_HIGHLIGHTED = 16;
|
||||
const EMPTY_MMSI_SET = new Set<number>();
|
||||
|
||||
const GLOBE_OVERLAY_PARAMS = {
|
||||
// In globe mode we want depth-testing against the globe so features on the far side don't draw through.
|
||||
@ -1079,6 +1080,13 @@ export function Map3D({
|
||||
onHoverPair,
|
||||
onClearPairHover,
|
||||
}: Props) {
|
||||
void onHoverFleet;
|
||||
void onClearFleetHover;
|
||||
void onHoverMmsi;
|
||||
void onClearMmsiHover;
|
||||
void onHoverPair;
|
||||
void onClearPairHover;
|
||||
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const mapRef = useRef<maplibregl.Map | null>(null);
|
||||
const overlayRef = useRef<MapboxOverlay | null>(null);
|
||||
@ -1196,6 +1204,18 @@ export function Map3D({
|
||||
(mmsi: number) => highlightedMmsiSetCombined.has(mmsi),
|
||||
[highlightedMmsiSetCombined],
|
||||
);
|
||||
const baseHighlightedMmsiSet = useMemo(() => {
|
||||
const out = new Set<number>();
|
||||
if (selectedMmsi != null) out.add(selectedMmsi);
|
||||
externalHighlightedSetRef.forEach((value) => {
|
||||
out.add(value);
|
||||
});
|
||||
return out;
|
||||
}, [selectedMmsi, externalHighlightedSetRef]);
|
||||
const isBaseHighlightedMmsi = useCallback(
|
||||
(mmsi: number) => baseHighlightedMmsiSet.has(mmsi),
|
||||
[baseHighlightedMmsiSet],
|
||||
);
|
||||
|
||||
const isHighlightedPair = useCallback(
|
||||
(aMmsi: number, bMmsi: number) =>
|
||||
@ -1274,12 +1294,6 @@ export function Map3D({
|
||||
setHoveredDeckFleetOwnerKey((prev) => (prev === ownerKey ? prev : ownerKey));
|
||||
}, []);
|
||||
|
||||
const onHoverFleetRef = useRef(onHoverFleet);
|
||||
const onClearFleetHoverRef = useRef(onClearFleetHover);
|
||||
const onHoverMmsiRef = useRef(onHoverMmsi);
|
||||
const onClearMmsiHoverRef = useRef(onClearMmsiHover);
|
||||
const onHoverPairRef = useRef(onHoverPair);
|
||||
const onClearPairHoverRef = useRef(onClearPairHover);
|
||||
const mapDeckMmsiHoverRef = useRef<number[]>([]);
|
||||
const mapDeckPairHoverRef = useRef<number[]>([]);
|
||||
const mapFleetHoverStateRef = useRef<{
|
||||
@ -1287,43 +1301,20 @@ export function Map3D({
|
||||
vesselMmsis: number[];
|
||||
}>({ ownerKey: null, vesselMmsis: [] });
|
||||
|
||||
useEffect(() => {
|
||||
onHoverFleetRef.current = onHoverFleet;
|
||||
onClearFleetHoverRef.current = onClearFleetHover;
|
||||
onHoverMmsiRef.current = onHoverMmsi;
|
||||
onClearMmsiHoverRef.current = onClearMmsiHover;
|
||||
onHoverPairRef.current = onHoverPair;
|
||||
onClearPairHoverRef.current = onClearPairHover;
|
||||
}, [onHoverFleet, onClearFleetHover, onHoverMmsi, onClearMmsiHover, onHoverPair, onClearPairHover]);
|
||||
|
||||
const clearMapFleetHoverState = useCallback(() => {
|
||||
const nextOwner = null;
|
||||
const prev = mapFleetHoverStateRef.current;
|
||||
const shouldNotify = prev.ownerKey !== null || prev.vesselMmsis.length !== 0;
|
||||
mapFleetHoverStateRef.current = { ownerKey: nextOwner, vesselMmsis: [] };
|
||||
mapFleetHoverStateRef.current = { ownerKey: null, vesselMmsis: [] };
|
||||
setHoveredDeckFleetOwner(null);
|
||||
setHoveredDeckFleetMmsis([]);
|
||||
if (shouldNotify) {
|
||||
onClearFleetHoverRef.current?.();
|
||||
}
|
||||
}, [setHoveredDeckFleetOwner, setHoveredDeckFleetMmsis]);
|
||||
|
||||
const clearDeckHoverPairs = useCallback(() => {
|
||||
const prev = mapDeckPairHoverRef.current;
|
||||
mapDeckPairHoverRef.current = [];
|
||||
setHoveredDeckPairMmsiSet((prevState) => (prevState.length === 0 ? prevState : []));
|
||||
if (prev.length > 0) {
|
||||
onClearPairHoverRef.current?.();
|
||||
}
|
||||
}, [setHoveredDeckPairMmsiSet]);
|
||||
|
||||
const clearDeckHoverMmsi = useCallback(() => {
|
||||
const prev = mapDeckMmsiHoverRef.current;
|
||||
mapDeckMmsiHoverRef.current = [];
|
||||
setHoveredDeckMmsiSet((prevState) => (prevState.length === 0 ? prevState : []));
|
||||
if (prev.length > 0) {
|
||||
onClearMmsiHoverRef.current?.();
|
||||
}
|
||||
}, [setHoveredDeckMmsiSet]);
|
||||
|
||||
const scheduleDeckHoverResolve = useCallback(() => {
|
||||
@ -1352,10 +1343,7 @@ export function Map3D({
|
||||
const normalized = makeUniqueSorted(next);
|
||||
touchDeckHoverState(normalized.length > 0);
|
||||
setHoveredDeckMmsiSet((prev) => (equalNumberArrays(prev, normalized) ? prev : normalized));
|
||||
if (!equalNumberArrays(mapDeckMmsiHoverRef.current, normalized)) {
|
||||
mapDeckMmsiHoverRef.current = normalized;
|
||||
onHoverMmsiRef.current?.(normalized);
|
||||
}
|
||||
},
|
||||
[setHoveredDeckMmsiSet, touchDeckHoverState],
|
||||
);
|
||||
@ -1365,10 +1353,7 @@ export function Map3D({
|
||||
const normalized = makeUniqueSorted(next);
|
||||
touchDeckHoverState(normalized.length > 0);
|
||||
setHoveredDeckPairMmsiSet((prev) => (equalNumberArrays(prev, normalized) ? prev : normalized));
|
||||
if (!equalNumberArrays(mapDeckPairHoverRef.current, normalized)) {
|
||||
mapDeckPairHoverRef.current = normalized;
|
||||
onHoverPairRef.current?.(normalized);
|
||||
}
|
||||
},
|
||||
[setHoveredDeckPairMmsiSet, touchDeckHoverState],
|
||||
);
|
||||
@ -1384,7 +1369,6 @@ export function Map3D({
|
||||
setHoveredDeckFleetOwner(ownerKey);
|
||||
setHoveredDeckFleetMmsis(normalized);
|
||||
mapFleetHoverStateRef.current = { ownerKey, vesselMmsis: normalized };
|
||||
onHoverFleetRef.current?.(ownerKey, normalized);
|
||||
},
|
||||
[setHoveredDeckFleetOwner, setHoveredDeckFleetMmsis, touchDeckHoverState],
|
||||
);
|
||||
@ -2360,7 +2344,7 @@ export function Map3D({
|
||||
const hull = clampNumber((isFiniteNumber(t.length) ? t.length : 0) + (isFiniteNumber(t.width) ? t.width : 0) * 3, 50, 420);
|
||||
const sizeScale = clampNumber(0.85 + (hull - 50) / 420, 0.82, 1.85);
|
||||
const selected = t.mmsi === selectedMmsi;
|
||||
const highlighted = isHighlightedMmsi(t.mmsi);
|
||||
const highlighted = isBaseHighlightedMmsi(t.mmsi);
|
||||
const selectedScale = selected ? 1.08 : 1;
|
||||
const highlightScale = highlighted ? 1.06 : 1;
|
||||
const iconScale = selected ? selectedScale : highlightScale;
|
||||
@ -2639,10 +2623,7 @@ export function Map3D({
|
||||
shipData,
|
||||
legacyHits,
|
||||
selectedMmsi,
|
||||
hoveredMmsiSetRef,
|
||||
hoveredFleetMmsiSetRef,
|
||||
hoveredPairMmsiSetRef,
|
||||
isHighlightedMmsi,
|
||||
isBaseHighlightedMmsi,
|
||||
mapSyncEpoch,
|
||||
reorderGlobeFeatureLayers,
|
||||
]);
|
||||
@ -3368,10 +3349,8 @@ export function Map3D({
|
||||
|
||||
const shipLayerData = useMemo(() => {
|
||||
if (shipData.length === 0) return shipData;
|
||||
const layer = [...shipData];
|
||||
layer.sort((a, b) => a.mmsi - b.mmsi);
|
||||
return layer;
|
||||
}, [shipData, isHighlightedMmsi, selectedMmsi]);
|
||||
return [...shipData];
|
||||
}, [shipData]);
|
||||
|
||||
const shipHighlightSet = useMemo(() => {
|
||||
const out = new Set(highlightedMmsiSetCombined);
|
||||
@ -3380,9 +3359,9 @@ export function Map3D({
|
||||
}, [highlightedMmsiSetCombined, selectedMmsi]);
|
||||
|
||||
const shipOverlayLayerData = useMemo(() => {
|
||||
if (shipHighlightSet.size === 0) return [];
|
||||
return shipLayerData.filter((target) => shipHighlightSet.has(target.mmsi));
|
||||
}, [shipLayerData, shipHighlightSet]);
|
||||
if (shipLayerData.length === 0) return shipLayerData;
|
||||
return shipLayerData;
|
||||
}, [shipLayerData]);
|
||||
|
||||
const clearGlobeTooltip = useCallback(() => {
|
||||
if (!mapTooltipRef.current) return;
|
||||
@ -4029,7 +4008,7 @@ export function Map3D({
|
||||
d,
|
||||
null,
|
||||
legacyHits?.get(d.mmsi)?.shipCode ?? null,
|
||||
new Set(),
|
||||
EMPTY_MMSI_SET,
|
||||
),
|
||||
onHover: (info) => {
|
||||
if (!info.object) {
|
||||
@ -4337,14 +4316,20 @@ export function Map3D({
|
||||
heading: d.heading,
|
||||
}),
|
||||
sizeUnits: "pixels",
|
||||
getSize: (d) => (selectedMmsi && d.mmsi === selectedMmsi ? FLAT_SHIP_ICON_SIZE_SELECTED : FLAT_SHIP_ICON_SIZE_HIGHLIGHTED),
|
||||
getColor: (d) =>
|
||||
getShipColor(
|
||||
getSize: (d) => {
|
||||
if (selectedMmsi != null && d.mmsi === selectedMmsi) return FLAT_SHIP_ICON_SIZE_SELECTED;
|
||||
if (shipHighlightSet.has(d.mmsi)) return FLAT_SHIP_ICON_SIZE_HIGHLIGHTED;
|
||||
return 0;
|
||||
},
|
||||
getColor: (d) => {
|
||||
if (!shipHighlightSet.has(d.mmsi) && !(selectedMmsi != null && d.mmsi === selectedMmsi)) return [0, 0, 0, 0];
|
||||
return getShipColor(
|
||||
d,
|
||||
selectedMmsi,
|
||||
legacyHits?.get(d.mmsi)?.shipCode ?? null,
|
||||
shipHighlightSet,
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -4603,11 +4588,15 @@ export function Map3D({
|
||||
sizeUnits: "pixels",
|
||||
getSize: (d) => {
|
||||
if (selectedMmsi && d.mmsi === selectedMmsi) return FLAT_SHIP_ICON_SIZE_SELECTED;
|
||||
if (isHighlightedMmsi(d.mmsi)) return FLAT_SHIP_ICON_SIZE_HIGHLIGHTED;
|
||||
return FLAT_SHIP_ICON_SIZE;
|
||||
},
|
||||
getColor: (d) =>
|
||||
getShipColor(d, selectedMmsi, legacyHits?.get(d.mmsi)?.shipCode ?? null, highlightedMmsiSetCombined),
|
||||
getShipColor(
|
||||
d,
|
||||
selectedMmsi,
|
||||
legacyHits?.get(d.mmsi)?.shipCode ?? null,
|
||||
EMPTY_MMSI_SET,
|
||||
),
|
||||
onHover: (info) => {
|
||||
if (!info.object) {
|
||||
clearDeckHoverPairs();
|
||||
@ -4626,6 +4615,43 @@ export function Map3D({
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.showShips) {
|
||||
globeLayers.push(
|
||||
new IconLayer<AisTarget>({
|
||||
id: "ships-globe-hover",
|
||||
data: shipLayerData,
|
||||
pickable: false,
|
||||
billboard: false,
|
||||
parameters: overlayParams,
|
||||
iconAtlas: "/assets/ship.svg",
|
||||
iconMapping: SHIP_ICON_MAPPING,
|
||||
getIcon: () => "ship",
|
||||
getPosition: (d) => [d.lon, d.lat] as [number, number],
|
||||
getAngle: (d) =>
|
||||
getDisplayHeading({
|
||||
cog: d.cog,
|
||||
heading: d.heading,
|
||||
}),
|
||||
sizeUnits: "pixels",
|
||||
getSize: (d) => {
|
||||
if (selectedMmsi != null && d.mmsi === selectedMmsi) return FLAT_SHIP_ICON_SIZE_SELECTED;
|
||||
if (shipHighlightSet.has(d.mmsi)) return FLAT_SHIP_ICON_SIZE_HIGHLIGHTED;
|
||||
return 0;
|
||||
},
|
||||
getColor: (d) => {
|
||||
if (!shipHighlightSet.has(d.mmsi) && !(selectedMmsi != null && d.mmsi === selectedMmsi)) return [0, 0, 0, 0];
|
||||
return getShipColor(
|
||||
d,
|
||||
selectedMmsi,
|
||||
legacyHits?.get(d.mmsi)?.shipCode ?? null,
|
||||
shipHighlightSet,
|
||||
);
|
||||
},
|
||||
alphaCutoff: 0.05,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedLayers = sanitizeDeckLayerList(globeLayers);
|
||||
const globeDeckProps = {
|
||||
layers: normalizedLayers,
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user