更新地图样式;调整时间轴,新增前进/后退一天按钮;新增爆管分析页面

This commit is contained in:
JIANG
2025-10-22 11:50:20 +08:00
parent 69b2e4fb98
commit 720f8a5dc2
12 changed files with 1557 additions and 59 deletions

View File

@@ -0,0 +1,438 @@
/**
* OpenLayers 地图工具函数集合
* 提供地图要素查询、选择和处理功能
*/
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";
// ========== 常量配置 ==========
const GEOSERVER_CONFIG = {
url: config.GEOSERVER_URL,
network: config.GEOSERVER_NETWORK,
layers: ['geo_pipes_mat', 'geo_junctions_mat'],
wfsVersion: '1.0.0',
outputFormat: 'application/json',
};
const MAP_CONFIG = {
hitTolerance: 5, // 像素容差
bufferUnits: 'meters',
};
// ========== 几何类型枚举 ==========
const GEOMETRY_TYPES = {
POINT: 'Point',
LINE_STRING: 'LineString',
POLYGON: 'Polygon',
};
// ========== 工具函数 ==========
/**
* 构建 WFS 查询 URL
* @param {string} layer - 图层名称
* @param {string} cqlFilter - CQL 过滤条件
* @returns {string} 完整的 WFS 查询 URL
*/
const buildWfsUrl = (layer, cqlFilter) => {
const { url, network, wfsVersion, outputFormat } = GEOSERVER_CONFIG;
const params = new URLSearchParams({
service: 'WFS',
version: wfsVersion,
request: 'GetFeature',
typeName: `${network}:${layer}`,
outputFormat,
CQL_FILTER: cqlFilter,
});
return `${url}/${network}/ows?${params.toString()}`;
};
/**
* 查询单个图层的要素
* @param {string} layer - 图层名称
* @param {string} cqlFilter - CQL 过滤条件
* @returns {Promise<Feature[]>} 要素数组
*/
const queryLayerFeatures = async (layer, cqlFilter) => {
try {
const url = buildWfsUrl(layer, cqlFilter);
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求失败: ${response.statusText}`);
}
const json = await response.json();
return new GeoJSON().readFeatures(json);
} catch (error) {
console.error(`图层 ${layer} 查询失败:`, error);
return [];
}
};
/**
* 根据 IDs通过 Geoserver WFS 服务查询要素
* @param {string[]} ids - 要素 ID 数组
* @param {string} [layer] - 可选的特定图层名称,不传则查询所有图层
* @returns {Promise<Feature[]>} 查询到的要素数组
*/
export const queryFeaturesByIds = async (ids, layer = null) => {
if (!ids || !ids.length) {
return [];
}
const cqlFilter = ids.map((id) => `id=${id}`).join(' OR ');
try {
if (layer) {
// 查询指定图层
return await queryLayerFeatures(layer, cqlFilter);
}
// 查询所有图层
const { layers } = GEOSERVER_CONFIG;
const promises = layers.map((layerName) =>
queryLayerFeatures(layerName, cqlFilter)
);
const results = await Promise.all(promises);
const features = results.flat();
return features;
} catch (error) {
console.error('根据 IDs 查询要素时出错:', error);
return [];
}
};
/**
* 将扁平坐标数组转换为坐标对数组
* @param {number[]} flatCoordinates - 扁平坐标数组 [x1, y1, x2, y2, ...]
* @returns {number[][]} 坐标对数组 [[x1, y1], [x2, y2], ...]
*/
const flatCoordinatesToPairs = (flatCoordinates) => {
const pairs = [];
for (let i = 0; i < flatCoordinates.length; i += 2) {
pairs.push([flatCoordinates[i], flatCoordinates[i + 1]]);
}
return pairs;
};
/**
* 创建点几何对象
* @param {number[]} flatCoordinates - 扁平坐标数组
* @returns {Point} 点几何对象
*/
const createPointGeometry = (flatCoordinates) => {
const coordinates = [flatCoordinates[0], flatCoordinates[1]];
return new Point(coordinates);
};
/**
* 创建线几何对象
* @param {number[]} flatCoordinates - 扁平坐标数组
* @returns {LineString} 线几何对象
*/
const createLineStringGeometry = (flatCoordinates) => {
const lineCoords = flatCoordinatesToPairs(flatCoordinates);
return new LineString(lineCoords);
};
/**
* 创建面几何对象
* @param {number[]} flatCoordinates - 扁平坐标数组
* @param {Object} geometry - 原始几何对象
* @returns {Polygon} 面几何对象
*/
const createPolygonGeometry = (flatCoordinates, geometry) => {
// 获取环的结束位置
const ends = geometry.getEnds ? geometry.getEnds() : [flatCoordinates.length];
const rings = [];
let start = 0;
for (const end of ends) {
const ring = [];
for (let i = start; i < end; i += 2) {
ring.push([flatCoordinates[i], flatCoordinates[i + 1]]);
}
rings.push(ring);
start = end;
}
return new Polygon(rings);
};
/**
* 将 RenderFeature 转换为标准 Feature
* @param {Object} renderFeature - 渲染要素对象
* @returns {Feature|null} OpenLayers Feature 对象,转换失败返回 null
*/
const renderFeature2Feature = (renderFeature) => {
if (!renderFeature) {
return null;
}
const geometry = renderFeature.getGeometry();
if (!geometry) {
return null;
}
try {
let clonedGeometry;
if (geometry instanceof Geometry) {
// 标准 Feature 的几何体,直接使用
clonedGeometry = geometry;
} else {
// RenderFeature 的几何体,需要转换
const type = geometry.getType();
const flatCoordinates = geometry.getFlatCoordinates();
switch (type) {
case 'Point':
clonedGeometry = createPointGeometry(flatCoordinates);
break;
case 'LineString':
clonedGeometry = createLineStringGeometry(flatCoordinates);
break;
case 'Polygon':
clonedGeometry = createPolygonGeometry(flatCoordinates, geometry);
break;
default:
console.warn('不支持的几何体类型:', type);
return null;
}
}
// 创建新的 Feature包含几何体和属性
const feature = new Feature({
geometry: clonedGeometry,
...renderFeature.getProperties(),
});
return feature;
} catch (error) {
console.error('RenderFeature 转换 Feature 时出错:', error);
return null;
}
};
/**
* 对要素按几何类型进行分类
* @param {Feature} feature - OpenLayers 要素
* @param {Object} categorized - 分类存储对象
*/
const categorizeFeatureByGeometry = (feature, categorized) => {
const geometryType = feature.getGeometry()?.getType();
if (geometryType === GEOMETRY_TYPES.POINT) {
categorized.points.push(feature);
} else if (geometryType === GEOMETRY_TYPES.LINE_STRING) {
categorized.lines.push(feature);
} else {
categorized.others.push(feature);
}
};
/**
* 检查要素是否在缓冲区内
* @param {Feature} feature - OpenLayers 要素
* @param {Object} bufferedGeometry - 缓冲区几何对象
* @returns {boolean} 是否相交
*/
const isFeatureInBuffer = (feature, bufferedGeometry) => {
if (!feature || !bufferedGeometry) {
return false;
}
try {
const geoJSONGeometry = new GeoJSON().writeGeometryObject(
feature.getGeometry()
);
return booleanIntersects(toWgs84(geoJSONGeometry), bufferedGeometry);
} catch (error) {
console.error('要素缓冲区检查失败:', error);
return false;
}
};
/**
* 处理矢量瓦片,提取符合条件的要素
* @param {Object} vectorTile - 矢量瓦片对象
* @param {Object} buffered - 缓冲区对象
* @param {Object} categorized - 分类存储对象
*/
const processVectorTile = (vectorTile, buffered, categorized) => {
if (vectorTile.getState() !== TileState.LOADED) {
return;
}
const renderFeatures = vectorTile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) {
return;
}
const selectedFeatures = renderFeatures
.map((renderFeature) => renderFeature2Feature(renderFeature))
.filter((feature) => feature !== null) // 过滤转换失败的要素
.filter((feature) => isFeatureInBuffer(feature, buffered?.geometry));
selectedFeatures.forEach((feature) =>
categorizeFeatureByGeometry(feature, categorized)
);
};
/**
* 处理矢量瓦片源,提取所有符合条件的要素
* @param {Object} vectorTileSource - 矢量瓦片源
* @param {number[]} coord - 坐标
* @param {number} z - 缩放级别
* @param {Object} projection - 投影
* @param {Object} categorized - 分类存储对象
*/
const processVectorTileSource = (
vectorTileSource,
coord,
z,
projection,
categorized
) => {
const tileGrid = vectorTileSource.getTileGrid();
if (!tileGrid) {
return;
}
// 确保缩放级别在有效范围内
const minZoom = tileGrid.getMinZoom();
const maxZoom = tileGrid.getMaxZoom();
const validZ = Math.max(minZoom, Math.min(z, maxZoom));
const [x, y] = coord;
const tileCoord = tileGrid.getTileCoordForCoordAndZ([x, y], validZ);
const resolution = tileGrid.getResolution(tileCoord[0]);
// 创建缓冲区用于容差计算
const { hitTolerance, bufferUnits } = MAP_CONFIG;
const hitPoint = point(toLonLat(coord));
const buffered = buffer(hitPoint, resolution * hitTolerance, {
units: bufferUnits,
});
// 获取矢量渲染瓦片
const pixelRatio = window.devicePixelRatio;
const vectorRenderTile = vectorTileSource.getTile(
tileCoord[0],
tileCoord[1],
tileCoord[2],
pixelRatio,
projection
);
// 检查 vectorRenderTile 是否有效
if (!vectorRenderTile) {
return;
}
// 获取源瓦片
const vectorTiles = typeof vectorTileSource.getSourceTiles === 'function' ? vectorTileSource.getSourceTiles(
pixelRatio,
projection,
vectorRenderTile
) : [];
vectorTiles.forEach((vectorTile) =>
processVectorTile(vectorTile, buffered, categorized)
);
};
/**
* 处理地图点击事件,选择要素
* @param {Object} event - 地图点击事件
* @param {Object} map - OpenLayers 地图对象
* @param {Function} setHighlightFeature - 设置高亮要素的回调函数
*/
export const handleMapClickSelectFeatures = (
event,
map,
setHighlightFeature
) => {
if (!map || !event?.coordinate) {
return;
}
const coord = event.coordinate;
const view = map.getView();
const currentZoom = view.getZoom() || 0;
const z = Math.floor(currentZoom) - 1;
const projection = view.getProjection();
// 获取所有矢量瓦片图层源
const vectorTileSources = map
.getAllLayers()
.filter((layer) => layer.getSource && layer.getSource())
.map((layer) => layer.getSource())
.filter((source) => source && source.getTileGrid);
if (!vectorTileSources.length) {
return;
}
// 按几何类型分类存储要素
const categorized = {
points: [],
lines: [],
others: [],
};
// 处理所有矢量瓦片源
vectorTileSources.forEach((vectorTileSource) =>
processVectorTileSource(vectorTileSource, coord, z, projection, categorized)
);
// 按优先级合并要素:点 > 线 > 其他
const selectedFeatures = [
...categorized.points,
...categorized.lines,
...categorized.others,
];
// 处理选中的第一个要素
if (selectedFeatures.length > 0) {
const firstFeature = selectedFeatures[0];
const queryId = firstFeature?.getProperties()?.id;
if (queryId) {
queryFeaturesByIds([queryId])
.then((features) => {
if (features && features.length > 0) {
setHighlightFeature(features[0]);
} else {
setHighlightFeature(null);
}
})
.catch((error) => {
console.error('查询要素详情失败:', error);
setHighlightFeature(null);
});
} else {
setHighlightFeature(null);
}
} else {
setHighlightFeature(null);
}
};
export default {
handleMapClickSelectFeatures,
queryFeaturesByIds,
};