import * as Cesium from 'cesium' import CesiumNavigation from 'cesium-navigation-es6' import 'cesium/Build/Cesium/Widgets/widgets.css' const getCatesian3FromPX = (viewer, px) => { const picks = viewer.scene.drillPick(px) viewer.scene.render() let cartesian let isOn3dtiles = false for (var i = 0; i < picks.length; i++) { if (picks[i] && picks[i].primitive && picks[i].primitive instanceof Cesium.Cesium3DTileset) { //模型上拾取 isOn3dtiles = true break } } if (isOn3dtiles) { cartesian = viewer.scene.pickPosition(px) } else { var ray = viewer.camera.getPickRay(px) if (!ray) return null cartesian = viewer.scene.globe.pick(ray, viewer.scene) } return cartesian } export default class MyCesium { static ImageryProviderUrl = '/map/mapWX/{z}/{x}/{y}.jpg' static RoadProviderUrl = '' static TerrainProviderUrl = '/map/mapTerrain/' static center = [116.39742, 39.90906, 2000000] static ENTITY_TYPES = { WARZONE: 1, SAFEZONE: 2, MODEL: 3, IMAGE: 4, ROUTE: 5, LINE: 6, // 两个模型之间的连线 POINT_PLANNING: 7, // 规划点 GRAPHICS: 8, // 折线 AIR_ROUTE: 9, // 航路 ROAMING_PLANE: 10, } viewer = null // 未完成的在地图上移动类的操作 operations = [] // 已添加的军标 plots = [] // 已添加的路线 routes = [] constructor(dom, options = {}) { const imageryProvider = new Cesium.UrlTemplateImageryProvider({ url: window._CONFIG.ImageryProviderUrl || MyCesium.ImageryProviderUrl, tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: 15, }) const roadProvider = new Cesium.UrlTemplateImageryProvider({ url: window._CONFIG.RoadProviderUrl || MyCesium.RoadProviderUrl, tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: 15, format: 'image/png', }) const terrainProvider = new Cesium.CesiumTerrainProvider({ url: window._CONFIG.TerrainProviderUrl || MyCesium.TerrainProviderUrl, }) const viewer = new Cesium.Viewer(dom, { shouldAnimate: true, // 粒子效果 geocoder: false, // 是否显示位置查找工具(true表示是,false表示否) homeButton: false, // 是否显示首页位置工具 sceneModePicker: false, // 是否显示视角模式切换工具 baseLayerPicker: false, // 是杏显示默认图层选择工具 navigationHelpButton: false, // 是否显示导航帮助工具 animation: false, // 是杏显示动画工具 timeline: false, // 是否显示时间轴工具 fullscreenButton: false, // 是否显示全屏按钮工具 imageryProvider, terrainProvider, infoBox: false, // 是否显示信息框 selectionIndicator: false, // 是否显示选中实体时的绿框 }) this.viewer = viewer const { center = MyCesium.center } = options // viewer.scene.globe.depthTestAgainstTerrain = true viewer.imageryLayers.addImageryProvider(roadProvider) viewer.camera.setView({ destination: Cesium.Cartesian3.fromDegrees(...center), }) // 不显示Cesium的Logo viewer._cesiumWidget._creditContainer.style.display = 'none' viewer.scene.sun.show = true //太阳和月亮 viewer.scene.moon.show = true viewer.scene.skyAtmosphere.show = true //大气层,显示天空颜色 // 设置外天空盒 viewer.scene.skyBox = new Cesium.SkyBox({ sources: { positiveX: '/images/Standard-Cube-Map/px1.png', negativeX: '/images/Standard-Cube-Map/nx1.png', positiveY: '/images/Standard-Cube-Map/pz.png', negativeY: '/images/Standard-Cube-Map/nz1.png', positiveZ: '/images/Standard-Cube-Map/py.png', negativeZ: '/images/Standard-Cube-Map/ny1.png', }, }) viewer.scene.skyBox.show = true //天空盒 // 创建指北针小部件并将其添加到地图 new CesiumNavigation(viewer, { // 用于在使用重置导航重置地图视图时设置默认视图控制。接受的值是Cesium.Cartographic 和Cesium.Rectangle. defaultResetView: Cesium.Cartographic.fromDegrees(...center), // 用于启用或禁用罗盘。true是启用罗盘,false是禁用罗盘。默认值为true。如果将选项设置为false,则罗盘将不会添加到地图中。 enableCompass: true, // 用于启用或禁用缩放控件。true是启用,false是禁用。默认值为true。如果将选项设置为false,则缩放控件 将不会添加到地图中。 enableZoomControls: true, // 用于启用或禁用距离图例。true是启用,false是禁用。默认值为true。如果将选项设置为false,距离图例将不会添加到地图中。 enableDistanceLegend: true, // 用于启用或禁用指南针外环。true是启用,false是禁用。默认值为true。如果将选项设置为false,则该环将可见但无效。 enableCompassOuterRing: true, }) } // 取消未完成的在地图上移动类的操作(划线之类的) cancelPreviousOperation() { this.operations.forEach((operation) => operation.clear()) this.operations = [] } /** * 根据四个角落坐标确定视口位置 * @param leftUp * @param rightUp * @param rightDown * @param leftDown */ setClientByAllCorner(leftUp, rightUp, rightDown, leftDown) { const [leftUpLon, leftUpLat] = leftUp const [rightUpLon, rightUpLat] = rightUp const [rightDownLon, rightDownLat] = rightDown const [leftDownLon, leftDownLat] = leftDown // 计算矩形区域的边界 const west = Math.min(leftDownLon, leftUpLon, rightDownLon, rightUpLon) const east = Math.max(leftDownLon, leftUpLon, rightDownLon, rightUpLon) const south = Math.min(leftDownLat, leftUpLat, rightDownLat, rightUpLat) const north = Math.max(leftDownLat, leftUpLat, rightDownLat, rightUpLat) // 创建矩形范围 const rectangle = Cesium.Rectangle.fromDegrees(west, south, east, north) this.viewer.camera.setView({ destination: rectangle, orientation: { heading: 0.0, pitch: -Cesium.Math.PI_OVER_TWO, roll: 0.0, }, }) } /** * 根据中心坐标确定视口位置 * @param position.longitude * @param position.latitude */ setClientByCenter(position) { this.viewer.camera.flyTo({ destination: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 500000), // 目标点坐标 orientation: { heading: 0, // 航向角(弧度),0表示正北 pitch: Cesium.Math.toRadians(-90), // 俯仰角(弧度),-90度表示垂直向下看 roll: 0.0, // 翻滚角(弧度),0表示水平 }, }) } /** * 移除军标 * @param id */ removePlotById(id) { const targetIndex = this.plots.findIndex((item) => item.id === id) if (targetIndex === -1) return this.viewer.entities.remove(this.plots[targetIndex]) this.plots.splice(targetIndex, 1) } /** * 添加军标(base64图片) * @param base64 * @param screenPosition.x * @param screenPosition.y */ addPlotByOffset(base64, screenPosition, customId) { this.cancelPreviousOperation() const position = getCatesian3FromPX(this.viewer, screenPosition) if (!position) return false const id = customId || Cesium.createGuid() const isEnemy = false const color = 'red' const radius = 150000 const plot = { id, position, billboard: { image: base64, width: 50, height: 50, scaleByDistance: new Cesium.NearFarScalar(1000000.0, 1.0, 10000000.0, 0.2), // 重点:设置随距离缩放 }, properties: { type: MyCesium.ENTITY_TYPES.IMAGE, color, isEnemy, radius, collisions: new Set(), }, } this.viewer.entities.add(plot) this.plots.push(plot) // 1. 将屏幕坐标转换为世界坐标(在椭球体表面) const cartesian = this.viewer.camera.pickEllipsoid(screenPosition, this.viewer.scene.globe.ellipsoid) if (Cesium.defined(cartesian)) { // 2. 将世界坐标 (Cartesian3) 转换为地理坐标 Cartographic (弧度) const cartographic = Cesium.Cartographic.fromCartesian(cartesian) // 3. 将弧度转换为度数 const longitude = Cesium.Math.toDegrees(cartographic.longitude) const latitude = Cesium.Math.toDegrees(cartographic.latitude) return { plotId: id, longitude, latitude } } } /** * 添加军标(base64图片) * @param base64 * @param location.lon * @param location.lat */ addPlotByLonLat(base64, location, customId) { this.cancelPreviousOperation() const position = Cesium.Cartesian3.fromDegrees(location.lon, location.lat, 0) if (!position) return false const id = customId || Cesium.createGuid() const isEnemy = false const color = 'red' const radius = 150000 const plot = { id, position, billboard: { image: base64, width: 50, height: 50, scaleByDistance: new Cesium.NearFarScalar(1000000.0, 1.0, 10000000.0, 0.2), // 重点:设置随距离缩放 }, properties: { type: MyCesium.ENTITY_TYPES.IMAGE, color, isEnemy, radius, collisions: new Set(), }, } this.viewer.entities.add(plot) this.plots.push(plot) } /** * 连续坐标绘制路线 * @param coordinates [[longitude, latitude], ...] */ drawRouteByCoordinates(coordinates) { const route = { id: Cesium.createGuid(), polyline: { positions: Cesium.Cartesian3.fromDegreesArray(coordinates.flat()), width: 2, material: Cesium.Color.fromCssColorString('red'), show: true, clampToGround: true, }, properties: {}, } this.viewer.entities.add(route) this.routes.push(route) } /** * 连续坐标指定军标移动 * @param plotId 军标id * @param coordinates [[longitude, latitude], ...] */ initPlotMoving(plot, coordinates) { // viewer.clock 控制了场景中时间的变化,从而驱动动画。 const startTime = Cesium.JulianDate.now() const stopTime = Cesium.JulianDate.addSeconds(startTime, 1, new Cesium.JulianDate()) // 动画持续1秒 this.viewer.clock.startTime = startTime.clone() this.viewer.clock.stopTime = stopTime.clone() this.viewer.clock.currentTime = startTime.clone() this.viewer.clock.clockRange = Cesium.ClockRange.CLAMPED // 动画播放一次后停止:cite[2] this.viewer.clock.multiplier = 1 // 时间流逝速度,默认为1 const positionProperty = new Cesium.SampledPositionProperty() // 假设你获取到的路线坐标点数组是 positions(Cartesian3数组) // 并且这些点应该在1秒内完成移动 const positions = Cesium.Cartesian3.fromDegreesArray(coordinates.flat()) // 你的坐标路线 const sampleTimeStep = 1 / (positions.length - 1) // 计算每个样本点之间的时间间隔(总时间为1秒) for (let i = 0; i < positions.length; i++) { const time = Cesium.JulianDate.addSeconds(startTime, i * sampleTimeStep, new Cesium.JulianDate()) positionProperty.addSample(time, positions[i]) } // 将实体的位置关联到 SampledPositionProperty plot.position = positionProperty // 可选:让图标朝向运动方向 // plot.orientation = new Cesium.VelocityOrientationProperty(positionProperty) // cite[8] plot._isMoving = true } movePlotByCoordinates(plotId, coordinates) { // 实体 const targetPlot = this.viewer.entities.getById(plotId) if (targetPlot._isMoving === true) { // 1. 获取新的路线坐标点(假设是同步获取的,或者是在回调中) const positions = Cesium.Cartesian3.fromDegreesArray(coordinates.flat()) // 你的坐标路线 // 2. 清除旧的位置样本 targetPlot.position._property._values = [] targetPlot.position._property._times = [] // 3. 计算新的开始时间和结束时间 const newStartTime = Cesium.JulianDate.now() const newStopTime = Cesium.JulianDate.addSeconds(newStartTime, 1, new Cesium.JulianDate()) // 4. 添加新的位置样本 const newSampleTimeStep = 1 / (positions.length - 1) for (let i = 0; i < positions.length; i++) { const time = Cesium.JulianDate.addSeconds(newStartTime, i * newSampleTimeStep, new Cesium.JulianDate()) targetPlot.position.addSample(time, positions[i]) } // 5. 重置时钟以立即开始新的动画 this.viewer.clock.startTime = newStartTime.clone() this.viewer.clock.stopTime = newStopTime.clone() this.viewer.clock.currentTime = newStartTime.clone() this.viewer.clock.shouldAnimate = true // 确保动画开始播放:cite[2] } else { this.initPlotMoving(targetPlot, coordinates) } } }