WholeProcessPlatform/frontend/src/components/gis/ol/point-layer-manager.ts
2026-07-14 16:09:01 +08:00

392 lines
12 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 Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import OlMap from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import { fromLonLat } from 'ol/proj';
import { getIconPath } from '@/utils/index';
type PointLayerStyleFactory = (feature: Feature) => any;
type PointLayerManagerOptions = {
map: OlMap | null;
createStyle: PointLayerStyleFactory;
};
export class PointLayerManager {
private map: OlMap | null;
private createStyle: PointLayerStyleFactory;
private layerRegistry: Map<string, VectorLayer<VectorSource>> = new Map();
private layerFeatureIndexes: Map<
string,
Map<string, Map<string, Feature[]>>
> = new Map();
constructor(options: PointLayerManagerOptions) {
this.map = options.map;
this.createStyle = options.createStyle;
}
// 备注:同步更新地图实例,供地图初始化后把真实 map 注入点图层管理器。
setMap(map: OlMap | null) {
this.map = map;
}
// 备注:获取当前屏幕可视区域内的所有点要素(仅包含图层可见且图例可见的要素)。
getFeaturesInViewport(): Feature[] {
if (!this.map) return [];
const extent = this.map.getView().calculateExtent(this.map.getSize());
if (!extent) return [];
const [minX, minY, maxX, maxY] = extent;
const result: Feature[] = [];
this.forEachFeature((feature, layer) => {
// 跳过图层本身不可见的要素
if (!layer.getVisible()) return;
const geom = feature.getGeometry();
if (!geom || geom.getType() !== 'Point') return;
const coords = (geom as Point).getCoordinates();
const [x, y] = coords;
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
result.push(feature);
}
});
return result;
}
// 备注:统一返回当前点图层注册表,供外层做遍历或清理。
getRegistry() {
return this.layerRegistry;
}
// 备注:按图层 key 获取已注册的点图层实例。
getLayer(layerKey: string) {
return this.layerRegistry.get(layerKey);
}
// 备注:返回当前所有点图层实例,供命中检测和批量刷新使用。
getLayers(): VectorLayer<VectorSource>[] {
return Array.from(this.layerRegistry.values());
}
// 备注:按当前缩放级别统一刷新近邻点展开坐标,保证抽吸后的点击和 popup 与视觉位置一致。
updateNearbyFeatureLayout(currentZoom?: number): void {
if (!this.map) return;
if (currentZoom === undefined || !Number.isFinite(currentZoom)) {
return;
}
this.forEachFeature(feature => {
this.resetFeatureToBaseCoordinate(feature);
});
}
// 备注:判断指定图层 key 的点图层是否已存在。
hasLayer(layerKey: string): boolean {
return this.layerRegistry.has(layerKey);
}
// 备注:按需获取或创建点图层实例,并自动完成注册与挂载。
ensureLayer(layerKey: string, layerType?: string) {
if (!this.map || !layerKey) return null;
let vectorLayer = this.layerRegistry.get(layerKey);
if (vectorLayer) {
return vectorLayer;
}
const vectorSource = new VectorSource();
vectorLayer = new VectorLayer({
source: vectorSource,
zIndex: 100,
declutter: true,
style: feature => this.createStyle(feature as Feature)
});
(vectorLayer as any).type = layerType || layerKey;
this.layerRegistry.set(layerKey, vectorLayer);
this.map.addLayer(vectorLayer);
return vectorLayer;
}
// 备注:把后端点位数组转换为 OpenLayers Feature并写入指定点图层。
addDataLayer(pointData: any, layerType: string): void {
if (!this.map) return;
let dataArray: any[] = [];
let targetLayerKey = layerType;
if (Array.isArray(pointData)) {
dataArray = pointData;
} else {
dataArray = pointData?.data || [];
targetLayerKey = pointData?.key || layerType;
}
if (!targetLayerKey) {
console.warn('缺少图层 Key无法加载描点');
return;
}
const vectorLayer = this.ensureLayer(targetLayerKey, layerType);
if (!vectorLayer) return;
const vectorSource = vectorLayer.getSource();
if (!vectorSource) {
return;
}
vectorSource.clear();
this.resetLayerFeatureIndexes(targetLayerKey);
if (dataArray.length === 0) {
return;
}
const features: Feature[] = [];
dataArray.forEach((item: any) => {
const feature = this.createFeature(item, targetLayerKey);
if (feature) {
this.indexFeature(targetLayerKey, feature);
features.push(feature);
}
});
if (features.length > 0) {
vectorSource.addFeatures(features);
}
}
// 备注:统一控制整个点图层显隐,供图层树勾选联动使用。
setLayerVisible(layerKey: string, visible?: boolean): void {
const vectorLayer = this.layerRegistry.get(layerKey);
if (vectorLayer && vectorLayer.getVisible() !== visible) {
vectorLayer.setVisible(visible);
vectorLayer.changed();
}
}
// 备注:按图层 key 删除点图层并清空其 source 数据。
removeLayer(layerKey: string): void {
if (!this.map || !layerKey) {
console.warn('removePointLayer: 无效的图层 key 或地图未初始化');
return;
}
const vectorLayer = this.layerRegistry.get(layerKey);
if (!vectorLayer) {
console.warn(`未找到标识为 [${layerKey}] 的描点图层实例`);
return;
}
const source = vectorLayer.getSource();
if (source) {
source.clear();
}
this.map.removeLayer(vectorLayer);
this.layerRegistry.delete(layerKey);
this.layerFeatureIndexes.delete(layerKey);
}
// 备注:按图层遍历所有点图层实例,供 hover 刷新和区域裁切复用。
forEachLayer(
callback: (layer: VectorLayer<VectorSource>, layerKey: string) => void
): void {
this.layerRegistry.forEach((layer, layerKey) => {
callback(layer, layerKey);
});
}
// 备注:遍历所有点要素,供区域裁切或批量状态更新使用。
forEachFeature(
callback: (
feature: Feature,
layer: VectorLayer<VectorSource>,
layerKey: string
) => void
): void {
this.forEachLayer((layer, layerKey) => {
const source = layer.getSource();
if (!source) return;
source.getFeatures().forEach(feature => {
callback(feature as Feature, layer, layerKey);
});
});
}
// 备注:按图例字段值统一控制要素显隐,供图例和容量筛选联动复用。
setLegendVisibleByField(
layerKey: string,
matchValue: string,
checked: boolean,
fieldKey = 'anchoPointState'
): void {
const vectorLayer = this.layerRegistry.get(layerKey);
if (!vectorLayer) return;
const source = vectorLayer.getSource();
if (!source) return;
if (!matchValue) {
source.getFeatures().forEach(feature => {
if (feature.get('_legendVisible') !== false) {
feature.set('_legendVisible', false);
feature.changed();
}
});
vectorLayer.changed();
return;
}
const targetFeatures = this.getIndexedFeatures(
layerKey,
fieldKey,
matchValue
);
if (targetFeatures.length === 0) return;
targetFeatures.forEach(feature => {
if (feature.get('_legendVisible') !== checked) {
feature.set('_legendVisible', checked);
feature.changed();
}
});
vectorLayer.changed();
}
// 备注:统一销毁所有点图层并清空注册表,供地图销毁阶段复用。
destroy(): void {
this.forEachLayer(layer => {
const source = layer.getSource();
if (source) {
source.clear();
}
if (this.map && this.map.getLayers().getArray().includes(layer)) {
this.map.removeLayer(layer);
}
if ((layer as any).setMap) {
(layer as any).setMap(null);
}
});
this.layerRegistry.clear();
this.layerFeatureIndexes.clear();
}
// 备注:统一把业务点位数据转换为 Feature并补齐样式依赖字段。
private createFeature(item: any, targetLayerKey: string): Feature | null {
const { lgtd, lttd, stnm, iconCode, titleName, ennm } = item;
if (lgtd == null || lttd == null) {
return null;
}
const lon = Number(lgtd);
const lat = Number(lttd);
if (isNaN(lon) || isNaN(lat) || !isFinite(lon) || !isFinite(lat)) {
return null;
}
if (Math.abs(lon) > 180 || Math.abs(lat) > 90) {
return null;
}
const coord = fromLonLat([lon, lat]);
if (!isFinite(coord[0]) || !isFinite(coord[1])) {
return null;
}
let iconUrl = iconCode ? getIconPath(iconCode) : '';
if (!iconUrl) {
iconUrl = getIconPath('default') || '';
}
const feature = new Feature({
geometry: new Point(coord),
...item,
_layerKey: targetLayerKey
});
const rawFeatureId =
item.stcd || item._id || `${lon.toFixed(6)},${lat.toFixed(6)}`;
feature.setId(`${targetLayerKey}:${rawFeatureId}`);
feature.set('_iconUrl', iconUrl);
feature.set(
'_labelText',
item.sttpMap === 'ylfb' ? item.ftp || '' : titleName || stnm || ennm || ''
);
feature.set('_baseCoordinates', coord);
feature.set('_legendVisible', true);
feature.set('_regionVisible', true);
feature.set('_sttpMap', item.sttpMap);
if (item.popupHtml) {
feature.set('popupHtml', item.popupHtml);
}
return feature;
}
// 备注:重置单个图层的字段索引,供重新灌入数据或删除图层时复用。
private resetLayerFeatureIndexes(layerKey: string): void {
this.layerFeatureIndexes.set(layerKey, new Map());
}
// 备注:按字段值给要素建立索引,供图例切换时快速命中目标锚点集合。
private indexFeature(layerKey: string, feature: Feature): void {
const fieldIndexMap =
this.layerFeatureIndexes.get(layerKey) ||
new Map<string, Map<string, Feature[]>>();
const legendFieldKey = 'anchoPointState';
const legendFieldValue = feature.get(legendFieldKey);
if (legendFieldValue != null) {
const valueMap =
fieldIndexMap.get(legendFieldKey) || new Map<string, Feature[]>();
const normalizedValue = String(legendFieldValue);
const targetFeatures = valueMap.get(normalizedValue) || [];
targetFeatures.push(feature);
valueMap.set(normalizedValue, targetFeatures);
fieldIndexMap.set(legendFieldKey, valueMap);
}
this.layerFeatureIndexes.set(layerKey, fieldIndexMap);
}
// 备注:根据图层 key、字段名和值快速返回目标要素集合避免图例切换时整层遍历。
private getIndexedFeatures(
layerKey: string,
fieldKey: string,
matchValue: string
): Feature[] {
const fieldIndexMap = this.layerFeatureIndexes.get(layerKey);
if (!fieldIndexMap) return [];
const valueMap = fieldIndexMap.get(fieldKey);
if (!valueMap) return [];
return valueMap.get(String(matchValue)) || [];
}
// 备注:先恢复要素原始坐标,再按缩放阶段决定是否做展开偏移,避免多次缩放后偏移累计。
private resetFeatureToBaseCoordinate(feature: Feature): void {
const baseCoordinates = feature.get('_baseCoordinates') as
| number[]
| undefined;
const geometry = feature.getGeometry();
if (
!baseCoordinates ||
baseCoordinates.length < 2 ||
!geometry ||
geometry.getType() !== 'Point'
) {
return;
}
(geometry as Point).setCoordinates([
baseCoordinates[0],
baseCoordinates[1]
]);
}
}