卫星位置解算


前言:

本章节代码均在Gitee中开源:

卫星位置计算代码icon-default.png?t=N7T8https://gitee.com/Ehundred/navigation-engineering/tree/master/%E5%8D%AB%E6%98%9F%E5%AF%BC%E8%88%AA%E5%8E%9F%E7%90%86/GPS%E5%8D%AB%E6%98%9F%E4%BD%8D%E7%BD%AE%E8%A7%A3%E7%AE%97/Satellite_position_calculation其中涉及到时空转换的部分,在另一章节有详细讲解,如有疑惑可以先阅读上一章节:

时空坐标转换icon-default.png?t=N7T8https://blog.csdn.net/qq_74260823/article/details/139271746?spm=1001.2014.3001.5501

因为这章是学校作业,所以稍微正经点. 


卫星参数的读取

首先,关于导航电文的理解和构成,如果还不太了解,请移步另一篇文章:

卫星信号的构成icon-default.png?t=N7T8http://t.csdnimg.cn/6yY29

导航电文会发出很多参数,最终会被接收机接收到,然后转换为人类可以看懂的具体的数字。接收机在转换之后,会把适用于我们定位的数据单独列出来,然后写入文件之中成为广播星历,其中文件的格式长这样:

其具体对应的含义是:

因为写入的格式一定是固定的,所以我们读取数据的时候,也可以利用这些固定的格式,按顺序定义一个结构体:

struct satellite_data_block
{double Serial;//序列号double year;double month;double day;double hour;double minute;double second;//年月日时分秒double a0;//卫星钟差double a1;//卫星钟偏double a2;//卫星钟偏移double AODE;//数据龄期double Crs;//轨道半径改正项double deltan;//平均角速度改正项double M0;//平近点角double Cuc;//升交点角距改正项double e;//轨道偏心率double Cus;//升交点角距改正项double sqrtA;//轨道长半轴平方根double TOE;//星历的参考时刻  周内秒double Cic;//轨道倾角的改正项double OMEGA;//升交点经度double Cis;//轨道倾角改正项double i0;//轨道倾角double Crc;//轨道半径的改正项double omega;//近地点角距double deltaomega;//升交点赤经变化double IDOT;//轨道倾角的变率//对于单点定位,以上数据足以,但是为了保持卫星数据完整,以下保持了所有的数据double CA;//CA码double GPSweek;//gps周数double L2P;//L2P码double SVA;//卫星精度double SVH;//卫星健康double TGD;//电离层延迟double IODC;//数据质量double shoot_time;//发射时间double h;//拟合区间
};

为什么序列号,年月日时分秒也要用double,在待会会讲解。 

而读取的时候,因为格式是固定的,每一个完整的数据都有8行,我们可以每次就读8行,然后把这8行合并成同一个字符串,用来表示同一个数据块。而这个函数只负责读取,把读取的数据转换为可用的数据并导入在结构体中,就交给另外一个函数来完成。

