完善爆管分析面板;整合地图查询函数

This commit is contained in:
JIANG
2025-10-23 11:59:45 +08:00
parent 720f8a5dc2
commit ad893ac19d
7 changed files with 1185 additions and 899 deletions

View File

@@ -0,0 +1,471 @@
/**
* OpenLayers 地图工具函数集合
* 提供地图要素查询、选择和处理功能
*
* @module mapQueryService
*/
import { GeoJSON } from "ol/format";
import { Feature } from "ol";
import { Point, LineString, Polygon } from "ol/geom";
import Geometry from "ol/geom/Geometry";
import TileState from "ol/TileState";
import { toLonLat } from "ol/proj";
import { booleanIntersects, buffer, point, toWgs84 } from "@turf/turf";
import config from "@config/config";
import RenderFeature from "ol/render/Feature";
import Map from "ol/Map";
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import VectorTileSource from "ol/source/VectorTile";
// ========== 类型定义 ==========
/**
* 几何类型枚举
*/
enum GeometryType {
POINT = "Point",
LINE_STRING = "LineString",
POLYGON = "Polygon",
}
/**
* 地图点击事件
*/
interface MapClickEvent {
coordinate: number[];
}
// ========== 常量配置 ==========
/**
* GeoServer 服务配置
*/
const GEOSERVER_CONFIG = {
url: config.mapUrl,
workspace: "TJWater",
layers: ["geo_pipes_mat", "geo_junctions_mat"],
wfsVersion: "1.0.0",
outputFormat: "application/json",
} as const;
/**
* 地图交互配置
*/
const MAP_CONFIG = {
hitTolerance: 5, // 像素容差
bufferUnits: "meters" as const,
} as const;
// ========== 辅助函数 ==========
/**
* 将扁平坐标数组转换为点坐标
* @param flatCoordinates 扁平坐标数组
* @returns 点坐标 [x, y]
*/
const flatCoordinatesToPoint = (flatCoordinates: number[]): number[] => {
return [flatCoordinates[0], flatCoordinates[1]];
};
/**
* 将扁平坐标数组转换为线坐标
* @param flatCoordinates 扁平坐标数组
* @returns 线坐标数组 [[x1, y1], [x2, y2], ...]
*/
const flatCoordinatesToLineString = (flatCoordinates: number[]): number[][] => {
const lineCoords: number[][] = [];
for (let i = 0; i < flatCoordinates.length; i += 2) {
lineCoords.push([flatCoordinates[i], flatCoordinates[i + 1]]);
}
return lineCoords;
};
/**
* 将扁平坐标数组转换为多边形坐标
* @param flatCoordinates 扁平坐标数组
* @param ends 环的结束位置数组
* @returns 多边形坐标数组 [[[x1, y1], [x2, y2], ...]]
*/
const flatCoordinatesToPolygon = (
flatCoordinates: number[],
ends: number[]
): number[][][] => {
const rings: number[][][] = [];
let start = 0;
for (const end of ends) {
const ring: number[][] = [];
for (let i = start; i < end; i += 2) {
ring.push([flatCoordinates[i], flatCoordinates[i + 1]]);
}
rings.push(ring);
start = end;
}
return rings;
};
/**
* 将 RenderFeature 转换为标准 Feature
* @param renderFeature OpenLayers 渲染要素
* @returns 标准 Feature 对象或 undefined
*/
const convertRenderFeatureToFeature = (
renderFeature: RenderFeature
): Feature | undefined => {
if (!renderFeature) {
return undefined;
}
const geometry = renderFeature.getGeometry();
if (!geometry) {
return undefined;
}
try {
let clonedGeometry: Geometry;
// 检查是否为标准几何体
if (geometry instanceof Geometry) {
clonedGeometry = geometry;
} else {
// 处理 RenderFeature 的几何体
const type = geometry.getType();
const flatCoordinates = geometry.getFlatCoordinates();
switch (type) {
case GeometryType.POINT:
clonedGeometry = new Point(flatCoordinatesToPoint(flatCoordinates));
break;
case GeometryType.LINE_STRING:
clonedGeometry = new LineString(
flatCoordinatesToLineString(flatCoordinates)
);
break;
case GeometryType.POLYGON:
const ends = (geometry as any).getEnds?.() || [
flatCoordinates.length,
];
clonedGeometry = new Polygon(
flatCoordinatesToPolygon(flatCoordinates, ends)
);
break;
default:
console.warn(`不支持的几何体类型: ${type}`);
return undefined;
}
}
return new Feature({
geometry: clonedGeometry,
...renderFeature.getProperties(),
});
} catch (error) {
console.error("RenderFeature 转换为 Feature 时出错:", error);
return undefined;
}
};
/**
* 构建 WFS 查询 URL
* @param layer 图层名称
* @param orFilter CQL 过滤条件
* @returns WFS 查询 URL
*/
const buildWfsUrl = (layer: string, orFilter: string): string => {
const { url, workspace, wfsVersion, outputFormat } = GEOSERVER_CONFIG;
const params = new URLSearchParams({
service: "WFS",
version: wfsVersion,
request: "GetFeature",
typeName: `${workspace}:${layer}`,
outputFormat: outputFormat,
CQL_FILTER: orFilter,
});
return `${url}/${workspace}/ows?${params.toString()}`;
};
/**
* 从指定图层查询要素
* @param layer 图层名称
* @param orFilter CQL 过滤条件
* @returns GeoJSON Feature 数组
*/
const fetchFeaturesFromLayer = async (
layer: string,
orFilter: string
): Promise<Feature[]> => {
try {
const url = buildWfsUrl(layer, orFilter);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP 错误: ${response.status} ${response.statusText}`);
}
const json = await response.json();
return new GeoJSON().readFeatures(json);
} catch (error) {
console.error(`图层 ${layer} 查询失败:`, error);
return [];
}
};
/**
* 根据 ID 列表通过 GeoServer WFS 服务查询要素
* @param ids 要素 ID 数组
* @param layer 可选的指定图层名称
* @returns Feature 数组
*/
const queryFeaturesByIds = async (
ids: string[],
layer?: string
): Promise<Feature[]> => {
if (!ids.length) {
return [];
}
const orFilter = ids.map((id) => `id=${id}`).join(" OR ");
try {
if (!layer) {
// 查询所有配置的图层
const promises = GEOSERVER_CONFIG.layers.map((layerName) =>
fetchFeaturesFromLayer(layerName, orFilter)
);
const results = await Promise.all(promises);
return results.flat();
} else {
// 查询指定图层
return await fetchFeaturesFromLayer(layer, orFilter);
}
} catch (error) {
console.error("根据 IDs 查询要素时出错:", error);
return [];
}
};
/**
* 获取地图上所有 VectorTileSource
* @param map OpenLayers 地图对象
* @returns VectorTileSource 数组
*/
const getVectorTileSources = (map: Map): VectorTileSource[] => {
return map
.getAllLayers()
.filter((layer) => layer instanceof WebGLVectorTileLayer)
.map((layer) => layer.getSource() as VectorTileSource)
.filter((source) => source !== null);
};
/**
* 确保缩放级别在有效范围内
* @param z 原始缩放级别
* @param minZoom 最小缩放级别
* @param maxZoom 最大缩放级别
* @returns 调整后的缩放级别
*/
const clampZoomLevel = (
z: number,
minZoom: number,
maxZoom: number
): number => {
return Math.max(minZoom, Math.min(maxZoom, z));
};
/**
* 按几何类型对要素进行分类
* @param features 要素数组
* @returns 分类后的要素对象
*/
const classifyFeaturesByGeometry = (
features: Feature[]
): {
points: Feature[];
lines: Feature[];
others: Feature[];
} => {
const points: Feature[] = [];
const lines: Feature[] = [];
const others: Feature[] = [];
features.forEach((feature) => {
const geometryType = feature.getGeometry()?.getType();
switch (geometryType) {
case GeometryType.POINT:
points.push(feature);
break;
case GeometryType.LINE_STRING:
lines.push(feature);
break;
default:
others.push(feature);
}
});
return { points, lines, others };
};
/**
* 检查要素是否与缓冲区相交
* @param feature 要素
* @param buffered 缓冲区几何对象
* @returns 是否相交
*/
const isFeatureIntersectsBuffer = (
feature: Feature,
buffered: any
): boolean => {
if (!feature || !buffered) {
return false;
}
try {
const geoJSONGeometry = new GeoJSON().writeGeometryObject(
feature.getGeometry()!
);
const bufferedGeometry = buffered.geometry;
return booleanIntersects(toWgs84(geoJSONGeometry), bufferedGeometry);
} catch (error) {
console.error("要素相交检测失败:", error);
return false;
}
};
/**
* 从 VectorTile 中提取选中的要素
* @param vectorTiles 矢量瓦片数组
* @param buffered 缓冲区几何对象
* @returns 选中的要素数组
*/
const extractSelectedFeatures = (
vectorTiles: any[],
buffered: any
): Feature[] => {
const allFeatures: Feature[] = [];
vectorTiles.forEach((vectorTile) => {
if (vectorTile.getState() !== TileState.LOADED) {
return;
}
const renderFeatures = vectorTile.getFeatures();
const selectedFeatures = renderFeatures
.map((renderFeature: RenderFeature) =>
convertRenderFeatureToFeature(renderFeature)
)
.filter(
(feature: Feature | undefined): feature is Feature =>
feature !== undefined && isFeatureIntersectsBuffer(feature, buffered)
);
allFeatures.push(...selectedFeatures);
});
return allFeatures;
};
/**
* 处理地图点击选择要素
* @param event 地图点击事件
* @param map OpenLayers 地图对象
* @returns 选中的第一个要素的 Promise如果没有选中则返回 null
*/
const handleMapClickSelectFeatures = async (
event: MapClickEvent,
map: Map
): Promise<Feature | null> => {
if (!map) {
return null;
}
const coord = event.coordinate;
const view = map.getView();
const projection = view.getProjection();
const pixelRatio = window.devicePixelRatio;
// 获取缩放级别并确保为整数
let z = Math.floor(view.getZoom() || 0) - 1;
// 获取所有 VectorTileSource
const vectorTileSources = getVectorTileSources(map);
if (!vectorTileSources.length) {
return null;
}
// 存储所有选中的要素
const allSelectedFeatures: Feature[] = [];
// 遍历所有 VectorTileSource
for (const vectorTileSource of vectorTileSources) {
const tileGrid = vectorTileSource.getTileGrid();
if (!tileGrid) {
continue;
}
// 调整缩放级别到有效范围
const minZoom = tileGrid.getMinZoom();
const maxZoom = tileGrid.getMaxZoom();
z = clampZoomLevel(z, minZoom, maxZoom);
// 获取瓦片坐标
const tileCoord = tileGrid.getTileCoordForCoordAndZ(coord, z);
const resolution = tileGrid.getResolution(tileCoord[0]);
// 创建点击点的缓冲区
const hitPoint = point(toLonLat(coord));
const buffered = buffer(hitPoint, resolution * MAP_CONFIG.hitTolerance, {
units: MAP_CONFIG.bufferUnits,
});
// 获取矢量瓦片
const vectorRenderTile = vectorTileSource.getTile(
tileCoord[0],
tileCoord[1],
tileCoord[2],
pixelRatio,
projection
);
const vectorTiles = vectorTileSource.getSourceTiles(
pixelRatio,
projection,
vectorRenderTile
);
// 提取选中的要素
const selectedFeatures = extractSelectedFeatures(vectorTiles, buffered);
allSelectedFeatures.push(...selectedFeatures);
}
// 按几何类型优先级排序:点 > 线 > 其他
const { points, lines, others } =
classifyFeaturesByGeometry(allSelectedFeatures);
const prioritizedFeatures = [...points, ...lines, ...others];
// 获取第一个要素的 ID 并查询完整信息
const firstFeature = prioritizedFeatures[0];
if (!firstFeature) {
return null;
}
const queryId = firstFeature.getProperties().id;
if (!queryId) {
return null;
}
try {
const features = await queryFeaturesByIds([queryId]);
return features[0] || null;
} catch (error) {
console.error("查询要素详情失败:", error);
return null;
}
};
// ========== 导出 ==========
export { handleMapClickSelectFeatures, queryFeaturesByIds };
export type { MapClickEvent };