由于工作中, 需要追踪卫星, 记录一下最近的工作成果, 分别有俩个方案, 一个是基于市面上主流的Threejs, 另外一个是基于Cesium, 还尝试过使用, Planetary.js, 效果其实还蛮好, 但是卫星这些不太容易往里加, 就放弃了。
下面是俩个方案的效果图, 以及原理, 由于版权是公司, 不太好贴出代码, 因此给出参考代码。
基于使用ThreeJs
原理
- 构建流星, 星团
- 使用Threejs构建旋转地球
- 构建卫星, 以及在帧率中控制卫星移动
参考源代码如下:
<html><head>
<title>Threejs实现卫星太阳板折叠</title>
<meta charset="UTF-8">
<!-- <script type="text/javascript" src="/js/lib/statistics.js"></script> -->
<script type="text/javascript" src="/js/lib/three.js"></script>
<script type="text/javascript" src="/js/lib/OrbitControls.js"></script>
<script type="text/javascript" src="/js/lib/GLTFLoader.js"></script>
<script type="text/javascript" src="/js/lib/dat.gui.js"></script>
<style type="text/css">
.dg {
/** Clear list styles */
/* Auto-place container */
/* Auto-placed GUI's */
/* Line items that don't contain folders. */
/** Folder names */
/** Hides closed items */
/** Controller row */
/** Name-half (left) */
/** Controller-half (right) */
/** Controller placement */
/** Shorter number boxes when slider is present. */
/** Ensure the entire boolean and function row shows a hand */ }
.dg ul {
list-style: none;
margin: 0;
padding: 0;
width: 100%;
clear: both; }
.dg.ac {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 0;
z-index: 0; }
.dg:not(.ac) .main {
/** Exclude mains in ac so that we don't hide close button */
overflow: hidden; }
.dg.main {
-webkit-transition: opacity 0.1s linear;
-o-transition: opacity 0.1s linear;
-moz-transition: opacity 0.1s linear;
transition: opacity 0.1s linear; }
.dg.main.taller-than-window {
overflow-y: auto; }
.dg.main.taller-than-window .close-button {
opacity: 1;
/* TODO, these are style notes */
margin-top: -1px;
border-top: 1px solid #2c2c2c; }
.dg.main ul.closed .close-button {
opacity: 1 !important; }
.dg.main:hover .close-button,
.dg.main .close-button.drag {
opacity: 1; }
.dg.main .close-button {
/*opacity: 0;*/
-webkit-transition: opacity 0.1s linear;
-o-transition: opacity 0.1s linear;
-moz-transition: opacity 0.1s linear;
transition: opacity 0.1s linear;
border: 0;
position: absolute;
line-height: 19px;
height: 20px;
/* TODO, these are style notes */
cursor: pointer;
text-align: center;
background-color: #000; }
.dg.main .close-button:hover {
background-color: #111; }
.dg.a {
float: right;
margin-right: 15px;
overflow-x: hidden; }
.dg.a.has-save > ul {
margin-top: 27px; }
.dg.a.has-save > ul.closed {
margin-top: 0; }
.dg.a .save-row {
position: fixed;
top: 0;
z-index: 1002; }
.dg li {
-webkit-transition: height 0.1s ease-out;
-o-transition: height 0.1s ease-out;
-moz-transition: height 0.1s ease-out;
transition: height 0.1s ease-out; }
.dg li:not(.folder) {
cursor: auto;
height: 27px;
line-height: 27px;
overflow: hidden;
padding: 0 4px 0 5px; }
.dg li.folder {
padding: 0;
border-left: 4px solid rgba(0, 0, 0, 0); }
.dg li.title {
cursor: pointer;
margin-left: -4px; }
.dg .closed li:not(.title),
.dg .closed ul li,
.dg .closed ul li > * {
height: 0;
overflow: hidden;
border: 0; }
.dg .cr {
clear: both;
padding-left: 3px;
height: 27px; }
.dg .property-name {
cursor: default;
float: left;
clear: left;
width: 40%;
overflow: hidden;
text-overflow: ellipsis; }
.dg .c {
float: left;
width: 60%; }
.dg .c input[type=text] {
border: 0;
margin-top: 4px;
padding: 3px;
width: 100%;
float: right; }
.dg .has-slider input[type=text] {
width: 30%;
/*display: none;*/
margin-left: 0; }
.dg .slider {
float: left;
width: 66%;
margin-left: -5px;
margin-right: 0;
height: 19px;
margin-top: 4px; }
.dg .slider-fg {
height: 100%; }
.dg .c input[type=checkbox] {
margin-top: 9px; }
.dg .c select {
margin-top: 5px; }
.dg .cr.function,
.dg .cr.function .property-name,
.dg .cr.function *,
.dg .cr.boolean,
.dg .cr.boolean * {
cursor: pointer; }
.dg .selector {
display: none;
position: absolute;
margin-left: -9px;
margin-top: 23px;
z-index: 10; }
.dg .c:hover .selector,
.dg .selector.drag {
display: block; }
.dg li.save-row {
padding: 0; }
.dg li.save-row .button {
display: inline-block;
padding: 0px 6px; }
.dg.dialogue {
background-color: #222;
width: 460px;
padding: 15px;
font-size: 13px;
line-height: 15px; }
/* TODO Separate style and structure */
#dg-new-constructor {
padding: 10px;
color: #222;
font-family: Monaco, monospace;
font-size: 10px;
border: 0;
resize: none;
box-shadow: inset 1px 1px 1px #888;
word-wrap: break-word;
margin: 12px 0;
display: block;
width: 440px;
overflow-y: scroll;
height: 100px;
position: relative; }
#dg-local-explain {
display: none;
font-size: 11px;
line-height: 17px;
border-radius: 3px;
background-color: #333;
padding: 8px;
margin-top: 10px; }
#dg-local-explain code {
font-size: 10px; }
#dat-gui-save-locally {
display: none; }
/** Main type */
.dg {
color: #eee;
font: 11px 'Lucida Grande', sans-serif;
text-shadow: 0 -1px 0 #111;
/** Auto place */
/* Controller row, <li> */
/** Controllers */ }
.dg.main {
/** Scrollbar */ }
.dg.main::-webkit-scrollbar {
width: 5px;
background: #1a1a1a; }
.dg.main::-webkit-scrollbar-corner {
height: 0;
display: none; }
.dg.main::-webkit-scrollbar-thumb {
border-radius: 5px;
background: #676767; }
.dg li:not(.folder) {
background: #1a1a1a;
border-bottom: 1px solid #2c2c2c; }
.dg li.save-row {
line-height: 25px;
background: #dad5cb;
border: 0; }
.dg li.save-row select {
margin-left: 5px;
width: 108px; }
.dg li.save-row .button {
margin-left: 5px;
margin-top: 1px;
border-radius: 2px;
font-size: 9px;
line-height: 7px;
padding: 4px 4px 5px 4px;
background: #c5bdad;
color: #fff;
text-shadow: 0 1px 0 #b0a58f;
box-shadow: 0 -1px 0 #b0a58f;
cursor: pointer; }
.dg li.save-row .button.gears {
background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;
height: 7px;
width: 8px; }
.dg li.save-row .button:hover {
background-color: #bab19e;
box-shadow: 0 -1px 0 #b0a58f; }
.dg li.folder {
border-bottom: 0; }
.dg li.title {
padding-left: 16px;
background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAPPz8yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;
cursor: pointer;
border-bottom: 1px solid rgba(255, 255, 255, 0.2); }
.dg .closed li.title {
background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAPPz8yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }
.dg .cr.boolean {
border-left: 3px solid #806787; }
.dg .cr.function {
border-left: 3px solid #e61d5f; }
.dg .cr.number {
border-left: 3px solid #2fa1d6; }
.dg .cr.number input[type=text] {
color: #2fa1d6; }
.dg .cr.string {
border-left: 3px solid #1ed36f; }
.dg .cr.string input[type=text] {
color: #1ed36f; }
.dg .cr.function:hover, .dg .cr.boolean:hover {
background: #111; }
.dg .c input[type=text] {
background: #303030;
outline: none; }
.dg .c input[type=text]:hover {
background: #3c3c3c; }
.dg .c input[type=text]:focus {
background: #494949;
color: #fff; }
.dg .c .slider {
background: #303030;
cursor: ew-resize; }
.dg .c .slider-fg {
background: #2fa1d6; }
.dg .c .slider:hover {
background: #3c3c3c; }
.dg .c .slider:hover .slider-fg {
background: #44abda; }
</style>
<script type="text/javascript" charset="UTF-8" src="/js/lib/Tween.min.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="dom"></div>
<script type="text/javascript">
var camera;
var renderer;
function init() {
// 创建一个场景,它将包含我们所有的元素,如物体,相机和灯光。
var scene = new THREE.Scene();
var textureLoader = new THREE.TextureLoader();
scene.background = textureLoader.load("assets/ba_starry.jpg");
// 创建一个摄像机,它定义了我们正在看的地方
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 150;
// camera.position.z = 90;
camera.position.x = 100;
camera.lookAt(scene.position);
var orbit = new THREE.OrbitControls(camera);
// 创建一个渲染器并设置大小,WebGLRenderer将会使用电脑显卡来渲染场景
renderer = new THREE.WebGLRenderer({
antialias: true,
logarithmicDepthBuffer: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
var ambientLight = new THREE.AmbientLight("#ffffff", 1);
scene.add(ambientLight);
// 将呈现器的输出添加到HTML元素
document.getElementById("dom").appendChild(renderer.domElement);
// 在屏幕上显示坐标轴
var axes = new THREE.AxesHelper(100);
// scene.add(axes);
// 启动动画
renderScene();
createWeiXing();
createLeiDa();
// 外层对象
const group1 = new THREE.Group();
group1.name = 'group1';
// 设置偏移中心点位置
group1.position.set(0, 0, 6);
var cube1 = createTaiYangBan("#00aa00");
group1.add(cube1);
cube1.position.set(-4, 0, 0);
// 外层对象
const group2 = new THREE.Group();
group2.name = 'group2';
// 设置外层对象的中心为原本想要旋转的位置
group2.position.set(-8, 0, 1);
var cube2 = createTaiYangBan("#0055ff");
group2.add(cube2);
// 设置偏移中心点位置
cube2.position.set(4, 0, 0);
// 外层对象
const group3 = new THREE.Group();
group3.name = 'group3';
// 设置外层对象的中心为原本想要旋转的位置
group3.position.set(8, 0, 1);
var cube3 = createTaiYangBan("#ff5500");
group3.add(cube3);
// 设置偏移中心点位置
cube3.position.set(-4, 0, 0);
group2.add(group3);
group1.add(group2);
scene.add(group1);
// 第二个太阳板
// 外层对象
const group11 = new THREE.Group();
// 设置偏移中心点位置
group11.position.set(0, 0, -4);
var cube11 = createTaiYangBan("#00aa00");
group11.add(cube11);
cube11.position.set(-4, 0, 0);
// 外层对象
const group12 = new THREE.Group();
// 设置外层对象的中心为原本想要旋转的位置
group12.position.set(-8, 0, -1);
var cube12 = createTaiYangBan("#0055ff");
group12.add(cube12);
// 设置偏移中心点位置
cube12.position.set(4, 0, 0);
// 外层对象
const group13 = new THREE.Group();
// 设置外层对象的中心为原本想要旋转的位置
group13.position.set(8, 0, -1);
var cube13 = createTaiYangBan("#ff5500");
group13.add(cube13);
// 设置偏移中心点位置
cube13.position.set(-4, 0, 0);
group12.add(group13);
group11.add(group12);
scene.add(group11);
setTimeout(function() {
tweenComplete();
}, 1000);
function tweenComplete() {
var num1 = 0,
num2 = 0,
num3 = 0,
num11 = 0,
num12 = 0,
num13 = 0;
new TWEEN.Tween({
y1: 0,
y2: 0,
y3: 0,
z: 1,
y11: 0,
y12: 0,
y13: 0,
z1: -1,
waves: 13,
dia: 1,
})
.to({
y1: Math.PI / 2,
y2: -Math.PI,
y3: Math.PI,
z: 0,
y11: -Math.PI / 2,
y12: Math.PI,
y13: -Math.PI,
z1: 0,
waves: 50,
dia: 8,
}, 3000)
.easing(TWEEN.Easing.Linear.None)
.onUpdate(function() {
group2.position.z = this.z;
group3.position.z = this.z;
group1.rotateY(this.y1 - num1);
group2.rotateY(this.y2 - num2);
group3.rotateY(this.y3 - num3);
group12.position.z = this.z1;
group13.position.z = this.z1;
group11.rotateY(this.y11 - num11);
group12.rotateY(this.y12 - num12);
group13.rotateY(this.y13 - num13);
scene.rotateZ(this.y1 - num1);
num1 = this.y1;
num2 = this.y2;
num3 = this.y3;
num11 = this.y11;
num12 = this.y12;
num13 = this.y13;
initSatellite(this.waves, this.dia);
})
.onComplete(tweenComplete1)
.start();
}
function tweenComplete1() {
var num1 = 0,
num2 = 0,
num3 = 0,
num11 = 0,
num12 = 0,
num13 = 0;
new TWEEN.Tween({
y1: 0,
y2: 0,
y3: 0,
z: 0,
y11: 0,
y12: 0,
y13: 0,
z1: 0,
waves: 13,
dia: 1,
})
.to({
y1: -Math.PI / 2,
y2: Math.PI,
y3: -Math.PI,
z: 1,
y11: Math.PI / 2,
y12: -Math.PI,
y13: Math.PI,
z1: -1,
waves: 50,
dia: 8,
}, 3000)
.easing(TWEEN.Easing.Linear.None)
.onUpdate(function() {
group2.position.z = this.z;
group3.position.z = this.z;
group1.rotateY(this.y1 - num1);
group2.rotateY(this.y2 - num2);
group3.rotateY(this.y3 - num3);
group12.position.z = this.z1;
group13.position.z = this.z1;
group11.rotateY(this.y11 - num11);
group12.rotateY(this.y12 - num12);
group13.rotateY(this.y13 - num13);
scene.rotateY(this.y1 - num1);
num1 = this.y1;
num2 = this.y2;
num3 = this.y3;
num11 = this.y11;
num12 = this.y12;
num13 = this.y13;
initSatellite(this.waves, this.dia);
})
.onComplete(tweenComplete)
.start();
}
// 创建一个基础太阳板
function createTaiYangBan(color) {
// 创建一个立方体并设置大小
var cubeGeometry = new THREE.BoxGeometry(8, 8, 1);
var cubeMaterial = new THREE.MeshBasicMaterial({
color: color,
});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
return cube;
}
// 创建一个卫星主体
function createWeiXing() {
// 创建一个立方体并设置大小
var wxGeometry = new THREE.CylinderGeometry(5, 5, 20, 100, 0);
var wxMaterial = new THREE.MeshBasicMaterial({
color: "#55557f",
});
var weixing = new THREE.Mesh(wxGeometry, wxMaterial);
weixing.position.set(0, 0, 1);
weixing.rotateZ(-Math.PI / 2);
scene.add(weixing);
}
// 创建一个雷达
function createLeiDa() {
// 创建一个立方体并设置大小
var wxGeometry = new THREE.CylinderGeometry(4, 0, 5, 100, 0);
var wxMaterial = new THREE.MeshBasicMaterial({
color: "#ffaa00",
});
var weixing = new THREE.Mesh(wxGeometry, wxMaterial);
weixing.position.set(10, 0, 1);
weixing.rotateZ(-Math.PI / 2);
scene.add(weixing);
}
// 雷达信号
function initSatellite(positionX, starLiteRadius) {
var group = new THREE.Group();
var obj = scene.getObjectByName("satellite");
scene.remove(obj);
var wxMaterial = new THREE.MeshBasicMaterial({
color: "#ffffff",
side: THREE.DoubleSide
});
var signalGeometry1 = new THREE.RingGeometry(starLiteRadius, starLiteRadius + 0.1, 100, 0);
var signal1 = new THREE.Mesh(signalGeometry1, wxMaterial);
signal1.position.z = -1;
var signalGeometry2 = new THREE.RingGeometry(starLiteRadius + 2, starLiteRadius + 2.1, 100, 0);
var signal2 = new THREE.Mesh(signalGeometry2, wxMaterial);
signal2.position.z = -10;
var signalGeometry3 = new THREE.RingGeometry(starLiteRadius + 3, starLiteRadius + 3.1, 100, 0);
var signal3 = new THREE.Mesh(signalGeometry3, wxMaterial);
signal3.position.z = -20;
group.rotateY(-Math.PI / 2);
group.position.set(positionX, 0, 1);
group.name = "satellite";
group.add(signal1);
group.add(signal2);
group.add(signal3);
scene.add(group);
}
var clock = new THREE.Clock(); //声明一个时钟对象
function renderScene() {
TWEEN.update();
orbit.update();
// 使用requestAnimationFrame函数进行渲染
requestAnimationFrame(renderScene);
renderer.render(scene, camera);
}
// 渲染的场景
renderer.render(scene, camera);
}
window.onload = init;
// 随着窗体的变化修改场景
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 监听窗体调整大小事件
window.addEventListener('resize', onResize, false);
</script>
</body></html>
卫星追踪 解析Tle 高度 速度 圈数 发射时长等。
原理
- 基于Cesium构建地球基础元素
- 使用Satellite解析Tle计算卫星运动轨迹
- 使用算法计算出卫星速度, 高度等
- 理论上可以通过逆向编码获取卫星所在的城市