Android 关于定位中的那点事(GPS,GPGGA,NMEA-0183,RTCM)

版权声明:本文为博主原创文章,未经博主允许不得转载,这片海工作室。 https://blog.csdn.net/qq_24536171/article/details/72742908

首先关于定位一些解释

通常在Android端地图相关用的最多的都是第三方的Sdkj进行二次开发,如百度,高德,World Wind ,arcgis等,对于手机自带的GPS芯片和国内的北斗芯片了解的相对较少,GPS在android中已经由android底层驱动封装好了,对于导航定位下面我先说基本的常识:

  • GNSS
  • NMEA协议
  • Rtcm协议
  • GPGGA数据
  • 差分定位

GNSS:

一般指全球导航卫星系统,其实GNSS就是所有导航系统的统称。
GNSS:Global Navigation Satellite System(全球卫星导航系统)
GPS:Global Positioning System(全球定位系统)
GPS是美国的卫星导航系统。
俄罗斯的GLONASS
欧盟的Galileo
中国的北斗。这几大导航系统统称为GNSS。
全球导航卫星系统定位是利用一组卫星的伪距、星历、卫星发射时间等观测量来是的,同时还必须知道用户钟差。全球导航卫星系统是能在地球表面或近地空间的任何地点为用户提供全天候的3维坐标和速度以及时间信息的空基无线电导航定位系统。因此,通俗一点说如果你除了要知道经纬度还想知道高度的话,那么,必须对收到4颗卫星才能准确定位。

NMEA协议

NMEA协议是为了在不同的GPS导航设备中建立统一的RTCM(海事无线电技术委员会)标准,它最初是由美国国家海洋电子协会(NMEA—The NationalMarine Electronics Association)制定的。NMEA协议有0180、0182和0183这3种,0183可以认为是前两种的升级,也是目前使用最为广泛的一种。在实际使用中,如果只是接收GPS的输出.则只需两根信号线 GPS数据输出线和信号地线,可以直接将EIA-422输出通道两条信号线中的一条同计算机的Rs232C输入线相连。
GPS(全球定位系统)接收机与手持机之间的数据交换格式一般都由生产厂商缺省定制,其定义内容普通用户很难知晓,且不同品牌、不同型号的GPS接收机所配置的控制应用程序也因生产厂家的不同而不同。所以,对于通用GPS应用软件,需要一个统一格式的数据标准,以解决与任意一台GPS的接口问题。NMEA-0183数据标准就是解决这类问题的方案之一。NMEA协议是为了在不同的GPS导航设备中建立统一的RTCM(海事无线电技术委员会)标准,它最初是由美国国家海洋电子协会(NMEA—The NationalMarine Electronics Association)制定的。
NMEA协议有0180、0182和0183这3种,0183可以认为是前两种的升级,也是目前使用最为广泛的一种,本文主要在0183下进行梳理

GPGGA

GPGGA,GPS固定数据输出语句($GPGGA),这是一帧GPS定位的主要数据,也是使用最广的数据。
$GPGGA 语句包括17个字段:
语句标识头,世界时间,纬度,纬度半球,经度,经度半球,定位质量指示,使用卫星数量,HDOP-水平精度因子,椭球高,高度单位,大地水准面高度异常差值,高度单位,差分GPS数据期限,差分参考基站标号,校验和结束标记(用回车符和换行符),分别用14个逗号进行分隔。
格式例子如下:
$GPGGA,014434.70,3817.13334637,N,12139.72994196,E,4,07,1.5,6.571,M,8.942,M,0.7,0016*7B
该数据帧的结构及各字段释义如下:
GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>xxGPGGA:起始引导符及语句格式说明(本句为GPS定位数据);
<1> UTC时间,格式为hhmmss.sss;
<2> 纬度,格式为ddmm.mmmm(第一位是零也将传送);
<3> 纬度半球,N或S(北纬或南纬)
<4> 经度,格式为dddmm.mmmm(第一位零也将传送);
<5> 经度半球,E或W(东经或西经)
<6> GPS状态, 0初始化, 1单点定位, 2码差分, 3无效PPS, 4固定解, 5浮点解, 6正在估算 7,人工输入固定值, 8模拟模式, 9WAAS差分
<7> 使用卫星数量,从00到12(第一个零也将传送)
<8> HDOP-水平精度因子,0.5到99.9,一般认为HDOP越小,质量越好。
<9> 椭球高,-9999.9到9999.9米
M 指单位米
<10> 大地水准面高度异常差值,-9999.9到9999.9米M 指单位米
<11> 差分GPS数据期限(RTCM SC-104),最后设立RTCM传送的秒数量,如不是差分定位则为空
<12> 差分参考基站标号,从0000到1023(首位0也将传送)。
1. 语句结束标志符xx 从$开始到*之间的所有ASCII码的异或校验
回车符,结束标记
换行符,结束标记

RTCM

