草庐IT

Cesium 卫星轨迹、卫星通信、卫星过境,模拟数据传输。

Oxygen_liu 2023-05-25 原文

起因:看了cesium官网卫星通信示例发现只有cmzl版本的,决定自己动手写一个。欢迎大家一起探讨,评论留言。

效果

全部代码在最后

起步

寻找卫星轨迹数据,在网站space-track上找的,自己注册账号QQ邮箱即可。

  1. 卫星轨道类型 轨道高度 卫星用途
  2. LEO (低地球轨道) 500-2000km 对地观测、测地、通信、导航等
  3. MEO (中地球轨道) 2000-35786km 导航
  4. GEO(地球静止轨道) 35786km 通信 导航、气象观测等
  5. SSO (太阳同步轨道) <6000km 观测等
  6. 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>

有关Cesium 卫星轨迹、卫星通信、卫星过境,模拟数据传输。的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  3. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  8. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  9. ruby-on-rails - 创建 ruby​​ 数据库时惰性符号绑定(bind)失败 - 2

    我正在尝试在Rails上安装ruby​​,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf

  10. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

随机推荐