Android GPS定位

柯琛
2023-12-01

目录

1、GNSS 介绍

2、NMEA协议

3、Android 获取GPS定位信息

4、GPS 冷、温、热启动

定位开发常用链接


1、GNSS 介绍

GNSS(Global Navigation Satellite System)全球导航卫星系统,是能在地球表面或近地空间的任何地点为用户提供全天候的三维坐标和速度以及时间信息的空基无线电导航定位系统。定位是利用几组卫星的伪距星历、卫星发射时间及用户钟差等来进行确定。

目前包含四大全球系统:GPS(美) GLONASS(俄) GALILEO(欧) BDS(中)

2、NMEA协议

NMEA (National Marine Electronics Association )美国国家海洋电子协会,现在是GPS导航设备统一的RTCM(国际海运事业无线电技术委员会)标准协议。NMEA协议有0180、0182和0183这3种,0183可以认为是前两种的升级,也是目前使用最为广泛的一种,大部分GNSS接收机、软件都是基于此协议的,Android 也是使用此协议,以下都是基于0183协议说明。

常用NMEA-0183语句的字段定义如下,也是后续GNSS log中常见的重要的部分。下文 GA/GL/GP/BD 分别表示上述 GPS/BDS 等四大系统缩写,Android 相关log日志中也是如此格式

(GA/GL/GP/BD)GSA
字段0$BDGSA,语句ID,表明该语句为BDS DOP(精度因子:Dilution of precision) and Active Satellites(GSA)当前卫星信息
字段1定位模式,A=自动选择,M=手动选择
字段2定位类型,1=未定位,2=2D定位,3=3D定位
字段3PRN码(伪随机噪声码),第1信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
...2-11信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段14PRN码(伪随机噪声码),第12信道正在使用的卫星PRN码编号(00)(前导位数不足则补0)
字段15PDOP综合位置精度因子(0.5 - 99.9)
字段16HDOP水平精度因子(0.5 - 99.9)
字段17VDOP垂直精度因子(0.5 - 99.9)
字段18校验值($与*之间的数异或后的值)
GNGGA
字段0$GNGGA,语句ID,表明该语句为Global Navigation Satellite System Fix Data(GGA)定位信息
字段1UTC 时间,hhmmss.sss,时分秒格式
字段2纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段3纬度N(北纬)或S(南纬)
字段4经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段5经度E(东经)或W(西经)

字段6

GPS状态,0=不可用(FIX NOT valid),1=单点定位(GPS FIX),2=差分定位(DGPS),3=无效PPS,4=实时差分定位(RTK FIX),5=RTK FLOAT,6=正在估算

字段7

正在使用的卫星数量(00 - 12)(前导位数不足则补0)

字段8

HDOP水平精度因子(0.5 - 99.9)

字段9

海拔高度(-9999.9 - 99999.9)

字段10

高度单位:M(米)

字段11

地球椭球面相对大地水准面的高度 WGS84水准面划分

字段12

WGS84水准面划分单位:M(米)

字段13

差分时间(从接收到差分信号开始的秒数,如果不是差分定位将为空)

字段14

差分站ID号0000 - 1023(前导位数不足则补0,如果不是差分定位将为空)

字段15校验值($与*之间的数异或后的值)
(GA/GL/GP/BD)GSV
字段0$(GA/GL/GP/BD)GSV,语句ID,表明该语句为GPS Satellites in View(GSV)可见卫星信息
字段1本次GSV语句的总数目(一般1 - 5)
字段2本条GSV语句是本次GSV语句的第几条,每条语句中应包含四个卫星信息(PRN、仰角、方位角、信噪比),不足的为空
字段3当前可见卫星总数(00 - 12)(前导位数不足则补0)
字段4PRN 码(伪随机噪声码)(01 - 32)(前导位数不足则补0)
字段5卫星仰角(00 - 90)度(前导位数不足则补0)

字段6

卫星方位角(00 - 359)度(前导位数不足则补0)

字段7

信噪比(00-99)dbHz

...