TCM全名国际海运事业无线电技术委员会,是国际标准组织。
NMEA 0183是美国国家海洋电子协会(National Marine Electronics Association )为海用电子设备制定的标准格式。目前业已成了GPS导航设备统一的RTCM(Radio Technical Commission for Maritime services)标准协议。也叫作差分站。

RTCM3.1数据格式及内容

CM3.1协议规范包括应用层、表示层、传输层、数据链路层和物理层。对于编解码最重要的是表示层和传输层。
应用层
描述了RTCM3.1协议中各类差分电文采用单向数据链广播给各类用户,用户如何利用差分电文实现高精度定位、测距以及各类拓展应用,如何提供高精度的定位和导航服务;阐述了普通定位系统与差分定位应用系统之间的精度差别,以及在测距、定位、导航等应用系统中发挥的明显优势。
表示层
表示层规定了差分的具体协议和电文格式,其数据架构包括两个主要方面,即数据字段和消息类型,包括消息、数据要素和数据定义。RTCM3.1中所涉及的实时动态定位消息分为若干个组,每个组又含有不同的子类,描述这些数据的是消息类型。
传输层

传输层定义了发送或接收RTCM3.1文电的帧结构,并详细介绍了差错控制算法(CRC校验算法)。差分电文是以二进制的形式进行传输的,定义该层是为了使RTCM10403.1的数据能够被正确解码。
通过网络传输的差分电文是按电文帧的形式进行,电文帧格式定义在物理层上的传输格式,目的是保证在应用中能被正确的解码,在数据传输过程中,差分电文提供者应把电文打包成一个个独立结构的帧,以使帧的传输最好适应这种传输方法。在传送到应用层之前,数据设置应该将这种帧的结构进行重建。基本的帧结构包含一个固定的前缀,一个电文长度定义,一组电文,和24比特循环冗余码校验(CRC),校验码是用于差错控制和保证每帧信息完整性的有效手段。帧格式的结构下图所示。

文件头 保留 电文长度 可变长度数据电文 CRC
8bits 6bits 10bits 电文字节的整数个数 24bits
11010011 未定义-设置为000000 按字节算的电文长度 0-1023字节 CRC-24Q

电文头是一个固定的8比特序列;接着的6个比特是保留的,设置成0;接着是可变长度电文的长度;接着是变长电文;最后是CRC校验码。如果数据链接需要短电文以保持一个连续的数据流,那么可变长度数据电文应该被设置为0,提供一个长度为48比特的填充电文。24比特的CRC奇偶性提供针对突发性错误和随机性错误的保护。CRC对连续字节的操作开始于文件头,直到可变长度电文域的结尾。24比特(p1,p2,…,p24)的顺序是按照信息比特(m1,m2, …,m8N)的顺序产生的,其中N是构成电文加上文件头和电文长度定义参数的序列的字节总数。

RTCM3.1解码
解码即按照版本定义将各字段数据取出并解出原数据。以1004电文的解码为例介绍解码的思路和过程。
1004是GPS RTK Message(1001-1004)的一种消息类型,它提供了卫星的载波、伪距和信噪比等信息,是观测文件的主要内容。GPS RTK Message在可变长度消息的开始有8个字节的消息头,其内容及字段定义在表4中列出。
1004电文的内容如表5所示,与其他类型信息不同,可变长度消息可能有N条1004电文,每条电文长度为125位,其长度不是字节位数的整数倍。为了提高利用率,每两条1004电文中间没有多余的位,如第一条电文的最后一个字节空余3位,则第二条电文补足,即最后3位是下一条电文开始的3位。这种排列为解码带来不便,因为数据是按字节读取,无法直接设循环解每条电文。注意到电文的排列还是有一定规律,解码的核心是编制一种方法,能够在字节数组中,以任意位为起始,取出所需比特位,这样就可以根据报文结构,在字节数组中获取数据,重构报文内容,然后就可以根据报文条数设置循环,对报文逐条进行相应处理。

差分定位

差分定位也叫差分GPS技术,即将一台GPS接收机安置在基准站上进行观测。根据基准站已知精密坐标,计算出基准站到卫星的距离改正数,并由基准站实时将这一数据发送出去。用户接收机在进行GPS观测的同时,也接收到基准站发出的改正数,并对其定位结果进行改正,从而提高定位精度。
差分定位(Differential positioning),也叫相对定位,是根据两台以上接收机的观测数据来确定观测点之间的相对位置的方法,它既可采用伪距观测量也可采用相位观测量,大地测量或工程测量均应采用相位观测值进行相对定位。
可以简单的理解为在已知坐标的点上安置一台GPS接收机(称为基准站),利用已知坐标和卫星星历计算出观测值的校正值,并通过无线电设备(称数据链)将校正值发送给运动中的GPS接收机(称为流动站),流动站应用接收到的校正值对自己的GPS观测值进行改正,以消除卫星钟差钟差、接收机钟差、大气电离层和对流层折射误差的影响。
这部分很专业,具体可以查资料

在Android中获取GPS中的NMEA-0183协议中的GPGGA数据,再获取经纬度

