修复地图瓦片长时间挂机后加载失败:Observer 去重 + tileerror 重试
Constraint: MutationObserver subtree=true 导致任意 class 变化触发全量瓦片重载,长期运行触发 CDN 限流 Tested: tsc --noEmit OK, ruff OK, pytest 184 passed
This commit is contained in:
@@ -39,6 +39,8 @@ const MAP_TILE_URLS = {
|
|||||||
dark: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
|
dark: "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png",
|
||||||
light: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
light: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
||||||
} as const;
|
} as const;
|
||||||
|
const TILE_RETRY_LIMIT = 2;
|
||||||
|
const TILE_RETRY_DELAY_BASE_MS = 500;
|
||||||
const CITY_MARKER_DISPLAY_OFFSETS: Record<
|
const CITY_MARKER_DISPLAY_OFFSETS: Record<
|
||||||
string,
|
string,
|
||||||
{ x: number; y: number; zIndexOffset?: number }
|
{ x: number; y: number; zIndexOffset?: number }
|
||||||
@@ -528,6 +530,21 @@ export function useLeafletMap({
|
|||||||
subdomains: "abcd",
|
subdomains: "abcd",
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Retry failed tiles (CartoCDN may throttle long-running sessions)
|
||||||
|
const tileRetries = new Map<string, number>();
|
||||||
|
tileLayer.on("tileerror", (ev: { tile: HTMLElement; coords: L.Coords }) => {
|
||||||
|
const { tile, coords } = ev;
|
||||||
|
const key = `${coords.z}/${coords.x}/${coords.y}`;
|
||||||
|
const attempts = tileRetries.get(key) ?? 0;
|
||||||
|
if (attempts >= TILE_RETRY_LIMIT) return;
|
||||||
|
tileRetries.set(key, attempts + 1);
|
||||||
|
const delay = TILE_RETRY_DELAY_BASE_MS * 2 ** attempts;
|
||||||
|
setTimeout(() => {
|
||||||
|
(tile as HTMLImageElement).src = tileLayer.getTileUrl(coords);
|
||||||
|
}, delay);
|
||||||
|
});
|
||||||
|
map.on("moveend zoomend", () => tileRetries.clear());
|
||||||
|
|
||||||
const nearbyLayer = L.layerGroup().addTo(map);
|
const nearbyLayer = L.layerGroup().addTo(map);
|
||||||
mapRef.current = map;
|
mapRef.current = map;
|
||||||
tileLayerRef.current = tileLayer;
|
tileLayerRef.current = tileLayer;
|
||||||
@@ -601,8 +618,13 @@ export function useLeafletMap({
|
|||||||
const tileLayer = tileLayerRef.current;
|
const tileLayer = tileLayerRef.current;
|
||||||
if (!tileLayer || typeof MutationObserver === "undefined") return;
|
if (!tileLayer || typeof MutationObserver === "undefined") return;
|
||||||
|
|
||||||
|
let lastTileUrl = "";
|
||||||
|
|
||||||
const syncMapTheme = () => {
|
const syncMapTheme = () => {
|
||||||
tileLayer.setUrl(getMapTileUrl(containerRef.current));
|
const nextUrl = getMapTileUrl(containerRef.current);
|
||||||
|
if (nextUrl === lastTileUrl) return;
|
||||||
|
lastTileUrl = nextUrl;
|
||||||
|
tileLayer.setUrl(nextUrl);
|
||||||
};
|
};
|
||||||
syncMapTheme();
|
syncMapTheme();
|
||||||
|
|
||||||
@@ -611,11 +633,12 @@ export function useLeafletMap({
|
|||||||
attributeFilter: ["class"],
|
attributeFilter: ["class"],
|
||||||
attributes: true,
|
attributes: true,
|
||||||
});
|
});
|
||||||
if (document.body) {
|
|
||||||
observer.observe(document.body, {
|
const scanTerminal = document.querySelector(".scan-terminal");
|
||||||
|
if (scanTerminal) {
|
||||||
|
observer.observe(scanTerminal, {
|
||||||
attributeFilter: ["class"],
|
attributeFilter: ["class"],
|
||||||
attributes: true,
|
attributes: true,
|
||||||
subtree: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user