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;
|
||||
|
||||
// Insert before the first symbol layer (keep labels on top), otherwise append.
|
||||
const rawLayers = Array.isArray(style.layers) ? style.layers : [];
|
||||
const layers = rawLayers.filter((layer): layer is LayerSpecification => {
|
||||
if (!layer || typeof layer !== "object") return false;
|
||||
return typeof (layer as { id?: unknown }).id === "string";
|
||||
});
|
||||
const symbolIndex = layers.findIndex((l) => l.type === "symbol");
|
||||
const layers = Array.isArray(style.layers) ? (style.layers as unknown as LayerSpecification[]) : [];
|
||||
if (!Array.isArray(style.layers)) {
|
||||
style.layers = layers as unknown as StyleSpecification["layers"];
|
||||
}
|
||||
|
||||
const symbolIndex = layers.findIndex((l) => (l as { type?: unknown } | null)?.type === "symbol");
|
||||
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 = [
|
||||
bathyFill,
|
||||
bathyBandBorders,
|
||||
@ -1013,12 +1019,44 @@ function injectOceanBathymetryLayers(style: StyleSpecification, maptilerKey: str
|
||||
bathyLinesMajor,
|
||||
bathyLabels,
|
||||
landformLabels,
|
||||
].filter(
|
||||
(l) => !layers.some((x) => x.id === l.id),
|
||||
);
|
||||
].filter((l) => !existingIds.has(l.id));
|
||||
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> {
|
||||
const key = getMapTilerKey();
|
||||
if (!key) return "/map/styles/osm-seamark.json";
|
||||
@ -1223,6 +1261,7 @@ export function Map3D({
|
||||
const projectionBusyTokenRef = useRef(0);
|
||||
const projectionBusyTimerRef = useRef<ReturnType<typeof window.setTimeout> | null>(null);
|
||||
const projectionPrevRef = useRef<MapProjectionId>(projection);
|
||||
const bathyZoomProfileKeyRef = useRef<string>("");
|
||||
const mapTooltipRef = useRef<maplibregl.Popup | null>(null);
|
||||
const deckHoverRafRef = useRef<number | null>(null);
|
||||
const deckHoverHasHitRef = useRef(false);
|
||||
@ -2081,33 +2120,25 @@ export function Map3D({
|
||||
}, [baseMap]);
|
||||
|
||||
// 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(() => {
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
|
||||
const apply = () => {
|
||||
if (!map.isStyleLoaded()) return;
|
||||
const disableBathyHeavy = projection === "globe" && baseMap === "enhanced";
|
||||
const visHeavy = disableBathyHeavy ? "none" : "visible";
|
||||
const seaVisibility = "visible" as const;
|
||||
const seaRegex = /(water|sea|ocean|river|lake|coast|bay)/i;
|
||||
|
||||
// Globe + our injected bathymetry fill polygons can exceed MapLibre's per-segment vertex limit
|
||||
// (65535), causing broken ocean rendering. Keep globe mode stable by disabling the heavy fill.
|
||||
const heavyIds = [
|
||||
"bathymetry-fill",
|
||||
"bathymetry-borders",
|
||||
"bathymetry-borders-major",
|
||||
"bathymetry-extrusion",
|
||||
"bathymetry-hillshade",
|
||||
];
|
||||
for (const id of heavyIds) {
|
||||
try {
|
||||
if (map.getLayer(id)) map.setLayoutProperty(id, "visibility", visHeavy);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
// Apply zoom gating for heavy bathymetry layers once per (baseMap, projection) combination.
|
||||
// This avoids repeatedly mutating layer zoom ranges on hover/mapSyncEpoch pulses.
|
||||
const nextProfileKey = `bathyZoomV1|${baseMap}|${projection}`;
|
||||
if (bathyZoomProfileKeyRef.current !== nextProfileKey) {
|
||||
applyBathymetryZoomProfile(map, baseMap, projection);
|
||||
bathyZoomProfileKeyRef.current = nextProfileKey;
|
||||
kickRepaint(map);
|
||||
}
|
||||
|
||||
// Vector basemap water layers can be tuned per-style. Keep visible by default,
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user