Files
TJWaterFrontend_Refine/src/utils/layers.ts
2025-11-24 15:09:37 +08:00

179 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Layer } from "ol/layer";
import { Deck } from "@deck.gl/core";
import { toLonLat } from "ol/proj";
/**
* 自定义 Layer 类来包装 deck.gl
* 将 deck.gl 图层集成到 OpenLayers 地图中
*/
export class DeckLayer extends Layer {
private deck: Deck;
private onVisibilityChange?: (layerId: string, visible: boolean) => void;
private userVisibility: Map<string, boolean> = new Map(); // 存储用户设置的可见性
/**
* @param deckInstance deck.gl 实例
* @param layerProperties 可选:在构造时直接设置到 OpenLayers Layer 的 properties
*/
constructor(deckInstance: Deck, layerProperties?: Record<string, any>) {
// 将 layerProperties 作为 Layer 的 properties 传入
super({ properties: layerProperties || {} });
this.deck = deckInstance;
// 再次确保属性应用到实例(兼容场景)
if (layerProperties) {
this.setProperties(layerProperties);
}
}
// 设置可见性变化回调
setVisibilityChangeCallback(
callback: (layerId: string, visible: boolean) => void
): void {
this.onVisibilityChange = callback;
}
// 初始化用户可见性状态
initUserVisibility(layerId: string, visible: boolean): void {
this.userVisibility.set(layerId, visible);
}
render(frameState: any): HTMLElement {
const { size, viewState } = frameState;
const [width, height] = size;
const [longitude, latitude] = toLonLat(viewState.center);
const zoom = viewState.zoom - 1; // 调整 zoom 以匹配
const bearing = (-viewState.rotation * 180) / Math.PI;
const deckViewState = { bearing, longitude, latitude, zoom };
this.deck.setProps({ width, height, viewState: deckViewState });
this.deck.redraw();
return document.getElementById("deck-canvas") as HTMLElement;
}
// 获取 Deck 实例
getDeck(): Deck {
return this.deck;
}
// 设置图层
setDeckLayers(layers: any[]): void {
this.deck.setProps({ layers });
}
// 获取当前图层
getDeckLayers(): any[] {
return this.deck.props.layers || [];
}
// 添加图层
addDeckLayer(layer: any): void {
const currentLayers = this.getDeckLayers();
// 如果已有同 id 图层,则替换保持顺序;否则追加
const idx = currentLayers.findIndex((l: any) => l && l.id === layer.id);
if (idx >= 0) {
const copy = [...currentLayers];
copy[idx] = layer;
this.deck.setProps({ layers: copy });
return;
}
this.deck.setProps({ layers: [...currentLayers, layer] });
}
// 移除图层
removeDeckLayer(layerId: string): void {
const currentLayers = this.getDeckLayers();
const filteredLayers = currentLayers.filter(
(layer: any) => layer && layer.id !== layerId
);
this.deck.setProps({ layers: filteredLayers });
}
// 根据 ID 查找图层
getDeckLayerById(layerId: string): any | undefined {
const layers = this.getDeckLayers();
return layers.find((layer: any) => layer && layer.id === layerId);
}
// 更新特定图层:支持传入一个新的 Layer 实例或一个 props 对象
// - 如果传入的是 Layer 实例,则直接替换同 id 的图层为该实例
// - 如果传入的是 props普通对象则基于原图层调用 clone(props)
updateDeckLayer(layerId: string, layerOrProps: any): void {
const layers = this.getDeckLayers();
const updatedLayers = layers.map((layer: any) => {
if (!layer || layer.id !== layerId) return layer;
// 如果传入的是一个 deck.gl Layer 实例(通常包含 id 和 props
if (
layerOrProps &&
typeof layerOrProps === "object" &&
layerOrProps.id !== undefined &&
typeof layerOrProps.clone === "function"
) {
// 替换为新的 layer 实例
return layerOrProps;
}
// 否则假定传入的是 props 对象,使用现有 layer.clone(props) 创建新实例
try {
return layer.clone(layerOrProps);
} catch (err) {
// 如果 clone 失败,作为降级策略尝试手动复制 props 到新对象(保留原 layer
// 这通常不应该发生,但保证不会抛出而破坏整个 layers 列表
const newLayer = layer.clone
? layer.clone(layerOrProps)
: {
...layer,
props: { ...(layer.props || {}), ...(layerOrProps || {}) },
};
return newLayer;
}
});
this.deck.setProps({ layers: updatedLayers });
}
// 获取图层的可见性(用户设置的状态,不受其他条件影响)
getDeckLayerVisible(layerId: string): boolean | undefined {
// 优先返回用户设置的可见性
if (this.userVisibility.has(layerId)) {
return this.userVisibility.get(layerId);
}
// 如果没有用户设置,返回实际图层的可见性
const layer = this.getDeckLayerById(layerId);
return layer ? layer.props.visible : undefined;
}
// 设置图层的可见性
setDeckLayerVisible(layerId: string, visible: boolean): void {
// 存储用户设置的可见性
this.userVisibility.set(layerId, visible);
// 更新图层(注意:实际的 visible 可能还受其他条件控制)
// 优先尝试使用传入的 layer 实例替换,否则使用 clone({ visible }) 来保留图层类型
const found = this.getDeckLayerById(layerId);
if (!found) return;
try {
// 使用 clone 来确保保持同类型实例
this.updateDeckLayer(layerId, { ...found.props, visible });
} catch (err) {
// 降级:直接替换属性
this.updateDeckLayer(layerId, { visible });
}
// 触发回调通知外部
if (this.onVisibilityChange) {
this.onVisibilityChange(layerId, visible);
}
}
// 切换图层的可见性
toggleDeckLayerVisible(layerId: string): void {
const currentVisible = this.getDeckLayerVisible(layerId);
if (currentVisible !== undefined) {
this.setDeckLayerVisible(layerId, !currentVisible);
}
}
// 可选的封装:在外部用更语义化的方式设置属性(内部可直接调用 setProperties
setLayerProperties(props: Record<string, any>): void {
this.setProperties(props);
}
}