Android解析GPS卫星的NMEA数据

2020-03-12  本文已影响0人  浪里_个郎

关于NMEA

GPS模块会上报NMEA协议的字符串,NMEA也分很多类数据,里面包含时间、卫星信息、经纬度等。

Android framework如何使用NMEA

可以参考我的上一篇文章: “android GPS框架” 

总的来说,正常情况下,framework层不参与NMEA数据解析,解析的工作由HAL层完成

但如果你想要外挂GPS模块,或者伪造一些NMEA数据,都可以让在framework自己进行解析,供原生Location相关API使用。具体要在GnssLocationProvider类中拿到NMEA或NMEA解析后的数据,然后调用该类的reportSvStatus注入卫星信息,调用reportLocation注入定位信息。

使用哪类NMEA数据

我自己使用ZDA数据获取时间;使用GSV数据获取卫星信息;使用GGA数据获取定位经纬度信息

解析NMEA数据的坑

1,导航信息回调流程中(GnssLocationProvider.reportLocation)封装成Location类,成员变量mProvider对应了定位监听注册函数LocationManager.requestLocationUpdates中第一个入参String provider,一般都是“gps”,所以构建用于定位的Location类时mProvider也要赋值“gps”

2,GPS状态回调流程中(GnssLocationProvider.reportSvStatus)需要解析得到mSvidWithFlags,这个值在后面会有大量的位移和标志位校验操作,必须设置正确。详见GpsStatus.setStatus中的代码。

我们从NMEA中拿到的svid一般是从1开始的数值,首先我们要将svid左移:

svid << GnssStatus.SVID_SHIFT_WIDTH

然后我们根据GSV所属的发送器标识符来确定卫星类型,如BDGSV就是北斗,我们要根据卫星类型,加上掩码:

//判断卫星类型

            if(sField[0].equalsIgnoreCase("$GLGSV")) {

                mSvidWithFlags[i] += GnssStatus.CONSTELLATION_GLONASS << GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH;

            }else if(sField[0].equalsIgnoreCase("$BDGSV")) {

                mSvidWithFlags[i] += GnssStatus.CONSTELLATION_BEIDOU << GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH;

            }else if(sField[0].equalsIgnoreCase("$GPGSV")) {

                mSvidWithFlags[i] += GnssStatus.CONSTELLATION_GPS << GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH;

            }else {

                mSvidWithFlags[i] += GnssStatus.CONSTELLATION_QZSS << GnssStatus.CONSTELLATION_TYPE_SHIFT_WIDTH;

            }

然后再根据实际情况,加上是否携带某类数据的标识:

mSvidWithFlags[i] += GnssStatus.GNSS_SV_FLAGS_HAS_EPHEMERIS_DATA + GnssStatus.GNSS_SV_FLAGS_HAS_ALMANAC_DATA +   GnssStatus.GNSS_SV_FLAGS_USED_IN_FIX;   

3,NMEA数据是带校验位的,如“$GPGGA,235316.000,2959.9925,S,12000.0090,E,1,06,1.21,62.77,M,0.00,M,,*7B” 其中“*”后面的7B就是$和"*"之间所有字符的异或结果,因为有时候以",12*5A"结尾,有时候又是",*7B",要小心split后最后一位是空数据拿不到,导致数组下标少一位,取数据时容易出错,我们预处理时可以这么搞:

data.replaceAll(",\\*\\w\\w",",0").replaceAll("\\*\\w\\w","").replace("\n","").replace("\r","").split(",");

这样如果是",*7B"会被转成“0”,而",12*5A"转换为“12”

4,要注意经纬度换算!

GGA中的经纬度是ddmm.mmmm和dddmm.mmmm(d代表度,m代表分),实际传给高德等导航软件,需要将分转化为度。比如1234.5678,转换算法为:12+34.5678/60

5,要注意时区

ZDA里的时间字符转成Date是UTC时间,而系统校时所需的Date是要加上时区的。我的做法如下:

//处理ZDA数据

        if (sField[0].contains("ZDA") && sField.length >= 5) {

            //拼接时间格式"yyyy-MM-dd HH:mm:ss"

            StringBuffer sb = new StringBuffer();

            sb.append(sField[4]).append("-").append(sField[3]).append("-").append(sField[2]).append(" ").append(sField[1].substring(0,2)).

                append(":").append(sField[1].substring(2,4)).append(":").append(sField[1].substring(4,6));

            Log.d(TAG, "ZDA : HIK receive time = " + sb.toString());

            SimpleDateFormat sdfUTC = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            sdfUTC.setTimeZone(TimeZone.getTimeZone("UTC"));  // 设置UTC时区。如果不设置,默认为当前时区时间

            try {

                mDate = sdfUTC.parse(sb.toString());//生成与时区无关的Date。但SimpleDateFormat 必须设置正确时区,因为生成Date时如果是别的时区,会转成UTC时间

                Log.d(TAG, "ZDA : HIK receive long time = " + mDate.getTime());

            } catch (ParseException e) {

                e.printStackTrace();

            }

        }

上一篇下一篇

猜你喜欢

热点阅读