public class MainActivity extends AppCompatActivity {
    private TextView tvWGS84, tvNmea;
    private LocationListener gpsListener;
    private LocationManager mLocationManager;
    private GeomagneticField gmfield;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //显示wgs84数据
        tvWGS84 = (TextView) findViewById(R.id.tv_rgs84);
        //显示nmea协议中数据
        tvNmea = (TextView) findViewById(R.id.tv_nmea);
        mLocationManager = ((LocationManager) getSystemService(Context.LOCATION_SERVICE));
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        mLocationManager.addNmeaListener(new GpsStatus.NmeaListener() {

            @Override
            public void onNmeaReceived(long timestamp, String nmea) {
                tvNmea.invalidate();
                //此处以GPGGA为例
                //$GPGGA,232427.000,3751.1956,N,11231.1494,E,1,6,1.20,824.4,M,-23.0,M,,*7E
                if (nmea.contains("GPGGA")) {
                    String info[] = nmea.split(",");
                    //GPGGA中altitude是MSL altitude(平均海平面)
                    tvWGS84.setText(nmea);
                    Log.i("GPGGA","获取的GPGGA数据是:"+nmea);
                    Log.i("GPGGA","获取的GPGGA数据length:"+info.length);
                    Log.i("GPGGA","GPS定位数据:"+info[0]);
                    Log.i("GPGGA","UTC时间:"+info[1]);

                    Log.i("GPGGA","纬度:"+info[2]);
                    Log.i("GPGGA","纬度半球:"+info[3]);
                    Log.i("GPGGA","经度:"+info[4]);
                    Log.i("GPGGA","经度半球:"+info[5]);
                    Log.i("GPGGA","GPS状态:"+info[6]);
                    Log.i("GPGGA","使用卫星数量:"+info[7]);
                    Log.i("GPGGA","HDOP-水平精度因子:"+info[8]);
                    Log.i("GPGGA","椭球高:"+info[9]);
                    Log.i("GPGGA","大地水准面高度异常差值:"+info[10]);
                    Log.i("GPGGA","差分GPS数据期限:"+info[11]);
                    Log.i("GPGGA","差分参考基站标号:"+info[12]);
                    Log.i("GPGGA","ASCII码的异或校验:"+info[info.length-1]);
                    //UTC + (+0800) = 本地(北京)时间
                    int a= Integer.parseInt(info[1].substring(0,2));
                    a+=8;
                    a%=24;
                    String time="";
                    String time1="";
                    if(a<10){
                        time="0"+a+info[1].substring(2,info[1].length()-1);
                    }
                    else{
                        time=a+info[1].substring(2,info[1].length()-1);
                    }
                    time1=time.substring(0,2)+":"+time.substring(2,4)+":"+time.substring(4,6);
                    tvNmea.setText("获取的GPGGA数据length:"+info.length+"\nUTC时间:"+info[1]+"\n北京时间: "+time1
                    +"\n纬度:"+info[2]+"\n纬度半球:"+info[3]+"\n经度:"+info[4]+"\n经度半球:"+info[5]
                    +"\nGPS状态:"+info[6]+"\n使用卫星数量:"+info[7]+"\nHDOP-水平精度因子:"+info[8]+"\n椭球高:"+info[9]
                    +"\n大地水准面高度异常差值:"+info[10]+"\n差分GPS数据期限:"+info[11]+"\n差分参考基站标号:"+info[12]
                    +"\nASCII码的异或校验:"+info[info.length-1]);
                }
            }
        });
        gpsListener = new MyLocationListner();
    }

    private class MyLocationListner implements LocationListener {

        @RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
        @Override
        public void onLocationChanged(Location location) {
            tvWGS84.invalidate();
            tvNmea.invalidate();
            Double longitude = location.getLongitude();
            float accuracy = location.getAccuracy();
            Double latitude = location.getLatitude();
            Double altitude = location.getAltitude();// WGS84
            float bearing = location.getBearing();
            gmfield = new GeomagneticField((float) location.getLatitude(),
                    (float) location.getLongitude(), (float) location.getAltitude(),
                    System.currentTimeMillis());
            tvWGS84.setText("Altitude=" + altitude + "\nLongitude=" + longitude + "\nLatitude="
                    + latitude + "\nDeclination=" + gmfield.getDeclination() + "\nBearing="
                    + bearing + "\nAccuracy=" + accuracy);
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }

        @Override
        public void onProviderEnabled(String provider) {

        }

        @Override
        public void onProviderDisabled(String provider) {

        }

    }

    @Override
    protected void onPause() {
        super.onPause();
        //退出Activity后不再定位
        mLocationManager.removeUpdates(gpsListener);
    }

    @Override
    protected void onResume() {
        super.onResume();
        //判断gps是否可用
        if (mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
            Toast.makeText(this, "gps可用", Toast.LENGTH_LONG).show();
            //开始定位
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)





                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return;
            }
            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, gpsListener);
        }else{
            Toast.makeText(this, "请打开gps或者选择gps模式为准确度高", Toast.LENGTH_LONG).show();
            //前往设置GPS页面
            startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
        }
    }
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页