重复 字段4 - 字段7 卫星信息内容
字段20校验值($与*之间的数异或后的值)

GNRMC
字段0$GNRMC,语句ID,表明该语句为Recommended Minimum Specific GNSS Data 推荐最小定位信息
字段1UTC时间,hhmmss.sss格式
字段2状态,A=定位,V=未定位
字段3纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段4纬度N(北纬)或S(南纬)
字段5经度dddmm.mmmm,度分格式(前导位数不足则补0)

字段6

经度E(东经)或W(西经)

字段7

速度,节,Knots

字段8

方位角,度

字段9

UTC日期,DDMMYY格式

字段10

磁偏角,(000 - 180)度(前导位数不足则补0)

字段11

磁偏角方向,E=东W=西
字段12模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)
字段13校验值($与*之间的数异或后的值)

GNVTG
字段0$GNVTG,语句ID,表明该语句为Track Made Good and Ground Speed(VTG)地面速度信息
字段1运动角度,000 - 359,(前导位数不足则补0)
字段2T=真北参照系
字段3运动角度,000 - 359,(前导位数不足则补0)
字段4M=磁北参照系
字段5水平运动速度(0.00)(前导位数不足则补0)

字段6

N=节,Knots 1N=1.852Km/h

字段7

水平运动速度(0.00)(前导位数不足则补0)

字段8

K=公里/时,km/h
字段9模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)
字段10校验值($与*之间的数异或后的值)
GNGLL
字段0$GNGLL,语句ID,表明该语句为Geographic Position(GLL)地理定位信息
字段1纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段2纬度N(北纬)或S(南纬)
字段3经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段4经度E(东经)或W(西经)
字段5UTC时间,hhmmss.sss格式

字段6

状态,A=定位,V=未定位

字段7

校验值($与*之间的数异或后的值)

3、Android 获取GPS定位信息

1、使用GPS定位需要申请以下两个权限

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

 2、获取位置信息,需要注册 LocationListener ,通过回调函数 onLocationChanged(Location location) 更新实时经纬度信息

        // 设置监听器
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, locationListener);

3、获取GPS原始数据 Nmea 语句的需要 mLocationManager.addNmeaListener 添加 Nmea 监听,通过相应回调函数获取数据,这个数据中包含原始的 Nmea 数据和平台商自定义的数据,可根据需要自行过滤

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            mLocationManager.addNmeaListener(mNmealocationListener, null);
            mLogTextView.setText("mLocationManager addNmeaListener");
        } else {
            mLocationManager.addNmeaListener(new GpsStatus.NmeaListener() {
                @Override
                public void onNmeaReceived(long timestamp, String nmea) {
                    // 低版本使用接口
                }
            });
        }

完整Demo如下 

public class TestActivity extends Activity {
    private static final String TAG = "TestActivity";

    private final static String[] MULTI_PERMISSIONS = new String[]{
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION};

