/* * @Author : yuan * @Date : 2025-07-03 14:58:03 * @LastEditors : yuan * @LastEditTime : 2025-08-11 16:20:46 * @FilePath : \src\utils\mapUtils.js * @Description : * Copyright 2025 OBKoro1, All Rights Reserved. * 2025-07-03 14:58:03 */ import * as Cesium from 'cesium' import * as turf from '@turf/turf' /** * 计算dataSource的包围盒 * @param {*} entities dataSource加载完成后的entities * @returns */ export function computeEntitiesBoundingSphere (entities) { let positions = [] entities.forEach(function (entity) { if (entity.position) { positions.push(entity.position.getValue(Cesium.JulianDate.now())) } // 如果是多边形或线,需要提取所有顶点 if (entity.polygon) { let hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()) positions = positions.concat(hierarchy.positions) } }) return Cesium.BoundingSphere.fromPoints(positions) } export function getGeoJsonBoundingSphere (geojson) { // 提取所有坐标点 let positions = [] geojson.features.forEach(function (feature) { if (feature.geometry.type === 'Point') { positions.push(Cesium.Cartesian3.fromDegrees( feature.geometry.coordinates[0], feature.geometry.coordinates[1] )) } else if (feature.geometry.type === 'Polygon') { feature.geometry.coordinates[0].forEach(function (coord) { positions.push(Cesium.Cartesian3.fromDegrees(coord[0], coord[1])) }) } else if (feature.geometry.type === 'MultiPolygon') { feature.geometry.coordinates[0][0].forEach(function (coord) { positions.push(Cesium.Cartesian3.fromDegrees(coord[0], coord[1])) }) } // 可以添加对其他几何类型的处理 }) // 计算边界球 return Cesium.BoundingSphere.fromPoints(positions) } /** * 三维白膜的shader设置 */ export const customShader = new Cesium.CustomShader({ lightingModel: Cesium.LightingModel.UNLIT, fragmentShaderText: ` void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) { vec4 position = czm_inverseModelView * vec4(fsInput.attributes.positionEC,1); // 位置 // 注意shader中写浮点数是,一定要带小数点,否则会报错,比如0需要写成0.0,1要写成1.0 float _baseHeight = 0.0; // 物体的基础高度,需要修改成一个合适的建筑基础高度 float _heightRange = 60.0; // 高亮的范围(_baseHeight ~ _baseHeight + _heightRange) float _glowRange = 80.0; // 光环的移动范围(高度) // 建筑基础色 float mars_height = position.z - _baseHeight; // ========== 地面以下透明处理 ========== // if(position.z < _baseHeight) { // // 地面以下部分完全透明 // material.alpha = 0.0; // return; // 提前返回,不执行后续着色计算 // } float mars_a11 = fract(czm_frameNumber / 60.0) * 3.14159265 * 2.0; float mars_a12 = mars_height / _heightRange; // + sin(mars_a11) * 0.1; // 基础颜色 material.diffuse = vec3(0.3176, 0.6706, 1.0); // 颜色 material.diffuse *= vec3(mars_a12);// 渐变 // 动态光环 // float maxFrame = 500.0; // 设置最大生效帧数(例如 1000 帧后停止) // if (czm_frameNumber <= maxFrame) { // // 动态光环逻辑 // float time = fract(czm_frameNumber / 360.0); // time = abs(time - 0.5) * 2.0; // float mars_h = clamp(mars_height / _glowRange, 0.0, 1.0); // float mars_diff = step(0.005, abs(mars_h - time)); // material.diffuse += material.diffuse * (1.0 - mars_diff); // } } ` }) /** * 用于在Cesium场景中创建和管理贴地圆形及圆形边框的Primitive */ export class GroundCirclePrimitiveManager { constructor() { this._viewer = null this._primitiveCollection = null this._entityCircleDataSource = null } init (viewer) { if (this._primitiveCollection && this._entityCircleDataSource && this._viewer) { this.removeAll() return } this._viewer = viewer this._entityCircleDataSource = new Cesium.CustomDataSource('entityCircleDataSource') this._viewer.dataSources.add(this._entityCircleDataSource) this._primitiveCollection = new Cesium.PrimitiveCollection() this._viewer.scene.primitives.add(this._primitiveCollection) } /** * 创建贴地实心圆形及圆环 * @param {object} options - 配置选项对象 * @param {object} options.data - 位置信息 * @param {number} [options.eventCount=0] - 事件数量,默认为 0 * @param {boolean} [options.useEventMaterial=false] - 是否使用事件的材质颜色,默认为 false * @param {number} [options.addAll=1] - 是否覆盖圆与光环都加载,1都加,2光加载覆盖圆,3光加载覆盖圆环 * @param {*} [options.materialColor] - 覆盖圆的填充材质 * @param {*} [options.frameColor] - 覆盖圆边界的填充材质 */ addCircleOrOutline (options) { let { data, eventCount = 0, useEventMaterial = false, addAll = 1, materialColor = Cesium.Color.fromBytes(100, 149, 237, 40), frameColor = Cesium.Color.fromBytes(100, 149, 237, 255), } = options if (useEventMaterial) { if (eventCount > 100) { materialColor = Cesium.Color.fromBytes(255, 37, 9, 40) frameColor = Cesium.Color.fromBytes(255, 37, 9, 255) } else if (eventCount > 50) { materialColor = Cesium.Color.fromBytes(255, 171, 54, 40) frameColor = Cesium.Color.fromBytes(255, 171, 54, 255) } else { materialColor = Cesium.Color.fromBytes(27, 215, 89, 40) frameColor = Cesium.Color.fromBytes(27, 215, 89, 255) } } if (addAll === 1) { this.addCircle({ data, materialColor }) this.addCircleOutline({ data, frameColor }) } else if (addAll === 2) { this.addCircle({ data, materialColor }) } else if (addAll === 3) { this.addCircleOutline({ data, frameColor }) } } /** * 创建贴地实心圆形---覆盖圆形 * @param {object} options - 配置选项对象 * @param {object} options.data - 位置信息 * @param {*} [options.materialColor] - 覆盖圆的填充材质 */ addCircle (options) { const { data, materialColor = Cesium.Color.fromBytes(100, 149, 237, 40), radius = 5000 } = options const center = Cesium.Cartesian3.fromDegrees(+data.lng, +data.lat) const entity = { position: center, // 北京坐标(示例) ellipse: { semiMajorAxis: radius, // 半长轴(米) semiMinorAxis: radius, // 半短轴(米) HeightReference: Cesium.HeightReference.CLAMP_TO_GROUND, classificationType: Cesium.ClassificationType.TERRAIN, material: new Cesium.ColorMaterialProperty(materialColor), } } this._entityCircleDataSource.entities.add(entity) } /** * 创建贴地实心圆形---覆盖圆形 * @param {object} options - 配置选项对象 * @param {object} options.data - 位置信息 * @param {*} [options.materialColor] - 覆盖圆的填充材质 */ addCirclePrimitive (options) { const { data, materialColor = Cesium.Color.fromBytes(100, 149, 237, 40), radius = 5000 } = options const center = Cesium.Cartesian3.fromDegrees(+data.lng, +data.lat) const circleGeometry = new Cesium.CircleGeometry({ center: center, radius: radius, vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT, height: 0, // 贴地 }) const primitive = new Cesium.GroundPrimitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: circleGeometry, }), appearance: new Cesium.EllipsoidSurfaceAppearance({ material: new Cesium.Material({ fabric: { type: 'Color', uniforms: { color: materialColor, }, }, }), }), classificationType: Cesium.ClassificationType.TERRAIN }) // this._primitiveCollection.add(primitive) } /** * 创建贴地实心圆形---覆盖圆环 * @param {object} options - 配置选项对象 * @param {object} options.data - 位置信息 * @param {*} [options.frameColor] - 覆盖圆边界的填充材质 */ addCircleOutline (options) { const { data, frameColor = Cesium.Color.fromBytes(100, 149, 237, 255), radius = 5000 } = options const turfCircle = turf.circle([data.lng, data.lat], radius, { steps: 360, units: 'meters' }) // 1. 获取Turf生成的圆形坐标点 const coordinates = turfCircle.geometry.coordinates[0] // 2. 转换为Cesium Cartesian3数组 const positions = coordinates.map(coord => Cesium.Cartesian3.fromDegrees(coord[0], coord[1])) // 3. 创建贴地线 const polyline = new Cesium.GroundPolylineGeometry({ positions: positions, width: 2, // 线宽 vertexFormat: Cesium.VertexFormat.POSITION_AND_ST, }) const primitive = new Cesium.GroundPolylinePrimitive({ geometryInstances: new Cesium.GeometryInstance({ geometry: polyline, }), appearance: new Cesium.PolylineMaterialAppearance({ material: new Cesium.Material({ fabric: { type: 'Color', // 使用纯色材质 uniforms: { color: frameColor, // 设置颜色 }, }, }), }), }) this._primitiveCollection.add(primitive) } // 清空当前primitive组 removeAll () { if (this._primitiveCollection) { this._primitiveCollection.removeAll() } if (this._entityCircleDataSource) { this._entityCircleDataSource?.entities.removeAll() } } // 销毁处理 destroy () { if (this._primitiveCollection) { this._primitiveCollection.removeAll() this._viewer.scene.primitives.remove(this._primitiveCollection) this._primitiveCollection = null } if (this._entityCircleDataSource) { this._entityCircleDataSource?.entities.removeAll() this._viewer.dataSources.remove(this._entityCircleDataSource, true) this._entityCircleDataSource = null } this._viewer = null } } /** * 计算多点距离 * @param {*} data 笛卡尔坐标系数组 * @returns {object} {length: 线长度, xRepeatCount: x重复数量} */ export function getCurPolylineLength (data) { let polylineLength = 0 data.forEach((item, index) => { if (index == data.length - 1) return let point1 = item let point2 = data[index + 1] polylineLength += Cesium.Cartesian3.distance(point1, point2) }) return { length: polylineLength, xRepeatCount: Math.ceil(polylineLength / 1000), } }