const satellite = require('satellite.js')
const moment = require('moment')
const julian = require('julian')
const { loadData } = require('./xlsx')
/*
根据卫星显示的起始时间,终止时间,tle轨道两行数得出czml文件,时间为js的Date对象,tles为对象数组,对象格式为
{
name:xx,
tle1:xx,
tle2:xx
}
*/
const tles2czml = (startTime, endTime, tles, fileName) => {
// 计算起始时间和终止时间相隔的分钟数
let minsInDuration = (endTime.getTime() - startTime.getTime()) / 60000; //mins
console.log(minsInDuration)
//设置为开始时间
let initialTime = moment(startTime.toISOString()).toISOString();
//设置为结束时间
endTime = moment(endTime.toISOString()).toISOString();
// 初始化czml数据,创建场景信息
let tempCZML = [];
tempCZML.push({
"id": "document",
"version": "1.0",
"name": `${fileName} CZML Model`,
"clock": {
"currentTime": `${initialTime}`,
"multiplier": 555,
"interval": `${initialTime}/${endTime}`,
"range": "LOOP_STOP",
"step": "SYSTEM_CLOCK_MULTIPLIER"
}
},
)
// 处理每一个sat
for (let no = 0; no < tles.length; no++) {
if(!tles[no].name){
return
};
if(!tles[no].tle1){
return
};
if(!tles[no].tle2){
return
};
let sat_name = tles[no].name;
// 保存位置信息
let res = [];
let satrec
satrec = satellite.twoline2satrec(tles[no].tle1, tles[no].tle2);
//satrec.no:以弧度/分钟为单位的平均运动,一天有1440分钟,一弧度是0.159155圈
// to go from RAD/DAY -> REV/DAY: rad * 1440 * 0.159155
//to go from REV/PER DAY to MINS/REV -> 1440/RevPerDay
let totalIntervalsInDay = satrec.no * 1440 * 0.159155; //1440 = min && 0.159155 = 1turn
// 获得运行一圈的分钟数
let minsPerInterval = 1440 / totalIntervalsInDay; // mins for 1 revolution around earth
// intervalTime 取结束时间 格式为2008-09-20T12:25:40.104Z
let intervalTime = endTime
let leadIntervalArray = [];
let trailIntervalArray = [];
console.log("Setting intervals...");
// 注意:这里之所以要倒过来求leadInterval和trailInterval是因为如果正着求,很有可能在终止时刻卫星并没有运行完一圈,导致轨道只显示一半
for (let i = minsInDuration; i >= 0; i -= minsPerInterval) {
if (i <= minsPerInterval) { // intial interval
let currentOrbitalInterval = {
"interval": `${initialTime}/${intervalTime}`,
"epoch": `${initialTime}`,
"number": [
0, minsPerInterval * 120,
minsPerInterval * 120, 0
]
}
let currTrail = {
"interval": `${initialTime}/${intervalTime}`,
"epoch": `${initialTime}`,
"number": [
0, 0,
minsPerInterval * 120, minsPerInterval * 120
]
}
leadIntervalArray.push(currentOrbitalInterval);
trailIntervalArray.push(currTrail);
}
else { //not initial so make intervals
let previousIntervalTime = moment(intervalTime).add(-minsPerInterval, 'm').toISOString();
let currentOrbitalInterval = {
"interval": `${previousIntervalTime}/${intervalTime}`,
"epoch": `${previousIntervalTime}`,
"number": [
0, minsPerInterval * 120,
minsPerInterval * 120, 0
]
}
let currTrail = {
"interval": `${previousIntervalTime}/${intervalTime}`,
"epoch": `${previousIntervalTime}`,
"number": [
0, 0,
minsPerInterval * 120, minsPerInterval * 120
]
}
intervalTime = moment(intervalTime).add(-minsPerInterval, 'm').toISOString();
leadIntervalArray.push(currentOrbitalInterval);
trailIntervalArray.push(currTrail);
}
}
// Seconds between current time and epoch time
let sec = (startTime - julian.toDate(satrec.jdsatepoch)) / 1000;
console.log(startTime, julian.toDate(satrec.jdsatepoch), sec);
for (let i = sec; i <= sec + minsInDuration * 60; i = i + 300) { //sec + minsInDuration * 60 每60秒计算一个位置信息,最后采用拉格朗日插值法处理数据
// 根据当前时间距tle两行数历元时刻的分钟数,计算当前卫星位置和速度
let positionAndVelocity = satellite.sgp4(satrec, i * 0.0166667); // 0.0166667min = 1sec
// 地惯坐标系
let positionEci = positionAndVelocity.position;
positionEci.x = positionEci.x * 1000;
positionEci.y = positionEci.y * 1000;
positionEci.z = positionEci.z * 1000;
// let velocityEci = positionAndVelocity.velocity;
// velocityEci.x = velocityEci.x * 1000;
// velocityEci.y = velocityEci.y * 1000;
// velocityEci.z = velocityEci.z * 1000;
res.push(i - sec, positionEci.x, positionEci.y, positionEci.z);
}
let dataArr = {
rawname: '',
showname: '',
airDefenseNumber: '',
PRN: '',
type: '',
launchDate: '',
runningCycle: '',
orbitalInclination: '',
apogeeAltitude: '',
perigeeAltitude: '',
atomicClockType: '',
beidouType: ''
}
loadData()[fileName].forEach(element => {
if(element.rawname == sat_name){
dataArr.rawname = element.rawname
dataArr.showname = element.showname
dataArr.airDefenseNumber = element.airDefenseNumber
dataArr.PRN = element.PRN
dataArr.type = element.type
dataArr.launchDate = element.launchDate
dataArr.runningCycle = element.runningCycle
dataArr.orbitalInclination = element.orbitalInclination
dataArr.apogeeAltitude = element.apogeeAltitude
dataArr.perigeeAltitude = element.perigeeAltitude
if(element.atomicClockType !== undefined && element.beidouType !== undefined){
dataArr.atomicClockType = element.atomicClockType
dataArr.beidouType = element.beidouType
}
}
})
let satelliteType
let description
let fillColor
let solidColor
let model
if(fileName == "BEIDOU"){
satelliteType = 'Beidou'
model = 'Beidou' + dataArr.type
fillColor = [ 0, 204, 255, 500 ]
solidColor = [ 0, 204, 255, 100 ]
description = `
NORAD ID:${ dataArr.airDefenseNumber }
PRN:${ dataArr.PRN }
\u7c7b\u578b:${ dataArr.type }
\u53d1\u5c04\u65e5\u671f:${ dataArr.launchDate }
\u8fd0\u884c\u5468\u671f\uff08\u5206\u949f\uff09:${ dataArr.runningCycle }
\u8f68\u9053\u9762\u503e\u89d2\uff08\u5ea6\uff09:${ dataArr.orbitalInclination }
\u8fdc\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.apogeeAltitude }
\u8fd1\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.perigeeAltitude }
\u539f\u5b50\u949f\u7c7b\u578b\uff08\u5317\u6597\uff09:${ dataArr.atomicClockType }
\u5317\u6597\u7c7b\u578b\uff082/3\uff09:${ dataArr.beidouType }
` } if(fileName == "GPS"){ satelliteType = 'GPS' model = 'GPS' + dataArr.type fillColor = [ 255, 255, 0, 500 ] solidColor = [ 255, 255, 0, 100 ] description = `NORAD ID:${ dataArr.airDefenseNumber }
PRN:${ dataArr.PRN }
\u7c7b\u578b:${ dataArr.type }
\u53d1\u5c04\u65e5\u671f:${ dataArr.launchDate }
\u8fd0\u884c\u5468\u671f\uff08\u5206\u949f\uff09:${ dataArr.runningCycle }
\u8f68\u9053\u9762\u503e\u89d2\uff08\u5ea6\uff09:${ dataArr.orbitalInclination }
\u8fdc\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.apogeeAltitude }
\u8fd1\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.perigeeAltitude }
` } if(fileName == "GLONASS"){ satelliteType = 'GLONASS' model = 'GLONASS' + dataArr.type fillColor = [ 102, 0, 255, 500 ] solidColor = [ 102, 0, 255, 100 ] description = `NORAD ID:${ dataArr.airDefenseNumber }
PRN:${ dataArr.PRN }
\u7c7b\u578b:${ dataArr.type }
\u53d1\u5c04\u65e5\u671f:${ dataArr.launchDate }
\u8fd0\u884c\u5468\u671f\uff08\u5206\u949f\uff09:${ dataArr.runningCycle }
\u8f68\u9053\u9762\u503e\u89d2\uff08\u5ea6\uff09:${ dataArr.orbitalInclination }
\u8fdc\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.apogeeAltitude }
\u8fd1\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.perigeeAltitude }
` } if(fileName == "GALILEO"){ satelliteType = 'Galileo' model = 'Galileo' + dataArr.type fillColor = [ 0, 204, 102, 500 ] solidColor = [ 0, 204, 102, 100 ] description = `NORAD ID:${ dataArr.airDefenseNumber }
PRN:${ dataArr.PRN }
\u7c7b\u578b:${ dataArr.type }
\u53d1\u5c04\u65e5\u671f:${ dataArr.launchDate }
\u8fd0\u884c\u5468\u671f\uff08\u5206\u949f\uff09:${ dataArr.runningCycle }
\u8f68\u9053\u9762\u503e\u89d2\uff08\u5ea6\uff09:${ dataArr.orbitalInclination }
\u8fdc\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.apogeeAltitude }
\u8fd1\u5730\u70b9\u9ad8\u5ea6\uff08\u516c\u91cc\uff09:${ dataArr.perigeeAltitude }
` } let initialCZMLProps = { "id": `Satellite/${sat_name}`, "name": `${sat_name}`, "description": description, "availability": `${initialTime}/${endTime}`, "label": { "fillColor": { "rgba": fillColor }, "font": "11pt Lucida Console", "horizontalOrigin": "TOP", "outlineColor": { "rgba": [ 0, 0, 0, 255 ] }, "outlineWidth": 2, "pixelOffset": { "cartesian2": [ 12, 0 ] }, "show": true, "style": "FILL_AND_OUTLINE", "text": `${sat_name}`, "verticalOrigin": "CENTER" }, "path": { "show": [ { "interval": `${initialTime}/${endTime}`, "boolean": true } ], "width": 1, "material": { "solidColor": { "color": { "rgba": solidColor // [ // // 随机生成轨道颜色 // // Math.floor(255 * Math.random(0, 1)), Math.floor(255 * Math.random(0, 1)), Math.floor(255 * Math.random(0, 1)), 255 // ] } } }, "resolution": 120, // The time ahead of the animation time, in seconds, to show the path. "leadTime": leadIntervalArray, // The time behind the animation time, in seconds, to show the "trailTime": trailIntervalArray }, "model": { "show": true, "gltf": `./models/geo/${model}.glb`, "minimumPixelSize": 60, "maximumPixelSize": 60, "scale": 24000.0 }, "properties":{ "satelliteType": satelliteType }, "position": { // 采用拉格朗日插值法 "interpolationAlgorithm": "LAGRANGE", // 1为线性插值,2为平方插值 "interpolationDegree": 5, // 参考坐标系,地惯坐标系 "referenceFrame": "INERTIAL", "epoch": `${initialTime}`, "cartesian": res }, "orientation": { "velocityReference": "#position" } } tempCZML.push(initialCZMLProps); } if(fileName == "BEIDOU"){ tempCZML.push({ "id": "Constellation/beidou", "name": "\u5317\u6597\u536b\u661f\u5bfc\u822a\u7cfb\u7edf", "description": "\u5317\u6597\u536b\u661f\u5bfc\u822a\u7cfb\u7edf\u662f\u4e2d\u56fd\u7740\u773c\u4e8e\u56fd\u5bb6\u5b89\u5168\u548c\u7ecf\u6d4e\u793e\u4f1a\u53d1\u5c55\u9700\u8981\uff0c\u81ea\u4e3b\u5efa\u8bbe\u8fd0\u884c\u7684\u5168\u7403\u536b\u661f\u5bfc\u822a\u7cfb\u7edf\uff0c\u662f\u4e3a\u5168\u7403\u7528\u6237\u63d0\u4f9b\u5168\u5929\u5019\u3001\u5168\u5929\u65f6\u3001\u9ad8\u7cbe\u5ea6\u7684\u5b9a\u4f4d\u3001\u5bfc\u822a\u548c\u6388\u65f6\u670d\u52a1\u7684\u56fd\u5bb6\u91cd\u8981\u65f6\u7a7a\u57fa\u7840\u8bbe\u65bd\u30022000\u5e74\u5e74\u5e95\uff0c\u5efa\u6210\u5317\u6597\u4e00\u53f7\u7cfb\u7edf\uff0c\u5411\u4e2d\u56fd\u63d0\u4f9b\u670d\u52a1\uff1b2012\u5e74\u5e74\u5e95\uff0c\u5efa\u6210\u5317\u6597\u4e8c\u53f7\u7cfb\u7edf\uff0c\u5411\u4e9a\u592a\u5730\u533a\u63d0\u4f9b\u670d\u52a1\uff1b2020\u5e74\uff0c\u5efa\u6210\u5317\u6597\u4e09\u53f7\u7cfb\u7edf\uff0c\u5411\u5168\u7403\u63d0\u4f9b\u670d\u52a1\u30022035\u5e74\u524d\u5c06\u5efa\u8bbe\u5b8c\u5584\u66f4\u52a0\u6cdb\u5728\u3001\u66f4\u52a0\u878d\u5408\u3001\u66f4\u52a0\u667a\u80fd\u7684\u7efc\u5408\u65f6\u7a7a\u4f53\u7cfb\u3002
" }) } if(fileName == "GALILEO"){ tempCZML.push({ "id": "Constellation/galileo", "name": "\u6b27\u76df\u7814\u5236\u4f3d\u5229\u7565\u536b\u661f\u5bfc\u822a\u7cfb\u7edf", "description": "\u4f3d\u5229\u7565\u5b9a\u4f4d\u7cfb\u7edf\uff08\u610f\u5927\u5229\u8bed\uff1aGalileo\uff09\uff0c\u662f\u4e00\u4e2a\u6b63\u5728\u5efa\u9020\u4e2d\u7684\u536b\u661f\u5b9a\u4f4d\u7cfb\u7edf\uff0c\u8be5\u7cfb\u7edf\u7531\u6b27\u76df\u901a\u8fc7\u6b27\u6d32\u7a7a\u95f4\u5c40\u548c\u6b27\u6d32\u5bfc\u822a\u536b\u661f\u7cfb\u7edf\u7ba1\u7406\u5c40\u5efa\u9020\uff0c\u603b\u90e8\u8bbe\u5728\u6377\u514b\u5171\u548c\u56fd\u7684\u5e03\u62c9\u683c\u3002\u4f3d\u5229\u7565\u7cfb\u7edf\u7684\u76ee\u6807\u662f\u5728\u6c34\u5e73\u548c\u5782\u76f4\u65b9\u5411\u63d0\u4f9b\u7cbe\u5ea61\u7c73\u4ee5\u5185\u7684\u5b9a\u4f4d\u670d\u52a1\uff0c\u5e76\u4e14\u5728\u9ad8\u7eac\u5ea6\u5730\u533a\u63d0\u4f9b\u6bd4\u5176\u4ed6\u7cfb\u7edf\u66f4\u597d\u7684\u5b9a\u4f4d\u670d\u52a1\u3002
" }) } if(fileName == "GLONASS"){ tempCZML.push({ "id": "Constellation/glo-ops", "name": "\u4fc4\u7f57\u65af\u683c\u6d1b\u7eb3\u65af\u536b\u661f\u5bfc\u822a\u7cfb\u7edf", "description": "\u683c\u6d1b\u7eb3\u65af\u7cfb\u7edf\u6216\u5168\u7403\u5bfc\u822a\u536b\u661f\u7cfb\u7edf\uff0c\u7b80\u79f0GLONASS\uff08\u4fc4\u8bed\uff1a\u0413\u041b\u041e\u041d\u0410\u0421\u0421\uff0c\u7f57\u9a6c\u5316\uff1aGLONASS\uff0c\u4fc4\u8bed\uff1a\u0413\u043b\u043e\u0431\u0430\u043b\u044c\u043d\u0430\u044f \u043da\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u043f\u0443\u0442\u043d\u0438\u043a\u043e\u0432\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430\uff0c\u7f57\u9a6c\u5316\uff1aGlobalnaya navigatsionnaya sputnikovaya sistema\uff09\uff0c\u5b83\u662f\u7531\u82cf\u8054\u4e8e1982\u5e74\u7814\u53d1\u7684\u536b\u661f\u5bfc\u822a\u7cfb\u7edf\uff0c\u82cf\u8054\u89e3\u4f53\u540e\u4e00\u5ea6\u4e27\u5931\u5927\u591a\u6570\u536b\u661f\u4e0e\u529f\u80fd\uff0c\u4eca\u65e5\u7531\u4fc4\u7f57\u65af\u7ef4\u62a4\u8fd0\u4f5c\u3002\u7c7b\u4f3c\u4e8e\u7f8e\u56fd\u7684\u5168\u7403\u5b9a\u4f4d\u7cfb\u7edf\u3001\u6b27\u76df\u7684\u4f3d\u5229\u7565\u5b9a\u4f4d\u7cfb\u7edf\u548c\u4e2d\u56fd\u7684\u5317\u6597\u536b\u661f\u5bfc\u822a\u7cfb\u7edf\uff0c\u4e0d\u8fc7\u6280\u672f\u7565\u6709\u5dee\u5f02\u3002
" }) } if(fileName == "GPS"){ tempCZML.push({ "id": "Constellation/gps-ops", "name": "\u7f8e\u56fd\u5168\u7403\u5b9a\u4f4d\u7cfb\u7edf", "description": "GPS\uff0c\u53c8\u79f0\u5168\u7403\u536b\u661f\u5b9a\u4f4d\u7cfb\u7edf\uff0c\u662f\u7f8e\u56fd\u56fd\u9632\u90e8\u7814\u5236\u548c\u7ef4\u62a4\u7684\u4e2d\u8ddd\u79bb\u5706\u578b\u8f68\u9053\u536b\u661f\u5bfc\u822a\u7cfb\u7edf\u3002\u5b83\u53ef\u4ee5\u4e3a\u5730\u7403\u8868\u9762\u7edd\u5927\u90e8\u5206\u5730\u533a\uff0898%\uff09\u63d0\u4f9b\u51c6\u786e\u7684\u5b9a\u4f4d\u3001\u6d4b\u901f\u548c\u9ad8\u7cbe\u5ea6\u7684\u6807\u51c6\u65f6\u95f4\u3002\u5168\u7403\u5b9a\u4f4d\u7cfb\u7edf\u53ef\u6ee1\u8db3\u4f4d\u4e8e\u5168\u7403\u5730\u9762\u4efb\u4f55\u4e00\u5904\u6216\u8fd1\u5730\u7a7a\u95f4\u7684\u519b\u4e8b\u7528\u6237\u8fde\u7eed\u4e14\u7cbe\u786e\u5730\u786e\u5b9a\u4e09\u7ef4\u4f4d\u7f6e\u3001\u4e09\u7ef4\u8fd0\u52a8\u548c\u65f6\u95f4\u7684\u9700\u6c42\u3002
" }) } return tempCZML; } module.exports = { tles2czml }