    private LocationManager mLocationManager;
    private TextView mLocTextView, mLocNmeaTextView, mLogTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.gps_layout);

        initViews();
        initData();
    }

    /**
     * 初始化View组件信息及相关点击事件
     */
    private void initViews() {
        mLocTextView = (TextView) findViewById(R.id.text_location);
        mLocNmeaTextView = (TextView) findViewById(R.id.text_nmea_location);
        mLogTextView = (TextView) findViewById(R.id.text_log);
    }

    /**
     * 初始化定位相关数据
     */
    private void initData() {
        if (mLocationManager == null) {
            //获取定位服务
            mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        }
        //判断是否开启GPS定位功能
        boolean isGpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        Log.i(TAG, "isGpsEnabled:" + isGpsEnabled);

        if (!isGpsEnabled) {
            //前往设置GPS页面
            startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
        } else {
            initLocationManager();
        }
    }

    private void initLocationManager() {
        mLogTextView.setText("init LocationManager");
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission
                (this, Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, MULTI_PERMISSIONS, 100);
            return;
        }
        if (mLocationManager == null) {
            //获取定位服务
            mLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            mLocationManager.addNmeaListener(mNmealocationListener, null);
            mLogTextView.setText("mLocationManager addNmeaListener");
        } else {
            mLocationManager.addNmeaListener(new GpsStatus.NmeaListener() {
                @Override
                public void onNmeaReceived(long timestamp, String nmea) {
                    // 低版本使用接口
                }
            });
        }
        // 设置监听器
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, locationListener);
        // 通过GPS获取位置
        Location location = mLocationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        if (location != null) {
            updateUI(location);
        }
    }

    @SuppressLint("NewApi")
    private OnNmeaMessageListener mNmealocationListener = new OnNmeaMessageListener() {

        @Override
        public void onNmeaMessage(String message, long timestamp) {
            //GNGGA,071249.988,3209.5304,N,11841.5127,E,0,0,,72.0,M,4.9,M,,*53
            if (message.contains("GNGGA")) {
                String info[] = message.split(",");
                //GPGGA中altitude是MSL altitude(平均海平面)
                mLocNmeaTextView.setText(message);
                //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);
                mLocNmeaTextView.setText("\nUTC时间:" + info[1] + "\n北京时间: " + time1
                        + "\n纬度:" + info[3] + info[2] + "\n经度:" + info[5] + info[4]
                        + "\nGPS状态:" + info[6] + "\n正在使用的卫星数量:" + info[7]
                        + "\nHDOP水平精度因子:" + info[8] + "\n海拔高度:" + info[9] + info[10]
                        + "\n水准面高度:" + info[11] + info[12]
                        + "\n差分时间:" + info[13] + "\n差分站ID*异或校验值" + info[14]);
            }
        }
    };

    private LocationListener locationListener = new LocationListener() {
        /**
         * 坐标发生变化
         * @param location
         */
        @Override
        public void onLocationChanged(Location location) {
            updateUI(location);
        }

        /**
         * GPS状态发生变化
         * @param provider
         * @param status
         * @param extras
         */
        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            switch (status) {
                case LocationProvider.AVAILABLE:
                    mLogTextView.setText("当前GPS处于活动");
                    break;
                case LocationProvider.OUT_OF_SERVICE:
                    mLogTextView.setText("当前GPS超出服务区");
                    break;
                case LocationProvider.TEMPORARILY_UNAVAILABLE:
                    mLogTextView.setText("当前GPS停止活动");
                    break;
            }
        }

        /**
         * GPS开启
         * @param provider
         */
        @Override
        public void onProviderEnabled(String provider) {
            mLogTextView.setText("GPS开启");
            initLocationManager();
        }

        /**
         * GPS关闭
         * @param provider
         */
        @Override
        public void onProviderDisabled(String provider) {
            mLogTextView.setText("GPS关闭");
            //关闭定位请求
            if (mLocationManager != null) {
                mLocationManager.removeUpdates(locationListener);
            }
        }
    };

    private void updateUI(Location location) {
        double longitude = location.getLongitude();
        double latitude = location.getLatitude();
        mLocTextView.setText("经度:" + longitude + "\n纬度:" + latitude);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        initLocationManager();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //关闭定位请求
        if (mLocationManager != null) {
            mLocationManager.removeUpdates(locationListener);
        }
    }
}

4、GPS 冷、温、热启动

热启动:GPS接收机保存有其最后计算的可视卫星的位置、almanac(历书)和UTC时间,在重启以后,GPS接收机以保存的上述内容为基础获取和计算当前卫星的最新位置。

暖启动:GPS接收机保存有最后计算的卫星的位置、历书和UTC时间,但保存的内容不是当前可视卫星的数据。GPS接收机重启以后尝试去获得当前卫星和信号并计算其新位置。接收机基于其最后的位置和历书得以大概知道当前天空中的可视卫星。 

冷启动:GPS接收机清空了所有历史信息并重启,然后它尝试定位并锁定卫星,由于没有先前信息,这将花去很长的时间。GPS接收机采用类似于轮询的方法,从所有的卫星中锁定信号,这将比事前知道该搜索哪些卫星要慢不少。这一类重新获取锁定要花最长的时间。

