起因:看了cesium官网卫星通信示例发现只有cmzl版本的,决定自己动手写一个。欢迎大家一起探讨,评论留言。
效果
全部代码在最后
起步
寻找卫星轨迹数据,在网站space-track上找的,自己注册账号QQ邮箱即可。
- 卫星轨道类型 轨道高度 卫星用途
- LEO (低地球轨道) 500-2000km 对地观测、测地、通信、导航等
- MEO (中地球轨道) 2000-35786km 导航
- GEO(地球静止轨道) 35786km 通信 导航、气象观测等
- SSO (太阳同步轨道) <6000km 观测等
- IGSO(倾斜地球同步轨道) 35786km 导航
点击TLE就可以得到卫星的两个轨道数据
当然这个数据需要相对应的插件satellite.js转换成我们熟悉的经纬高;
拔下来的数据存入json文件中:
最后构造卫星轨迹对象
import { twoline2satrec, gstime, eciToGeodetic, PositionAndVelocity, propagate, EciVec3, degreesLong } from 'satellite.js'; fetch("data/points.json").then(res => res.json()).then(data => { for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { const element = data[key]; const satrec = twoline2satrec(element.data[0], element.data[1]); const positionAndVelocity: PositionAndVelocity = propagate(satrec, time); const positionEci = positionAndVelocity.position as EciVec3<number>; obj[key] = { country: element.country, times: [], positions: [] }; let lon, lat, alt; //一年365天 一天为间隔 for (let index = min; index <= nowTime; index = index + 86400000) { const gmst = gstime(new Date(index)); const positionGd = eciToGeodetic(positionEci, gmst) lon = positionGd.longitude, lat = positionGd.latitude, alt = positionGd.height; obj[key].times.push(index) obj[key].positions.push([degreesLong(lon), degreesLong(lat), alt]) } } } })
复制
加载卫星和轨迹线
//用数据集方便管理 const satellites = new Cesium.CustomDataSource("satellite"); const polylines = new Cesium.CustomDataSource("statelliteLine"); function computeCirclularFlight(arr: Obj, hasLine: boolean = true) { for (const key in arr) { if (Object.prototype.hasOwnProperty.call(arr, key)) { const element = arr[key]; const property = new Cesium.SampledPositionProperty(); const length = element.positions.length const positions: number[] = [] let p, t for (let index = 0; index < length; index++) { p = element.positions[index] t = element.times[index] property.addSample(Cesium.JulianDate.addHours(Cesium.JulianDate.fromDate(new Date(t)), 8, new Cesium.JulianDate()), Cesium.Cartesian3.fromDegrees(p[0], p[1], p[2])); positions.push(...element.positions[index]) } satellites.entities.add({ id: key, model: { uri: element.country === 'US' ? 'models/satellite/satellite1/Satellite.gltf' : element.country === 'PRC' ? 'models/satellite/satellite2/10477_Satellite_v1_L3.gltf' : 'models/satellite/satellite3/satellite.gltf', minimumPixelSize: 32 }, position: property, }); if (hasLine) polylines.entities.add({ id: key, polyline: { width: 1, material: Cesium.Color.BLUE.withAlpha(.5), positions: Cesium.Cartesian3.fromDegreesArrayHeights(positions) } }) } } viewer.dataSources.add(satellites); viewer.dataSources.add(polylines); }
复制
加载卫星和轨迹的效果
加载地面雷达
const radars = new Cesium.CustomDataSource("radar"); const radarpoints: { id: string; lon: number; lat: number; radius: number }[] = [ { id: 'radar1', lon: 104, lat: 34, radius: 300000 }, { id: 'radar2', lon: -100, lat: 55, radius: 300000 }, { id: 'radar3', lon: 109.70841, lat: 19.365791, radius: 300000 }, ] //添加雷达 radarpoints.forEach(i => { createRadar(i.id, i.lon, i.lat, i.radius) }) function createRadar(id: string, lon: number, lat: number, radius: number) { radars.entities.add({ id: id, model: { uri: 'models/antenna_07.glb', minimumPixelSize: 32, }, position: Cesium.Cartesian3.fromDegrees(lon, lat), }) viewer.dataSources.add(radars) new LCesiumApi.RadarPrimitive({ radius: radius, stackPartitions: 10, slicePartitions: 10, stackDegrees: { x: 0, y: 90, }, sliceDegrees: { x: 0, y: 360, }, color: Cesium.Color.GREEN.withAlpha(0.2), lineColor: Cesium.Color.RED, scanColor: Cesium.Color.YELLOW.withAlpha(0.2), scanLineColor: Cesium.Color.RED, scene: viewer.scene, center: Cesium.Cartesian3.fromDegrees(lon, lat), scanSpeed: 5000, show: true, scan: true, }); }
复制
关于雷达效果在我之前文章里面有
卫星与地面雷达通信
- 暂时只做了m(雷达)-n(卫星),m*n;没有做卫星之间的通信判断,不过原理都是一样的.
- 网上搜索了一下通信距离一般是3,580km
- 计算此时卫星距雷达的距离,其实就是计算带高度的经纬度之间的距离
Cartesian3.distance(point1: Cartesian3, point2: Cartesian3)
复制
当卫星和地面卫星通信时,创建连线,离开设置为隐藏。
function computeRange() { satellites.entities.values.forEach(i => { radars.entities.values.forEach(j => { const po1 = i.position?.getValue(viewer.clock.currentTime) const po2 = j.position?.getValue(viewer.clock.currentTime) if (po1 && po2) { const len = LCesiumApi.Tool.getDistanceFromCartesian3(po1, po2) if (len <= communicationRange) { if (showFlyObject[`${i.id}-${j.id}`]) { showFlyObject[`${i.id}-${j.id}`].show = true showFlyObject[`${i.id}-${j.id}`].po1 = LCesiumApi.Tramsform.degreesFromCartesian(po1) showFlyObject[`${i.id}-${j.id}`].po2 = LCesiumApi.Tramsform.degreesFromCartesian(po2) } else { showFlyObject[`${i.id}-${j.id}`] = { entity: null, show: true, po1: LCesiumApi.Tramsform.degreesFromCartesian(po1), po2: LCesiumApi.Tramsform.degreesFromCartesian(po2) } } } else { if (showFlyObject[`${i.id}-${j.id}`]) showFlyObject[`${i.id}-${j.id}`].show = false } } }) }) setLine() } function setLine() { for (const key in showFlyObject) { if (Object.prototype.hasOwnProperty.call(showFlyObject, key)) { const element = showFlyObject[key]; if (element.entity === null) element.entity = createFlyLine(key) element.entity.show = element.show } } } function createFlyLine(id: string) { var material = new PolylineTrailLinkMaterialProperty({ color: Cesium.Color.fromCssColorString('#7ffeff'), duration: 3000, }); const line = Connection.entities.add({ id: id, polyline: { positions: new Cesium.CallbackProperty(() => { return Cesium.Cartesian3.fromDegreesArrayHeights([ showFlyObject[id].po1.longitude, showFlyObject[id].po1.latitude, showFlyObject[id].po1.height, showFlyObject[id].po2.longitude, showFlyObject[id].po2.latitude, showFlyObject[id].po2.height, ]) }, false), width: 8, material } }) return line }
复制
完整代码
<template> <Map @onViewerLoaded="onViewerLoaded" :options="options" /> </template> <script lang="ts" setup> import Map from "@/components/Cesium/lib/Map.vue"; import * as Cesium from "cesium"; import { message } from 'ant-design-vue' import { twoline2satrec, gstime, eciToGeodetic, PositionAndVelocity, propagate, EciVec3, degreesLong } from 'satellite.js'; import LCesiumApi from "@lib/main"; //@ts-ignore import { PolylineTrailLinkMaterialProperty } from './PolylineTrailMaterialProperty.js' const options = { imageryProvider: new Cesium.ArcGisMapServerImageryProvider({ url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer' }), } let viewer: Cesium.Viewer let start: Cesium.JulianDate let stop: Cesium.JulianDate let handler: Cesium.ScreenSpaceEventHandler; const communicationRange = 3580000; const time = new Date() let max = time.getTime() let year = 31622400000; let min = max - year; type Obj = { [index: string]: { country: string; times: number[]; positions: [[number, number, number]] | number[][] } } const showFlyObject: { [index: string]: any } = {} let obj: Obj = {} const radarpoints: { id: string; lon: number; lat: number; radius: number }[] = [ { id: 'radar1', lon: 104, lat: 34, radius: 300000 }, { id: 'radar2', lon: -100, lat: 55, radius: 300000 }, { id: 'radar3', lon: 109.70841, lat: 19.365791, radius: 300000 }, ] const onViewerLoaded = (Viewer: Cesium.Viewer) => { viewer = Viewer handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas); //设置时间轴 setTimeline() //读取卫星分布两行数据 const nowTime = time.getTime() fetch("data/points.json").then(res => res.json()).then(data => { for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { const element = data[key]; const satrec = twoline2satrec(element.data[0], element.data[1]); const positionAndVelocity: PositionAndVelocity = propagate(satrec, time); const positionEci = positionAndVelocity.position as EciVec3<number>; obj[key] = { country: element.country, times: [], positions: [] }; let lon, lat, alt; //一年365天 一天为间隔 for (let index = min; index <= nowTime; index = index + 86400000) { const gmst = gstime(new Date(index)); const positionGd = eciToGeodetic(positionEci, gmst) lon = positionGd.longitude, lat = positionGd.latitude, alt = positionGd.height; obj[key].times.push(index) obj[key].positions.push([degreesLong(lon), degreesLong(lat), alt]) } } } computeCirclularFlight(obj) }) //添加点击事件 addPick() //添加雷达 radarpoints.forEach(i => { createRadar(i.id, i.lon, i.lat, i.radius) }) //添加过境扫描 ; (viewer as any).frameUpdate.addEventListener((delta: any) => { computeRange() }); } function setTimeline() { start = Cesium.JulianDate.fromDate(new Date(min)); // 获取当前时间 这不是国内的时间 start = Cesium.JulianDate.addHours(start, 8, new Cesium.JulianDate()); // 添加八小时,得到我们东八区的北京时间 stop = Cesium.JulianDate.fromDate(new Date(max)); // 设置一个结束时间,意思是360秒之后时间结束 viewer.clock.startTime = start.clone(); // 给cesium时间轴设置开始的时间,也就是上边的东八区时间 viewer.clock.stopTime = stop.clone(); // 设置cesium时间轴设置结束的时间 viewer.clock.currentTime = start.clone(); // 设置cesium时间轴设置当前的时间 viewer.clock.clockRange = Cesium.ClockRange.LOOP_STOP; // 时间结束了,再继续重复来一遍 //时间变化来控制速度 // 时间速率,数字越大时间过的越快 viewer.clock.multiplier = 1; //给时间线设置边界 viewer.timeline.zoomTo(start, stop); } const satellites = new Cesium.CustomDataSource("satellite"); const polylines = new Cesium.CustomDataSource("statelliteLine"); const radars = new Cesium.CustomDataSource("radar"); const Connection = new Cesium.CustomDataSource("connection"); function computeCirclularFlight(arr: Obj, hasLine: boolean = true) { for (const key in arr) { if (Object.prototype.hasOwnProperty.call(arr, key)) { const element = arr[key]; const property = new Cesium.SampledPositionProperty(); const length = element.positions.length const positions: number[] = [] let p, t for (let index = 0; index < length; index++) { p = element.positions[index] t = element.times[index] property.addSample(Cesium.JulianDate.addHours(Cesium.JulianDate.fromDate(new Date(t)), 8, new Cesium.JulianDate()), Cesium.Cartesian3.fromDegrees(p[0], p[1], p[2])); positions.push(...element.positions[index]) } satellites.entities.add({ id: key, model: { uri: element.country === 'US' ? 'models/satellite/satellite1/Satellite.gltf' : element.country === 'PRC' ? 'models/satellite/satellite2/10477_Satellite_v1_L3.gltf' : 'models/satellite/satellite3/satellite.gltf', minimumPixelSize: 32 }, position: property, }); if (hasLine) polylines.entities.add({ id: key, polyline: { width: 1, material: Cesium.Color.BLUE.withAlpha(.5), positions: Cesium.Cartesian3.fromDegreesArrayHeights(positions) } }) } } viewer.dataSources.add(satellites); viewer.dataSources.add(polylines); viewer.dataSources.add(Connection) } const addPick = () => { handler.setInputAction((movement: any) => { const pickedObject = viewer.scene.pick(movement.position); if (Cesium.defined(pickedObject)) { message.info(pickedObject.id.id) } }, Cesium.ScreenSpaceEventType.LEFT_CLICK) } function createRadar(id: string, lon: number, lat: number, radius: number) { radars.entities.add({ id: id, model: { uri: 'models/antenna_07.glb', minimumPixelSize: 32, }, position: Cesium.Cartesian3.fromDegrees(lon, lat), }) viewer.dataSources.add(radars) new LCesiumApi.RadarPrimitive({ radius: radius, stackPartitions: 10, slicePartitions: 10, stackDegrees: { x: 0, y: 90, }, sliceDegrees: { x: 0, y: 360, }, color: Cesium.Color.GREEN.withAlpha(0.2), lineColor: Cesium.Color.RED, scanColor: Cesium.Color.YELLOW.withAlpha(0.2), scanLineColor: Cesium.Color.RED, scene: viewer.scene, center: Cesium.Cartesian3.fromDegrees(lon, lat), scanSpeed: 5000, show: true, scan: true, }); } function computeRange() { satellites.entities.values.forEach(i => { radars.entities.values.forEach(j => { const po1 = i.position?.getValue(viewer.clock.currentTime) const po2 = j.position?.getValue(viewer.clock.currentTime) if (po1 && po2) { const len = LCesiumApi.Tool.getDistanceFromCartesian3(po1, po2) if (len <= communicationRange) { if (showFlyObject[`${i.id}-${j.id}`]) { showFlyObject[`${i.id}-${j.id}`].show = true showFlyObject[`${i.id}-${j.id}`].po1 = LCesiumApi.Tramsform.degreesFromCartesian(po1) showFlyObject[`${i.id}-${j.id}`].po2 = LCesiumApi.Tramsform.degreesFromCartesian(po2) } else { showFlyObject[`${i.id}-${j.id}`] = { entity: null, show: true, po1: LCesiumApi.Tramsform.degreesFromCartesian(po1), po2: LCesiumApi.Tramsform.degreesFromCartesian(po2) } } } else { if (showFlyObject[`${i.id}-${j.id}`]) showFlyObject[`${i.id}-${j.id}`].show = false } } }) }) setLine() } function setLine() { for (const key in showFlyObject) { if (Object.prototype.hasOwnProperty.call(showFlyObject, key)) { const element = showFlyObject[key]; if (element.entity === null) element.entity = createFlyLine(key) element.entity.show = element.show } } } function createFlyLine(id: string) { var material = new PolylineTrailLinkMaterialProperty({ color: Cesium.Color.fromCssColorString('#7ffeff'), duration: 3000, }); const line = Connection.entities.add({ id: id, polyline: { positions: new Cesium.CallbackProperty(() => { return Cesium.Cartesian3.fromDegreesArrayHeights([ showFlyObject[id].po1.longitude, showFlyObject[id].po1.latitude, showFlyObject[id].po1.height, showFlyObject[id].po2.longitude, showFlyObject[id].po2.latitude, showFlyObject[id].po2.height, ]) }, false), width: 8, material } }) return line } </script>
复制