179 lines
6.1 KiB
TypeScript
179 lines
6.1 KiB
TypeScript
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);
|
||
}
|
||
}
|