1. 程式人生 > >Android 關於定位中的那點事(GPS,GPGGA,NMEA-0183,RTCM)

Android 關於定位中的那點事(GPS,GPGGA,NMEA-0183,RTCM)

首先關於定位一些解釋

通常在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));
        }
    }
}