void read()
{fstream ifs(_path_name, ios_base::in);if (!ifs)	exit(-1);while (!ifs.eof()){string data;for (int i = 0; i < 8; i++){//采用的是读取整个数据块的方法char buffer[82];ifs.getline(buffer, 81);data += buffer;}get_data(data);}
}

这个把字符串转换成有用的数据的方法,可以有很多实现形式,完全凭个人习惯。因为有36个数据,我们就定义一个36大小的double数组,每次读一个数据,就把数据放在double数组之中,一直到读完整个36个数据。
而读完之后,因为结构体采用的是内存连续区间,而数组也是内存连续区间,我们之间用memcpy,把数组的内存copy到结构体的内存上,就省略了大量的代码量。

void get_data(string& data)
{double init[36];int cur = 0;for (int i = 0; i < 36; i++){string tmp = get_part(data, cur);//获取一串数字的字符串init[i] = _to_double(tmp);//把获得的字符串转换成double//两个函数的具体实现看个人习惯,我的实现可以去看完整代码}satellite_data_block ins;memcpy(&ins, init, sizeof(double) * 36);_serial.insert(make_pair(ins.Serial, _data.size()));_data.push_back(ins);
}

读取之后,我们直接把他放在数组中,然后接着读取下一个。最终数组中便完整记录了所有卫星的参数,我们可以访问任意卫星的参数。
但是,这里会有一个问题:我们怎么知道数组中对应的是哪个卫星?难道第5个元素对应的一定是序号为5的卫星吗?当然不一定。我们有两个解决方案:

  1. 每次遍历数组,查看每个元素的序列号
  2. 用哈希表,把元素下标对应上卫星的序列号

第一个方法的时间复杂度每次都是O(n),自然是比较不靠谱的,所以我们就采用了第二个方法,用哈希表的搜索降低时间复杂度。


卫星位置的解算

导航电文是两个小时更新一次的。因为这个特性,注定了导航电文无法发送即时的位置数据,所以卫星只能发送更新时刻的位置,然后根据一些参数来模拟卫星的运动,推算出卫星每一刻的位置。

用最简单的思想来看待这个问题,其实也很简单:

只不过具体实现的时候,要考虑的影响因素有很多就是了。

因为每个卫星参数对应的位置是独立的,所以对每一个卫星参数,我们都用一个类来表示,类中包含所有可能要用到的参数:

class calculate
{
private:double sqrtA;double angular_velocity;double _tk;double _angular_velocity;double _Mk;double _Ek;double _Vk;double _argument_of_latitude;double _Cuk;double _Crk;double _Cik;double _Uk;double _Rk;double _Ik;double _OMGk;
};

具体的符号会在计算的过程中讲解。 

1.计算角速度

第一个在广播星历中已经给出,我们只需要计算第二个:

#define GM 3.9860050E14void sqrt_A(double sqrt_a)
{sqrtA = sqrt_a;angular_velocity = sqrt(GM) / pow(sqrtA, 3);
}

2.计算卫星运动时间

因为广播星历给出的时间Toe是每次更新的瞬间时间,而Tobs是当前的观测时间,所以Tobs-Toe,再消去卫星信号传播时间导致的误差,就是卫星运动的时间Tk。
注意:在广播星历中,给出的Toe是周内秒,而实际的计算计算的是总时间差。这里的Toe并非表示周内秒,而是GPS包含周的总时间。为了计算方便,我们可以直接把他转换成Unix时来进行加减。

double get_time(double Toe,double week,atime cur = curtime)
{//因为没有考虑信号传播时间,最终有小部分误差GPStime _bt(week, Toe);atime bt(_bt);double ret = cur.GetGtime()._time-bt.GetGtime()._time;if (ret > ROUND_SECOND/2)	ret -= ROUND_SECOND;else if (ret < -ROUND_SECOND / 2)	ret += ROUND_SECOND;//ROUND_SECOND GPS周一周的秒数_tk = ret;return ret;
}

3.对平均角速度改正

在广播星历中,会给出一个参数:Δn,这个参数就是给我们用以修正角速度的,我们直接把计算出的角速度修正上Δn便可以了:

double new_angular_velocity(double deltan)
{_angular_velocity = angular_velocity + deltan;return _angular_velocity;
}

4.计算平近点角

广播星历会给出一个参数M0,是在数据更新时刻的近点角。而此时,卫星运动了Tk时刻,我们用角速度n乘以运动时间Tk,就能得出卫星距离更新时刻转动的角度,再加上更新时刻的角度,就是当前时刻的平近点角

double mean_anomaly(double M0)
{_Mk = M0 + _tk * _angular_velocity;return _Mk;
}

 5.计算偏近点角

公式:E=M+e*sinE

和时空转换一样,我们发现,这个公式左边有一个E,右边有一个E,怎么可以用自己来计算自己呢?这里就要用迭代的方法,取E一个初值En,然后不断求下一个自己的值En+1,一直到En+1和En相差很小很小,就代表他们收敛到了一个具体的值上,这个值就是我们要求得的值。 

double Eccentric_Anomaly(double e)
{double Mk = _Mk;auto get_Ek = [e,Mk](double Ep){return Ep - (Ep - e * sin(Ep) - Mk) / (1 - e * cos(Ep));};double E0 = _Mk;double E1 = get_Ek(E0);while (abs(E1 - E0) >= 1E-14){E0 = E1;E1 = get_Ek(E0);}_Ek = E1;return _Ek;
}

6.计算真近点角

double True_anomaly(double e)
{_Vk = atan((sqrt(1 - e * e) * sin(_Ek) )/ (cos(_Ek) - e));if (abs(_Vk - _Mk) > Pi / 2)_Vk += _Vk > _Mk ? -Pi : Pi;return _Vk;
}

 这里也可以直接用atan2来实现。

有一点要注意:导航电文中给出的所有数据都是弧度,而非角度,所以我们在使用sin等函数的时候,直接使用广播星历中的数据便可以了,不需要角度转弧度即*180/Pi

7.计算升交角距

double argument_of_latitude(double omega)
{_argument_of_latitude = _Vk + omega;return _argument_of_latitude;
}

8.计算二阶调和改正数

double Correction_of_uk(double cuc,double cus)
{_Cuk = cus * sin(2 * _argument_of_latitude) + cuc * cos(2 * _argument_of_latitude);return _Cuk;
}double Correction_of_rk(double crc, double crs)
{_Crk = crs * sin(2 * _argument_of_latitude) + crc * cos(2 * _argument_of_latitude);return _Crk;
}double Correction_of_ik(double cic, double cis)
{_Cik = cis * sin(2 * _argument_of_latitude) + cic * cos(2 * _argument_of_latitude);return _Cik;
}

9.计算改正后的开普勒轨道根数

double get_uk()
{_Uk = _argument_of_latitude + _Cuk;return _Uk;
}double get_rk(double e)
{_Rk = sqrtA*sqrtA * (1 - e * cos(_Ek)) + _Crk;return _Rk;
}double get_ik(double i0, double idot)
{_Ik = i0 + _Cik + idot * _tk;return _Ik;
}double get_OMGk(double OMG0,double OMGd,double toe)
{_OMGk = OMG0 + (OMGd - OMGE) * _tk - OMGE * toe;return _OMGk;
}

 10.计算卫星的位置

在求出卫星在轨道平面坐标系的坐标后,把该坐标绕x轴旋转i角(轨道倾角),绕z轴旋转OmegaK角(升交点经度),得到的便是在地心地固坐标系下的坐标。这个部分是线代中的坐标系转换,具体的方程感兴趣可以自己推导

vector<double> get_xyz()
{double _xk = _Rk * cos(_Uk);double _yk = _Rk * sin(_Uk);double x = _xk * cos(_OMGk) - _yk * cos(_Ik) * sin(_OMGk);double y = _xk * sin(_OMGk) + _yk * cos(_Ik) * cos(_OMGk);double z = _yk * sin(_Ik);return { x,y,z };
}

最终,便可以得到卫星坐标的位置xyz。

代码汇总:

我们采用了一个类来完成,在传入一个卫星数据块后,便在构造函数里直接计算出该数据块对应的卫星位置。而因为读取卫星数据,计算卫星数据是分离的,所以我们可以再设计另外一个接口,将两个类的功能结合在一起,而这个实现方法可以根据个人习惯,也可以直接参考我完整的代码。

#define GM 3.9860050E14
#define OMGE 7.2921151467E-5commontime _curtime = commontime({ 2021,2,1,0,0 }, 30);//当前观测时间
atime curtime = atime(_curtime);//计算的完整过程,必须按顺序依次进行
class calculate
{
public:vector<double> _calculate(const satellite_data_block& s){sqrt_A(s.sqrtA);get_time(s.TOE, s.GPSweek);new_angular_velocity(s.deltan);mean_anomaly(s.M0);Eccentric_Anomaly(s.e);True_anomaly(s.e);argument_of_latitude(s.omega);Correction_of_uk(s.Cuc, s.Cus);Correction_of_rk(s.Crc, s.Crs);Correction_of_ik(s.Cic, s.Cis);get_uk();get_rk(s.e);get_ik(s.i0, s.IDOT);get_OMGk(s.OMEGA, s.deltaomega, s.TOE);vector<double> xyz = get_xyz();return xyz;}
private:void sqrt_A(double sqrt_a){sqrtA = sqrt_a;angular_velocity = sqrt(GM) / pow(sqrtA, 3);}double get_time(double Toe,double week,atime cur = curtime){//因为没有考虑信号传播时间,最终有小部分误差GPStime _bt(week, Toe);atime bt(_bt);double ret = cur.GetGtime()._time-bt.GetGtime()._time;if (ret > ROUND_SECOND/2)	ret -= ROUND_SECOND;else if (ret < -ROUND_SECOND / 2)	ret += ROUND_SECOND;_tk = ret;return ret;}double new_angular_velocity(double deltan){_angular_velocity = angular_velocity + deltan;return _angular_velocity;}double mean_anomaly(double M0){_Mk = M0 + _tk * _angular_velocity;return _Mk;}double Eccentric_Anomaly(double e){double Mk = _Mk;auto get_Ek = [e,Mk](double Ep){return Ep - (Ep - e * sin(Ep) - Mk) / (1 - e * cos(Ep));};double E0 = _Mk;double E1 = get_Ek(E0);while (abs(E1 - E0) >= 1E-14){E0 = E1;E1 = get_Ek(E0);}_Ek = E1;return _Ek;}double True_anomaly(double e){_Vk = atan((sqrt(1 - e * e) * sin(_Ek) )/ (cos(_Ek) - e));if (abs(_Vk - _Mk) > Pi / 2)_Vk += _Vk > _Mk ? -Pi : Pi;return _Vk;}double argument_of_latitude(double omega){_argument_of_latitude = _Vk + omega;return _argument_of_latitude;}double Correction_of_uk(double cuc,double cus){_Cuk = cus * sin(2 * _argument_of_latitude) + cuc * cos(2 * _argument_of_latitude);return _Cuk;}double Correction_of_rk(double crc, double crs){_Crk = crs * sin(2 * _argument_of_latitude) + crc * cos(2 * _argument_of_latitude);return _Crk;}double Correction_of_ik(double cic, double cis){_Cik = cis * sin(2 * _argument_of_latitude) + cic * cos(2 * _argument_of_latitude);return _Cik;}double get_uk(){_Uk = _argument_of_latitude + _Cuk;return _Uk;}double get_rk(double e){_Rk = sqrtA*sqrtA * (1 - e * cos(_Ek)) + _Crk;return _Rk;}double get_ik(double i0, double idot){_Ik = i0 + _Cik + idot * _tk;return _Ik;}double get_OMGk(double OMG0,double OMGd,double toe){_OMGk = OMG0 + (OMGd - OMGE) * _tk - OMGE * toe;return _OMGk;}vector<double> get_xyz(){double _xk = _Rk * cos(_Uk);double _yk = _Rk * sin(_Uk);double x = _xk * cos(_OMGk) - _yk * cos(_Ik) * sin(_OMGk);double y = _xk * sin(_OMGk) + _yk * cos(_Ik) * cos(_OMGk);double z = _yk * sin(_Ik);return { x,y,z };}private:double sqrtA;double angular_velocity;double _tk;double _angular_velocity;double _Mk;double _Ek;double _Vk;double _argument_of_latitude;double _Cuk;double _Crk;double _Cik;double _Uk;double _Rk;double _Ik;double _OMGk;
};

调试数据:

为了方便大家调试,我列出了对应的调试数据:

对应我的文件中序列号为1的卫星:

广播星历参数:

计算出的数据:
最终结果:

更多的测试用例可以直接在完整源码中查看所有31个卫星数据 


最后,给自己叠个甲。因为自己才是导航工程大二的本科生,有些概念理解可能不到位,而又想用最容易理解的方式表达出来,所以可能正确性会稍微有些偏差。但是对初学者来说,应该不会存在太大的错误,如果可以帮到你,真的荣幸之极。还有

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/22199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

心怀希望の光栅化

还记得什么是光栅化咩&#xff1f; 将三维空间的几何形体显现在屏幕上&#xff0c;这就是光栅化&#xff08;游戏、实时图形学的应用&#xff09; Perspective Projection 在正交投影里如何定义三维空间中的立方体呢&#xff1f; 用x轴的覆盖&#xff08;左、右&#xff09;…

【UML用户指南】-02-UML基本元素的介绍(二)

目录 1、语法和语义规则 2、UML中的公共机制 &#xff08;1&#xff09;规约 &#xff08;2&#xff09;修饰 &#xff08;3&#xff09;通用划分 &#xff08;4&#xff09;扩展机制 衍型/版型/类型&#xff08;stereotype&#xff09; 标记值 &#xff08;tagged val…

Java编程常见问题汇总四

系列文章目录 文章目录 系列文章目录前言一、忽略所有异常二、重复包装RuntimeException三、不正确的传播异常四、用日志记录异常五、异常处理不彻底 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。…

[C/C++]_[初级]_[在Windows和macOS平台上导出动态库的一些思考]

场景 最近看了《COM本质论》里关于如何设计基于抽象基类作为二进制接口,把编译器和链接器的实现隐藏在这个二进制接口中,从而使用该DLL时不需要重新编译。在编译出C接口时,发现接口名直接是函数名,比如BindNativePort,怎么不是_BindNativePort?说明 VC++导出的函数默认是使…

Google Earth Engine精度评价方法

今天讲讲如何在GEE中做最后的精度评价。主要是因为在和许多读者或通过交流群&#xff0c;或通过私聊沟通过程中&#xff0c;发现很多人还不是很理解在GEE中分类后精度评价的问题。 在进行评价之前&#xff0c;需要明晰在GEE中精度评价分为哪几种情况。我们这里说的是两种情况。…

收藏品NFT的开发流程

开发收藏品NFT的流程涉及多个阶段&#xff0c;从概念化和设计到技术实现和市场推广。以下是详细的开发步骤&#xff0c;通过这些步骤&#xff0c;可以成功开发和发布收藏品NFT项目&#xff0c;吸引用户和投资者&#xff0c;并确保项目的持续运营和成功。北京木奇移动技术有限公…

Fiddler入门(接口抓包及APP测试)

目录 一、Fiddler基础介绍 二、Fiddler的作用 三、Fiddler安装 四、Fiddler界面功能介绍 1、界面介绍 1&#xff09;、菜单栏介绍 2&#xff09;、工具栏介绍 3&#xff09;、会话栏介绍 五、Fiddler抓取https数据 &#xff08;面试题&#xff09; 六、Fiddler…

【刷题(17)】技巧

一 技巧基础 二 136. 只出现一次的数字 1 题目 2 解题思路 哈希表map 其实看到题目数组中某个元素出现的次数也可以直接用unordered_map容器统计每一个元素出现的次数&#xff0c;然后在遍历整个map容器查看是否有元素出现的次数等于1 3 code class Solution { public:in…

商城项目【尚品汇】07分布式锁-2 Redisson篇

1 Redisson功能介绍 基于自定义setnx实现的分布式锁存在下面的问题&#xff1a; 重入问题&#xff1a;重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中&#xff0c;可重入锁的意义在于防止死锁&#xff0c;比如HashTable这样的代码中&#xff0c;他的方法都是使用…

将HTML页面中的table表格元素转换为矩形,计算出每个单元格的宽高以及左上角坐标点,输出为json数据

export function huoQuTableElement() {const tableData []; // 存储表格数据的数组let res [];// 获取到包含表格的foreignObject元素const foreignObject document.getElementById(mydctable);if (!foreignObject){return ;}// 获取到表格元素let oldTable foreignObject…

Nativefier : 将网址打包成exe桌面程序

1、需求场景 在日常开发中&#xff0c;需要针对一些网页在一体机上使用&#xff0c;同时在浏览器上也可以使用&#xff0c;这里推荐大家用nativefier&#xff0c;对网址进行打包。以下是nativefier安装命令&#xff1a; npm install nativefier -g 2、使用方法 --arch 系统 …

《混凝土坝监测仪器系列型谱》修订中监测仪器分类方案解读

随着科技的不断进步和监测需求的日益增加&#xff0c;对监测仪器分类方案进行修订已成为必然的趋势。本文旨在探讨《混凝土坝监测仪器系列型谱》中对现有仪器分类方式的修订&#xff0c;以及监测仪器选用的相关内容。希望对大家中有所帮助&#xff1a; 一、取消过时条目&#x…

java中方法引用

目录 方法引用&#xff1a; 引用静态方法 引用成员方法 引用构造方法 使用类名引用成员方法 引用数组的构造方法 练习 方法引用&#xff1a; 把已经有的方法拿过来用&#xff0c;当做函数式接口中抽象方法的方法体 在Java中&#xff0c;方法引用是一种简化Lambda表达式的…

教务管理系统-学员办理体系介绍

随着时代的快速开展&#xff0c;教育方面也没落下&#xff0c;不仅是线下线上都呈现许多训练校园&#xff0c;办理软件也顺势而为的呈现广阔训练校园面前&#xff0c;许多的校园和训练组织也都在运用教务管理系统了。运用教务管理系统里边的学员办理体系可以让相应的办理人员更…

Redis的一致性

一、产生的原因 使用缓存&#xff0c;在进行写操作的时候就会出现不一致的问题。 一致性分为三类&#xff1a;强一致性&#xff0c;弱一致性&#xff0c;最终一致性 二、方案 2.1 延时双删 在更新数据库的操作前后分别进行一次删除缓存的操作&#xff0c;并在更新数据库之后…

《HelloGitHub》第 98 期

兴趣是最好的老师&#xff0c;HelloGitHub 让你对编程感兴趣&#xff01; 简介 HelloGitHub 分享 GitHub 上有趣、入门级的开源项目。 github.com/521xueweihan/HelloGitHub 这里有实战项目、入门教程、黑科技、开源书籍、大厂开源项目等&#xff0c;涵盖多种编程语言 Python、…

容器化部署fastdfs文件存储

目录 一、软件信息 二、构建fastdfs镜像 三、docker 启动fdfs服务 四、k8s部署fdfs服务 1、fdfs部署文件 五、外部服务访问 一、软件信息 fastdfs版本&#xff1a;fastdfs:V5.11 libfastcommon版本: V1.0.36 fastdfs-nginx-module版本&#xff1a;V1.20 nginx版本&…

使用Spring Boot和MybatisPlus的Java CRM客户关系管理系统源码

项目名称&#xff1a;CRM客户关系管理系统 功能模块及描述&#xff1a; 一、待办事项 今日需联系客户&#xff1a;显示当日需跟进的客户列表&#xff0c;支持查询和筛选。 分配给我的线索&#xff1a;管理分配给用户的线索&#xff0c;包括线索列表和查询功能。 分配给我的客…

导弹研究中常用坐标系及坐标系之间的变换

在导弹飞行控制过程中&#xff0c;需要时刻掌握导弹的飞行状态 &#xff08;速度、位置、姿态角等&#xff09;&#xff0c;这就有赖于描述导弹飞行状态的坐标系。除了大地坐标系和地心大地直角坐标系外&#xff0c;导弹常用的坐标系还有很多&#xff0c;合理而恰当地选择参考系…

37【透视】两点透视

1 两点透视比较合适表现物体的结构 用两点透视绘制比较小的、箱子之类的物体 2 一点透视和两点透视的共存关系