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类


这个类的准确定义如下,相信大家也能看懂:

  1. A class containing a GPS clock timestamp.
  2. 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原始观测值的含义及伪距计算的主要内容,如果未能解决你的问题,请参考以下文章

GNSS名词解释?

什么是伪距单点定位

周跳探测方法之MW组合法

开源PPP软件PRIDE-PPPAR使用记录解算网友发来的GNSS观测文件

开源PPP软件PRIDE-PPPAR使用记录解算网友发来的GNSS观测文件

开源PPP软件PRIDE-PPPAR使用记录解算网友发来的GNSS观测文件