fix: stabilize globe projection loading and globe ship icon fallback
This commit is contained in:
부모
05b0c6b881
커밋
5b7d1c4331
@ -606,6 +606,44 @@ function makeGlobeCircleRadiusExpr() {
|
||||
|
||||
const GLOBE_SHIP_CIRCLE_RADIUS_EXPR = makeGlobeCircleRadiusExpr() as never;
|
||||
|
||||
function buildFallbackGlobeShipIcon() {
|
||||
const size = 96;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return null;
|
||||
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
ctx.fillStyle = "rgba(255,255,255,1)";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size / 2, 6);
|
||||
ctx.lineTo(size / 2 - 14, 24);
|
||||
ctx.lineTo(size / 2 - 18, 58);
|
||||
ctx.lineTo(size / 2 - 10, 88);
|
||||
ctx.lineTo(size / 2 + 10, 88);
|
||||
ctx.lineTo(size / 2 + 18, 58);
|
||||
ctx.lineTo(size / 2 + 14, 24);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillRect(size / 2 - 8, 34, 16, 18);
|
||||
|
||||
return ctx.getImageData(0, 0, size, size);
|
||||
}
|
||||
|
||||
function ensureFallbackShipImage(map: maplibregl.Map, imageId: string) {
|
||||
if (!map || map.hasImage(imageId)) return;
|
||||
const image = buildFallbackGlobeShipIcon();
|
||||
if (!image) return;
|
||||
|
||||
try {
|
||||
map.addImage(imageId, image, { pixelRatio: 2, sdf: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function getMapTilerKey(): string | null {
|
||||
const k = import.meta.env.VITE_MAPTILER_KEY;
|
||||
if (typeof k !== "string") return null;
|
||||
@ -1051,6 +1089,7 @@ export function Map3D({
|
||||
const projectionRef = useRef<MapProjectionId>(projection);
|
||||
const globeShipIconLoadingRef = useRef(false);
|
||||
const projectionBusyRef = useRef(false);
|
||||
const projectionBusyTokenRef = useRef(0);
|
||||
const projectionBusyTimerRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
|
||||
const projectionPrevRef = useRef<MapProjectionId>(projection);
|
||||
const mapTooltipRef = useRef<maplibregl.Popup | null>(null);
|
||||
@ -1344,28 +1383,37 @@ export function Map3D({
|
||||
projectionBusyTimerRef.current = null;
|
||||
}, []);
|
||||
|
||||
const endProjectionLoading = useCallback(() => {
|
||||
if (!projectionBusyRef.current) return;
|
||||
projectionBusyRef.current = false;
|
||||
clearProjectionBusyTimer();
|
||||
if (onProjectionLoadingChange) {
|
||||
onProjectionLoadingChange(false);
|
||||
}
|
||||
}, [clearProjectionBusyTimer, onProjectionLoadingChange]);
|
||||
|
||||
const setProjectionLoading = useCallback(
|
||||
(loading: boolean) => {
|
||||
if (projectionBusyRef.current === loading) return;
|
||||
projectionBusyRef.current = loading;
|
||||
|
||||
if (loading) {
|
||||
clearProjectionBusyTimer();
|
||||
projectionBusyTimerRef.current = setTimeout(() => {
|
||||
if (projectionBusyRef.current) {
|
||||
setProjectionLoading(false);
|
||||
console.warn("Projection loading fallback timeout reached.");
|
||||
}
|
||||
}, 3000);
|
||||
} else {
|
||||
clearProjectionBusyTimer();
|
||||
if (!loading) {
|
||||
endProjectionLoading();
|
||||
return;
|
||||
}
|
||||
|
||||
clearProjectionBusyTimer();
|
||||
projectionBusyRef.current = true;
|
||||
const token = ++projectionBusyTokenRef.current;
|
||||
if (onProjectionLoadingChange) {
|
||||
onProjectionLoadingChange(loading);
|
||||
onProjectionLoadingChange(true);
|
||||
}
|
||||
|
||||
projectionBusyTimerRef.current = setTimeout(() => {
|
||||
if (!projectionBusyRef.current || projectionBusyTokenRef.current !== token) return;
|
||||
console.debug("Projection loading fallback timeout reached.");
|
||||
endProjectionLoading();
|
||||
}, 4000);
|
||||
},
|
||||
[onProjectionLoadingChange, clearProjectionBusyTimer],
|
||||
[clearProjectionBusyTimer, endProjectionLoading, onProjectionLoadingChange],
|
||||
);
|
||||
|
||||
const pulseMapSync = () => {
|
||||
@ -1379,11 +1427,9 @@ export function Map3D({
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearProjectionBusyTimer();
|
||||
if (projectionBusyRef.current) {
|
||||
setProjectionLoading(false);
|
||||
}
|
||||
endProjectionLoading();
|
||||
};
|
||||
}, [clearProjectionBusyTimer, setProjectionLoading]);
|
||||
}, [clearProjectionBusyTimer, endProjectionLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
showSeamarkRef.current = settings.showSeamark;
|
||||
@ -2167,45 +2213,29 @@ export function Map3D({
|
||||
};
|
||||
|
||||
const ensureImage = () => {
|
||||
ensureFallbackShipImage(map, imgId);
|
||||
if (globeShipIconLoadingRef.current) return;
|
||||
if (map.hasImage(imgId)) return;
|
||||
|
||||
const addFallbackImage = () => {
|
||||
const size = 96;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
// Simple top-down ship silhouette, pointing north.
|
||||
ctx.clearRect(0, 0, size, size);
|
||||
ctx.fillStyle = "rgba(255,255,255,1)";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(size / 2, 6);
|
||||
ctx.lineTo(size / 2 - 14, 24);
|
||||
ctx.lineTo(size / 2 - 18, 58);
|
||||
ctx.lineTo(size / 2 - 10, 88);
|
||||
ctx.lineTo(size / 2 + 10, 88);
|
||||
ctx.lineTo(size / 2 + 18, 58);
|
||||
ctx.lineTo(size / 2 + 14, 24);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillRect(size / 2 - 8, 34, 16, 18);
|
||||
|
||||
const img = ctx.getImageData(0, 0, size, size);
|
||||
map.addImage(imgId, img, { pixelRatio: 2, sdf: true });
|
||||
ensureFallbackShipImage(map, imgId);
|
||||
kickRepaint(map);
|
||||
};
|
||||
|
||||
if (map.hasImage(imgId)) return;
|
||||
if (globeShipIconLoadingRef.current) return;
|
||||
|
||||
let fallbackTimer: ReturnType<typeof window.setTimeout> | null = null;
|
||||
try {
|
||||
globeShipIconLoadingRef.current = true;
|
||||
fallbackTimer = window.setTimeout(() => {
|
||||
addFallbackImage();
|
||||
}, 80);
|
||||
void map
|
||||
.loadImage("/assets/ship.svg")
|
||||
.then((response) => {
|
||||
globeShipIconLoadingRef.current = false;
|
||||
if (map.hasImage(imgId)) return;
|
||||
if (fallbackTimer != null) {
|
||||
clearTimeout(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
}
|
||||
|
||||
const loadedImage = (response as { data?: HTMLImageElement | ImageBitmap } | undefined)?.data;
|
||||
if (!loadedImage) {
|
||||
@ -2214,6 +2244,13 @@ export function Map3D({
|
||||
}
|
||||
|
||||
try {
|
||||
if (map.hasImage(imgId)) {
|
||||
try {
|
||||
map.removeImage(imgId);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
map.addImage(imgId, loadedImage, { pixelRatio: 2, sdf: true });
|
||||
kickRepaint(map);
|
||||
} catch (e) {
|
||||
@ -2222,10 +2259,18 @@ export function Map3D({
|
||||
})
|
||||
.catch(() => {
|
||||
globeShipIconLoadingRef.current = false;
|
||||
if (fallbackTimer != null) {
|
||||
clearTimeout(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
}
|
||||
addFallbackImage();
|
||||
});
|
||||
} catch (e) {
|
||||
globeShipIconLoadingRef.current = false;
|
||||
if (fallbackTimer != null) {
|
||||
clearTimeout(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
}
|
||||
try {
|
||||
addFallbackImage();
|
||||
} catch (fallbackError) {
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user