fix(globe): gate bathymetry fill by zoom to avoid ocean tearing
This commit is contained in:
부모
3497b8c7e2
커밋
15d5d5ad23
@ -997,14 +997,20 @@ function injectOceanBathymetryLayers(style: StyleSpecification, maptilerKey: str
|
|||||||
} as unknown as LayerSpecification;
|
} as unknown as LayerSpecification;
|
||||||
|
|
||||||
// Insert before the first symbol layer (keep labels on top), otherwise append.
|
// Insert before the first symbol layer (keep labels on top), otherwise append.
|
||||||
const rawLayers = Array.isArray(style.layers) ? style.layers : [];
|
const layers = Array.isArray(style.layers) ? (style.layers as unknown as LayerSpecification[]) : [];
|
||||||
const layers = rawLayers.filter((layer): layer is LayerSpecification => {
|
if (!Array.isArray(style.layers)) {
|
||||||
if (!layer || typeof layer !== "object") return false;
|
style.layers = layers as unknown as StyleSpecification["layers"];
|
||||||
return typeof (layer as { id?: unknown }).id === "string";
|
}
|
||||||
});
|
|
||||||
const symbolIndex = layers.findIndex((l) => l.type === "symbol");
|
const symbolIndex = layers.findIndex((l) => (l as { type?: unknown } | null)?.type === "symbol");
|
||||||
const insertAt = symbolIndex >= 0 ? symbolIndex : layers.length;
|
const insertAt = symbolIndex >= 0 ? symbolIndex : layers.length;
|
||||||
|
|
||||||
|
const existingIds = new Set<string>();
|
||||||
|
for (const layer of layers) {
|
||||||
|
const id = getLayerId(layer);
|
||||||
|
if (id) existingIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
const toInsert = [
|
const toInsert = [
|
||||||
bathyFill,
|
bathyFill,
|
||||||
bathyBandBorders,
|
bathyBandBorders,
|
||||||
@ -1013,12 +1019,44 @@ function injectOceanBathymetryLayers(style: StyleSpecification, maptilerKey: str
|
|||||||
bathyLinesMajor,
|
bathyLinesMajor,
|
||||||
bathyLabels,
|
bathyLabels,
|
||||||
landformLabels,
|
landformLabels,
|
||||||
].filter(
|
].filter((l) => !existingIds.has(l.id));
|
||||||
(l) => !layers.some((x) => x.id === l.id),
|
|
||||||
);
|
|
||||||
if (toInsert.length > 0) layers.splice(insertAt, 0, ...toInsert);
|
if (toInsert.length > 0) layers.splice(insertAt, 0, ...toInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BathyZoomRange = {
|
||||||
|
id: string;
|
||||||
|
mercator: [number, number];
|
||||||
|
globe: [number, number];
|
||||||
|
};
|
||||||
|
|
||||||
|
const BATHY_ZOOM_RANGES: BathyZoomRange[] = [
|
||||||
|
{ id: "bathymetry-fill", mercator: [6, 12], globe: [8, 12] },
|
||||||
|
{ id: "bathymetry-borders", mercator: [6, 14], globe: [8, 14] },
|
||||||
|
{ id: "bathymetry-borders-major", mercator: [4, 14], globe: [8, 14] },
|
||||||
|
];
|
||||||
|
|
||||||
|
function applyBathymetryZoomProfile(map: maplibregl.Map, baseMap: BaseMapId, projection: MapProjectionId) {
|
||||||
|
if (!map || !map.isStyleLoaded()) return;
|
||||||
|
if (baseMap !== "enhanced") return;
|
||||||
|
const isGlobe = projection === "globe";
|
||||||
|
|
||||||
|
for (const range of BATHY_ZOOM_RANGES) {
|
||||||
|
if (!map.getLayer(range.id)) continue;
|
||||||
|
const [minzoom, maxzoom] = isGlobe ? range.globe : range.mercator;
|
||||||
|
try {
|
||||||
|
// Safety: ensure heavy layers aren't stuck hidden from a previous session.
|
||||||
|
map.setLayoutProperty(range.id, "visibility", "visible");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
map.setLayerZoomRange(range.id, minzoom, maxzoom);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function resolveInitialMapStyle(signal: AbortSignal): Promise<string | StyleSpecification> {
|
async function resolveInitialMapStyle(signal: AbortSignal): Promise<string | StyleSpecification> {
|
||||||
const key = getMapTilerKey();
|
const key = getMapTilerKey();
|
||||||
if (!key) return "/map/styles/osm-seamark.json";
|
if (!key) return "/map/styles/osm-seamark.json";
|
||||||
@ -1223,6 +1261,7 @@ export function Map3D({
|
|||||||
const projectionBusyTokenRef = useRef(0);
|
const projectionBusyTokenRef = useRef(0);
|
||||||
const projectionBusyTimerRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
|
const projectionBusyTimerRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
|
||||||
const projectionPrevRef = useRef<MapProjectionId>(projection);
|
const projectionPrevRef = useRef<MapProjectionId>(projection);
|
||||||
|
const bathyZoomProfileKeyRef = useRef<string>("");
|
||||||
const mapTooltipRef = useRef<maplibregl.Popup | null>(null);
|
const mapTooltipRef = useRef<maplibregl.Popup | null>(null);
|
||||||
const deckHoverRafRef = useRef<number | null>(null);
|
const deckHoverRafRef = useRef<number | null>(null);
|
||||||
const deckHoverHasHitRef = useRef(false);
|
const deckHoverHasHitRef = useRef(false);
|
||||||
@ -2081,33 +2120,25 @@ export function Map3D({
|
|||||||
}, [baseMap]);
|
}, [baseMap]);
|
||||||
|
|
||||||
// Globe rendering + bathymetry tuning.
|
// Globe rendering + bathymetry tuning.
|
||||||
// Some terrain/hillshade/extrusion effects look unstable under globe and can occlude Deck overlays.
|
// Under globe projection, low-zoom bathymetry polygons can exceed MapLibre's per-segment 16-bit vertex
|
||||||
|
// limit (65535) due to projection subdivision. Keep globe stable by gating heavy bathymetry fills/borders
|
||||||
|
// to higher zoom levels rather than toggling them on every frame.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const map = mapRef.current;
|
const map = mapRef.current;
|
||||||
if (!map) return;
|
if (!map) return;
|
||||||
|
|
||||||
const apply = () => {
|
const apply = () => {
|
||||||
if (!map.isStyleLoaded()) return;
|
if (!map.isStyleLoaded()) return;
|
||||||
const disableBathyHeavy = projection === "globe" && baseMap === "enhanced";
|
|
||||||
const visHeavy = disableBathyHeavy ? "none" : "visible";
|
|
||||||
const seaVisibility = "visible" as const;
|
const seaVisibility = "visible" as const;
|
||||||
const seaRegex = /(water|sea|ocean|river|lake|coast|bay)/i;
|
const seaRegex = /(water|sea|ocean|river|lake|coast|bay)/i;
|
||||||
|
|
||||||
// Globe + our injected bathymetry fill polygons can exceed MapLibre's per-segment vertex limit
|
// Apply zoom gating for heavy bathymetry layers once per (baseMap, projection) combination.
|
||||||
// (65535), causing broken ocean rendering. Keep globe mode stable by disabling the heavy fill.
|
// This avoids repeatedly mutating layer zoom ranges on hover/mapSyncEpoch pulses.
|
||||||
const heavyIds = [
|
const nextProfileKey = `bathyZoomV1|${baseMap}|${projection}`;
|
||||||
"bathymetry-fill",
|
if (bathyZoomProfileKeyRef.current !== nextProfileKey) {
|
||||||
"bathymetry-borders",
|
applyBathymetryZoomProfile(map, baseMap, projection);
|
||||||
"bathymetry-borders-major",
|
bathyZoomProfileKeyRef.current = nextProfileKey;
|
||||||
"bathymetry-extrusion",
|
kickRepaint(map);
|
||||||
"bathymetry-hillshade",
|
|
||||||
];
|
|
||||||
for (const id of heavyIds) {
|
|
||||||
try {
|
|
||||||
if (map.getLayer(id)) map.setLayoutProperty(id, "visibility", visHeavy);
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vector basemap water layers can be tuned per-style. Keep visible by default,
|
// Vector basemap water layers can be tuned per-style. Keep visible by default,
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user