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;
        }
    }
  • 9
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 21
    评论
Android 12版本提供了通过GNSS原始观测量API获取GPS原始观测量的功能。你可以使用以下步骤来获取GPS原始观测量: 1. 确保你的设备支持GNSS原始观测量API。你可以在设备的设置中查找“GNSS原始观测量”选项,如果找到了该选项,说明你的设备支持该API。 2. 在你的应用程序中添加以下权限: ``` <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> ``` 3. 在你的应用程序中添加以下依赖项: ``` implementation 'com.android.support:support-v4:28.0.0' implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.google.android.gms:play-services-location:17.0.0' ``` 4. 创建一个LocationCallback对象来处理位置更新: ``` LocationCallback locationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { // 处理位置更新 } }; ``` 5. 创建LocationRequest对象以请求位置更新和GNSS原始观测量: ``` LocationRequest locationRequest = new LocationRequest(); locationRequest.setInterval(1000); locationRequest.setFastestInterval(500); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); locationRequest.setNumUpdates(1); locationRequest.setWaitForAccurateLocation(true); locationRequest.setMinAccuracyMeters(10); locationRequest.setMinTime(5000); locationRequest.setMaxTime(10000); locationRequest.setBearingRequired(false); locationRequest.setSpeedRequired(false); locationRequest.setVerticalAccuracyMeters(10); locationRequest.setHorizontalAccuracyMeters(10); locationRequest.setPowerRequirement(LocationRequest.POWER_HIGH); locationRequest.setLocationPurpose(LocationRequest.PURPOSE_NAVIGATION); locationRequest.setNeedGnssMeasurement(true); ``` 6. 请求位置更新: ``` LocationServices.getFusedLocationProviderClient(context) .requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); ``` 7. 在onLocationResult回调方法中获取GNSS原始观测量: ``` @Override public void onLocationResult(LocationResult locationResult) { if (locationResult == null) { return; } Location location = locationResult.getLastLocation(); if (location != null) { GnssMeasurement measurement = location.getExtras().getParcelable(Location.EXTRA_GNSS_MEASUREMENTS); if (measurement != null) { // 处理GNSS原始观测量 } } } ``` 请注意,获取GNSS原始观测量需要设备具有GNSS芯片,因此不是所有设备都支持此功能。另外,由于该功能涉及到敏感数据,因此需要向用户请求权限。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值