Android GNSS原始观测值的含义及伪距计算
Posted 除了心跳都忘掉
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android GNSS原始观测值的含义及伪距计算相关的知识,希望对你有一定的参考价值。
目录
引言
2016年谷歌在android上开放原始观测值的访问接口之后,开发者可以通过获取原始观测值进行自主的定位计算,本文简要介绍定位过程中的一环,即伪距的计算。
本文首先对Android应用框架层和其中的位置服务类进行简要介绍,随后说明开发者如何在Android中获取原始观测值及它们各自的含义,最后介绍如何通过对原始观测值进行简单计算获得伪距观测值。
Android应用框架层
Android系统架构分为五层:从上到下依次是应用层、Java应用框架层、系统Native库和Android运行时环境、硬件抽象层和Linux内核层。
Android应用框架层是用来支持应用层中的程序运行的框架层。简而言之,框架层就是Android系统提供给开发者的轮子,你调用这里面的各种类去完成你应用的搭建。
这一层提供了构建应用程序时可能用到的各种API,如:
Android位置管理器(LocationManager):提供了一系列方法来处理地理位置相关的问题。
我们就是要调用这个位置管理的API去完成原始观测值的获取,当然,如何计算就由我们自己定义了。
如何获取原始观测值
要获取原始观测值必须通过LocationManager类,向LocationManager注册监听器后请求位置更新,就能获得相应的信息。
监听器有很多种,比如:
1.GnssMeasurements.Callback:测量监听器
2.GnssStatus.Callback:卫星状态监听器
3.GnssNavigationMessage.Callback:卫星导航监听器
在这里我们要实现的是伪距的计算,所以注册测量监听器就可以。
当我们通过LocationManager注册实现了GnssMeasurementsEvent.Callback接口的监听器,就可以通过onGnssMeasurementsReceived回调方法来接收原始观测值(实际上是通过参数)。
那么我们如何通过LocationManager注册监听器呢?
首先当然要创建好监听器,这里的监听器就是实现了GnssMeasurementsEvent.Callback接口的类的实例化对象,因为这个对象必然实现了接口中定义的回调方法。
然后通过以下三步注册监听器:
第一步:获取系统位置服务的引用。通过调用当前活动对象的getSystemService方法,传入Context.LOCATION_SERVICE参数,再将结果进行一个类型转换。
第二步:调用第一步获取的位置服务引用的registerGnssMeasurementsCallback方法,方法的名字已经基本说明了它的含义,将监听器对象作为参数传进去。
第三步:请求位置更新。调用第一步获取的位置服务引用的requestLocationUpdates方法,这个方法有四个参数,含义分别为:类容提供者(一般为LocationManager.GPS_PROVIDER)、最小时间间隔、最小位移间隔、位置监听器(这里给一个空实现)。
获取原始观测值的过程及其含义见下方代码和注释
// 使用匿名类来创建一个监听器,不了解匿名类的可以先学习一下
GnssMeasurementsEvent.Callback mGnssMeasurementsListener = new GnssMeasurementsEvent.Callback()
@Override
public void onGnssMeasurementsReceived(GnssMeasurementsEvent eventArgs)
super.onGnssMeasurementsReceived(eventArgs);
// 通过参数的 getClock()方法获取钟对象
GnssClock clock = eventArgs.getClock();
// 通过参数的 getMeasurements()方法获取观测值对象的集合
// 因为接收机只有一部,而观测卫星众多,这就是钟对象只有一个
// 而观测值对象有多个的原因
Collection<GnssMeasurement> measurements = eventArgs.getMeasurements();
// 遍历观测值对象集合,定义行为
for(GnssMeasurement measurement : measurements)
// to do
);
@Override
public void onStatusChanged(int status)
super.onStatusChanged(status);
;
// 获取系统 LocationManager 的一个引用
LocationManager mLocationManager = (LocationManager) mActivity.getSystemService(Context.LOCATION_SERVICE);
// 向 LocationManager 注册我们的测量监听器
mLocationManager.registerGnssMeasurementsCallback(mGnssMeasurementsListener, null);
// 请求位置跟新,所有监听器的回调都必须通过此方法
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 2000, 0, new LocationListener()
@Override
public void onLocationChanged(@NonNull Location location)
);
下面介绍一下与伪距计算相关的两个重要的类
GnssMeasurement类
这个类的准确定义如下,相信大家都能看懂:
A class representing a GNSS satellite measurement, containing raw and computed information.
获取方式:通过eventArgs.getMeasurements()获取GnssMeasurement对象的集合,遍历得到每一个GnssMeasurement对象。
下面的方法都属于GnssMeasurement对象:
方法 | 含义 |
---|---|
getSvid() | 获取卫星id |
getConstellationType() | 获取卫星星座类型 |
hasCarrierFrequencyHz() | |
getCarrierFrequencyHz() | 获取该信号的载波频率 |
getTimeOffsetNanos() | Gets the time offset at which the measurement was taken |
getReceivedSvTimeNanos() | 获取卫星信号发射时间 |
GnssClock类
这个类的准确定义如下,相信大家也能看懂:
- A class containing a GPS clock timestamp.
- It represents a measurement of the GPS receiver’s clock.
获取方式:eventArgs.getClock()获取GnssClock对象
下面的方法都属于GnssClock对象:
方法 | 含义 |
---|---|
getTimeNanos() | 获取本地硬件时钟 |
hasFullBiasNanos() | |
getFullBiasNanos() | 获取本地硬件时钟与GPST的偏差 |
hasBiasNanos() | |
getBiasNanos() | 获取FullBiasNanos的亚纳米级偏差 |
hasLeapSecond() | |
getLeapSecond() | (1) |
Note:
(1)GPST属于原子时,GPST在起点1980年1月6日0h00m00s与UTC时刻相等;UTC也是以原子时的时间频率基准来记录,但为了维持其与UT1的差距保持在0.9s以内,UTC会跳秒。getLeapSecond()获取的就是1980年1月6日0h00m00s以来UTC所跳的秒数。
对时间系统有疑问的话可以看一下这篇文章 时间系统
伪距的计算原理
伪距 = (信号接收时间 - 信号发射时间)* 光速
下面的接收时间和发射时间的计算都以GPS为例,如果要根据其它系统的卫星来计算伪距,参考GNSS各星座时间系统转换
(一)原始观测值计算得到 信号接收时间:
其中,TimeNanos由GnssClock对象调用 getTimeNanos() 方法得到,其它变量的获取方式类似。
另外,还要计算GPS整周纳秒数,GPS整周weekNumber的计算方法如下:
将weekNumber乘以一星期的纳秒数,就得到了GPS整周纳秒数。
(二)原始观测值计算得到 信号发射时间:
发射时间取决于每个信号,所以从GnssMeasurement对象获得。
注意GLONASS的发射时间使用的是日内秒,需要对接收时间以86400取余。
样例代码
我的思路是,自定义一个GnssRawData类,用一个GnssClock和一个GnssMeasurement对象来创建这个RawData对象,然后从中获取伪距、载波、卫星标识等信息。
public class GnssRawData
// 用到的常量
private static final double L1Frequency = 1575.42 * 1E6;
private static final double L2Frequency = 1227.60 * 1E6;
private static final double c_ON_NANO = 299792458E-9;
private static final double WEEK_SECOND = 604800;
private static final double WEEK_NANOSECOND = 604800 * 1E9;
private static final double DAY_NANOSECOND = 86400 * 1E9;
private final GnssMeasurement measurement; // final修饰的成员变量必须在定义时或者在构造器中初始化。
private final GnssClock clock;
private final int prn;
private final int constellationType;
private final double carrierFrequencyHZ;
private double pseudorange;
// 构造函数,传入 GnssClock 和 GnssMeasurement 对象
public GnssRawData(GnssMeasurement measurement, GnssClock clock)
this.measurement = measurement;
this.clock = clock;
this.prn = measurement.getSvid();
this.constellationType = measurement.getConstellationType();
this.carrierFrequencyHZ =
measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : L1Frequency;
this.pseudorange = 0;
calcPseudorange();
// 获取卫星标识
public String getPRN()
Locale locale = Locale.getDefault();
switch (constellationType)
case GnssStatus.CONSTELLATION_BEIDOU:
return "C" + String.format(locale, "%02d", prn);
case GnssStatus.CONSTELLATION_GLONASS:
return "R" + String.format(locale, "%02d", prn);
case GnssStatus.CONSTELLATION_GPS:
return "G" + String.format(locale, "%02d", prn);
case GnssStatus.CONSTELLATION_GALILEO:
return "E" + String.format(locale, "%02d", prn);
case GnssStatus.CONSTELLATION_QZSS:
return "J" + String.format(locale, "%02d", prn);
default:
return "U" + String.format(locale, "%02d", prn);
// 获取信号载波频率,以 MHz 为单位
public double getCarrierFrequencyHZ()
return carrierFrequencyHZ / 1E6;
// 获取伪距,以 m 为单位
public double getPseudorange()
return pseudorange;
private void calcPseudorange()
double TimeNanos = clock.getTimeNanos();
double TimeOffsetNanos = measurement.getTimeOffsetNanos();
double FullBiasNanos = clock.hasFullBiasNanos() ? clock.getFullBiasNanos() : 0;
double BiasNanos = clock.hasBiasNanos() ? clock.getBiasNanos() : 0;
double ReceivedSvTimeNanos = measurement.getReceivedSvTimeNanos();
double LeapSecond = clock.hasLeapSecond() ? clock.getLeapSecond() : 0;
// Arrival Time
double tTxNanos = ReceivedSvTimeNanos;
// Transmission Time
int weekNumber = (int) Math.floor(-(double) (FullBiasNanos) * 1E-9 / WEEK_SECOND);
double tRxNanos = (TimeNanos + TimeOffsetNanos) - (FullBiasNanos + BiasNanos) - weekNumber * WEEK_NANOSECOND;
switch (constellationType)
case GnssStatus.CONSTELLATION_GALILEO:
case GnssStatus.CONSTELLATION_GPS:
break;
case GnssStatus.CONSTELLATION_BEIDOU:
tRxNanos -= 14E9;
break;
case GnssStatus.CONSTELLATION_GLONASS:
tRxNanos = tRxNanos - LeapSecond * 1E9 + 3 * 3600 * 1E9;
tRxNanos = tRxNanos % DAY_NANOSECOND;
break;
default:
tRxNanos = tTxNanos;
pseudorange = (tRxNanos - tTxNanos) * c_ON_NANO;
只需要按第一个代码块接收GnssMeasurementsEvent参数eventArgs,然后遍历获取GnssMeasurement对象,和GnssClock对象传入构造器,就可以从GnssRawData对象中获取所需的信息。注意 GnssClock同一历元是相同的,而GnssMeasurement有多个 ,如果一颗卫星有多频的观测,会有多个GnssMeasurement对象有相同的prn而carrierFrequencyHz不同。
我的工程中此部分的完整代码如下,便不再多做叙述,有不懂的地方可以私信我,也可以自行学习相关知识:
private final GnssMeasurementsEvent.Callback gnssMeasurementsListener = new GnssMeasurementsEvent.Callback()
@Override
public void onGnssMeasurementsReceived(GnssMeasurementsEvent eventArgs)
super.onGnssMeasurementsReceived(eventArgs);
GnssClock clock = eventArgs.getClock();
Collection<GnssMeasurement> measurements = eventArgs.getMeasurements();
List<Mea> mLst = new ArrayList<>();
for (GnssMeasurement measurement : measurements)
GnssRawData data = new GnssRawData(measurement, clock);
mLst.add(new Mea(data.getPRN(), data.getCarrierFrequencyHZ(), data.getPseudorange(),
clock.getTimeNanos(), clock.getFullBiasNanos(), measurement.getReceivedSvTimeNanos()));
Collections.sort(mLst);
StringBuilder builder = new StringBuilder();
Locale locale = Locale.getDefault();
builder.append(String.format(locale, "%5s%20s%20s\\n", "SV", "Carrier(MHz)", "Value"));
for (Mea m : mLst)
builder.append(String.format(locale, "%5s%20.3f%20.3f\\n", m.getPRN(),
m.getCarrier(), m.getPseudorange()));
mActivity.runOnUiThread(new Runnable()
@Override
public void run()
textView.setText(builder.toString());
);
@Override
public void onStatusChanged(int status)
super.onStatusChanged(status);
;
/**
* 将 measurement排序(为了查看是否有多频)
*/
class Mea implements Comparable<Mea>
private final String prn;
private final double carrier;
private final double pseudorange;
private final double TimeNanos;
private final double FullBiasNanos;
private final double ReceivedSvTimeNanos;
public Mea(String prn, double carrier, double pseudorange, double TimeNanos,
double FullBiasNanos, double ReceivedSvTimeNanos)
this.prn = prn;
this.carrier = carrier;
this.pseudorange = pseudorange;
this.TimeNanos = TimeNanos;
this.FullBiasNanos = FullBiasNanos;
this.ReceivedSvTimeNanos = ReceivedSvTimeNanos;
@Override
public int compareTo(Mea o)
return this.prn.compareTo(o.prn);
public String getPRN()
return prn;
public double getCarrier()
return carrier;
public double getPseudorange()
return pseudorange;
public double getTimeNanos()
return TimeNanos;
public double getFullBiasNanos()
return FullBiasNanos;
public double getReceivedSvTimeNanos()
return ReceivedSvTimeNanos;
以上是关于Android GNSS原始观测值的含义及伪距计算的主要内容,如果未能解决你的问题,请参考以下文章
开源PPP软件PRIDE-PPPAR使用记录解算网友发来的GNSS观测文件