简单理解如下:

冷启动:以下几种情况开机均属冷启动。初次使用时; 电池 耗尽导致星历信息丢失时;关机状态下将接收机移动1000公里以上距离。

温启动:距离上次定位的时间超过两个小时的启动。

热启动:距离上次定位的时间小于两个小时的启动。

系统 LocationManager.java 也提供了启动方式的 API ,通过 sendExtraCommand 传递不同参数来设置 GPS 启动。

    //冷启动
    private void sendColdCommand() {
        LocationManager locationManager = (LocationManager) MainActivity.this.getSystemService(Context.LOCATION_SERVICE);
        locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "force_xtra_injection", null);
        locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "force_time_injection", null);
        boolean b1 = locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "delete_aiding_data", null);
    }

    //温启动
    private void sendWarmCommand() {
        LocationManager locationManager = (LocationManager) MainActivity.this.getSystemService(Context.LOCATION_SERVICE);

        Bundle bundle = new Bundle();
        bundle.putBoolean("ephemeris", true);
        boolean b1 = locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "delete_aiding_data", bundle);
    }

    //热启动
    private void sendHotCommand() {
        LocationManager locationManager = (LocationManager) MainActivity.this.getSystemService(Context.LOCATION_SERVICE);

        Bundle bundle = new Bundle();
        bundle.putBoolean("almanac", true);
        boolean b1 = locationManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "delete_aiding_data", bundle);
    }

支持的 command 指令在 GnssLocationProvider.java 中定义好的,最终通过 native_delete_aiding_data 传递到底层,通过不同的 command 指令去操作 GPS 模块,实际操作内容如下:

冷启动:关闭GPS并清除所有数据,然后打开GPS去定位,耗时最多。

温启动:关掉GPS并清除星历数据,然后打开GPS去定位,耗时30秒左右。

热启动:关掉GPS并清除历书数据,然后打开GPS去定位,耗时3秒左右。

    @Override
    public void onExtraCommand(int uid, int pid, String command, Bundle extras) {

        long identity = Binder.clearCallingIdentity();
        try {
            if ("delete_aiding_data".equals(command)) {
                deleteAidingData(extras);
            } else if ("force_time_injection".equals(command)) {
                requestUtcTime();
            } else if ("force_psds_injection".equals(command)) {
                if (mSupportsPsds) {
                    psdsDownloadRequest();
                }
            } else {
                Log.w(TAG, "sendExtraCommand: unknown command " + command);
            }
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
    }

    private void deleteAidingData(Bundle extras) {
        int flags;

        if (extras == null) {
            flags = GPS_DELETE_ALL;
        } else {
            flags = 0;
            if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS;
            if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC;
            if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION;
            if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME;
            if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO;
            if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC;
            if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH;
            if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR;
            if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER;
            if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
            if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
            if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
            if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
        }

        if (flags != 0) {
            native_delete_aiding_data(flags);
        }
    }

这里 ephemeris almanac 指的就是 GPS 历书(Almanac)和星历(Ephemeris),关于他们的作用和区别:

GPS接收机接收到广播星历(Broadcast Ephemeris)与历书(Almanac)两种导航信息。广播星历包含基本轨道参数及摄动改正量,由其确定的卫星位置精度高,可用于定位计算。历书仅提供基本轨道参数,精度低,可用于接收机快速捕捉卫星和预报。

为了缩短卫星锁定时间,GPS接收机要利用历书、当地位置的时间来预报卫星运行状态。历书与星历都是表示卫星运行的参数。

历书包括全部卫星的大概位置,用于卫星预报;

星历只是当前接收机观测到的卫星的精确位置,用于定位。

***相关介绍文档***

GPS卫星星历与卫星历书的区别

GPS星历与历书

GPS学堂 GPS卫星历书(ALMANAC)的用途及含义

定位开发常用链接

1、百度地图SDK 文档

2、百度地图坐标拾取转换

 类似资料: