android中几种定位方式详解

目录

前言:

1、GPS定位

2、NETWORK定位

3、AGPS定位

4、基站定位

5、WIFI定位

6、混合定位


目前,移动端大致通过三种方式来进行设备定位:GPS、基站、wifi。本文就详细的讲解一下这几种定位方式和实现方法。

前言:

android中我们一般使用LocationManager来获取位置信息,这里面有四中provider:

public static final String NETWORK_PROVIDER = "network";
public static final String GPS_PROVIDER = "gps";
public static final String PASSIVE_PROVIDER = "passive";
public static final String FUSED_PROVIDER = "fused";

区别如下:

(1)GPS_PROVIDER:通过 GPS 来获取地理位置的经纬度信息; 优点:获取地理位置信息精确度高; 缺点:只能在户外使用,获取经纬度信息耗时,耗电;

(2)NETWORK_PROVIDER:通过移动网络的基站或者 Wi-Fi 来获取地理位置; 优点:只要有网络,就可以快速定位,室内室外都可; 缺点:精确度不高;

(3)PASSIVE_PROVIDER:被动接收更新地理位置信息,而不用自己请求地理位置信息。

PASSIVE_PROVIDER 返回的位置是通过其他 providers 产生的,可以查询 getProvider() 方法决定位置更新的由来,需要 ACCESS_FINE_LOCATION 权限,但是如果未启用 GPS,则此 provider 可能只返回粗略位置匹配;

(4)FUSED_PROVIDER:这个本来已经被废弃了,但是目前在Android12(即android api 31)上又重新使用了起来,但是它依赖GMS,所以国内暂时无法使用。

我们通常使用gps和network这两种方式。

但是我们还可以通过其它方式获取位置信息,这篇文章就详细的讲解一下在android中几种获取定位的方式。

1、GPS定位

这个用的最普遍,可以获取上次定位,也可以监听变化,代码如下: 

需要权限

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

GPS定位

var locManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager
var loc = locManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if(loc != null){
    Log.e("gpslocation", loc.toString())
    toast(loc.toString())
}
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0F, object : LocationListener{
    override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {

    }

    override fun onProviderEnabled(provider: String?) {
    }

    override fun onProviderDisabled(provider: String?) {
    }

    override fun onLocationChanged(location: Location?) {
        Log.e("gpslocation", location.toString())
        toast(location.toString())
    }
})

2、NETWORK定位

与gps定位代码基本一致,只不过将provider改成LocationManager.NETWORK_PROVIDER

3、AGPS定位

实际上是将上面两种定位结合起来,具体原理如下:

AGPS手机首先将本身的基站地址通过网络传输到位置服务器;

位置服务器根据该手机的大概位置传输与该位置相关的GPS辅助信息(包含GPS的星历和方位俯仰角等)到手机;

该手机的AGPS模块根据辅助信息(以提升GPS信号的第一锁定时间TTFF能力)接收GPS原始信号;

手机在接收到GPS原始信号后解调信号,计算手机到卫星的伪距(伪距为受各种GPS误差影响的距离),并将有关信息通过网络传输到位置服务器;

位置服务器根据传来的GPS伪距信息和来自其他定位设备(如差分GPS基准站等)的辅助信息完成对GPS信息的处理,并估算该手机的位置;

位置服务器将该手机的位置通过网络传输到定位网关或应用平台。

我的理解就是通过网络位置和位置服务器判断出最佳的卫星,减少了获取卫星信号的时间。因为网络位置获取很快,所以可以减少整体的定位时间。

AGPS并不是一种定位方式,只是一种优化方案,代码与GPS一样,只不过在设置中将定位模式设成AGPS。


上面是android自带的定位方式,我们还可以获取一些原始信息(比如基站信息、wifi信息),通过公开的接口来获取位置信息。下面几种方式就是使用原始信息通过API来获取位置信息。


4、基站定位

通过TelephonyManager我们可以拿到基站信息,再通过相关的api接口就能得到经纬度,但是基站定位精度很差。


基站信息包含如下:

MCC,Mobile Country Code,移动国家代码(中国的为460);
MNC,Mobile Network Code,移动网络号码(中国移动为00,中国联通为01);
LAC,Location Area Code,位置区域码;
CID,Cell Identity,基站编号,是个16位的数据(范围是0到65535)。


拿到这个信息后,我们可以通过一些公开的api服务拿到经纬度,如下:

http://www.google.com/loc/json   google的,post请求,好像停用了

接口说明文档 | LBS数据仓库  目前可用,是免费的

实例:

http://api.cellocation.com:82/cell/?mcc=460&mnc=1&lac=4301&ci=20986&output=json

返回:

{"errcode":0, "lat":"40.00598145", "lon":"116.48539734", "radius":"937", "address":"北京市朝阳区来广营地区东湖渠;溪阳东路与屏翠东路路口东70米"}


接口参数:

名称类型必填说明
mccintmcc国家代码:中国代码 460
mncintmnc网络类型:0移动,1联通(电信对应sid),十进制
lacintlac(电信对应nid),十进制
ciintcellid(电信对应bid),十进制
coordstring坐标类型(wgs84/gcj02/bd09),默认wgs84
outputstring返回格式(csv/json/xml),默认csv

另外还有很多提供这种接口和数据的平台,自己搜索即可

代码如下:

需要权限

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

 基站定位,这里只实现了GSM的,CDMA的有些许不同

val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
if(telManager.cellLocation is GsmCellLocation) {
    val cellLoc = telManager.cellLocation as GsmCellLocation
    if (cellLoc != null) {
        val operator = telManager.networkOperator
        val mcc = operator.substring(0, 3).toInt()
        val mnc = operator.substring(3).toInt()
        val cid = cellLoc.cid
        val lac = cellLoc.lac

        ...

        doAsync {
            val result = URL(sb.toString()).readText()
            Log.e("tellocation", result)
        }
    }
}

5、WIFI定位

wifi定位是通过WifiManager拿到wifi的信息,主要是wifi的BSSID(即mac地址)。然后通过一些api查询经纬度,比如接口说明文档 | LBS数据仓库

实例:

http://api.cellocation.com:82/wifi/?mac=00:87:36:05:5d:ea&output=json

返回:

{"errcode":0, "lat":"39.950008", "lon":"116.230049", "radius":"222", "address":"北京市海淀区四季青镇益园文创基地c区9号楼;南平庄中路与西平庄路路口西北561米"}

接口参数:

名称类型必填说明
macstringWIFI热点的MAC地址(BSSID)
coordstring坐标类型(wgs84/gcj02/bd09),默认wgs84
outputstring返回格式(csv/json/xml),默认csv

代码如下:

需要权限

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

 wifi定位

val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
if(wifiManager.isWifiEnabled) {
    var mac = wifiManager.connectionInfo.bssid
    if(TextUtils.isEmpty(mac)) {
        /**
         * 当未链接wifi时,可以使用扫描到的wifi列表中找一个信号强度最好的。这里没进行比较,直接使用第一个了
         * ScanReuslt有三个字段比较重要:SSID是wifi名称,BSSID是wifi的mac,level则是信号强度(负数)
         * 注意结果中同一个SSID可能会有多个,如果需要链接wifi可以通过信号强度过滤出最好的来链接
         */
        val scanlist = wifiManager.scanResults
        for(info in scanlist){
            Log.e("wifiinfo", info.toString())
        }
        if(scanlist.size > 0) {
            mac = wifiManager.scanResults[0].BSSID
        }
    }
    if(!TextUtils.isEmpty(mac)) {
        ...
        doAsync {
            val result = URL(sb.toString()).readText()
            Log.e("wifilocation", result)
        }
    }
}

6、混合定位

混合定位就是获取附近的wifi列表信息(包括信号强度)和附近的基站列表信息(包括信号强度),通过一些api获取经纬度。

这种方式相对于单一的基站和wifi定位要更精确一些。

获取附近的wifi列表在WIFI定位已经提到过了,通过WifiManager的getScanResults函数获取扫描到的wifi列表,其中level就是信号强度,可能需要做一下去重。

获取附近的基站列表则有些问题。

官方提供了一个方式,通过TelephonyManager的getNeighboringCellInfo函数获得,其中mRssi就是信号强度。

需要权限

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

获取附近基站信息

val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val neighborCells = telManager.neighboringCellInfo
for(cell in neighborCells){
    //这里将rssi转化为dBm
    val level = -131 + 2 * cell.rssi
    Log.e("neighboringCellInfo", "cid:${cell.cid} lac:${cell.lac} rssi:$level")
}

但是实际使用时发现cid和lac都是-1。

检查NeighboringCellInfo的构造方法,如下:

 public NeighboringCellInfo(int rssi, String location, int radioType) {
        // set default value
        mRssi = rssi;
        mNetworkType = NETWORK_TYPE_UNKNOWN;
        mPsc = UNKNOWN_CID;
        mLac = UNKNOWN_CID;
        mCid = UNKNOWN_CID;


        // pad location string with leading "0"
        int l = location.length();
        if (l > 8) return;
        if (l < 8) {
            for (int i = 0; i < (8-l); i++) {
                location = "0" + location;
            }
        }
        // TODO - handle LTE and eHRPD (or find they can't be supported)
        try {// set LAC/CID or PSC based on radioType
            switch (radioType) {
                case NETWORK_TYPE_GPRS:
                case NETWORK_TYPE_EDGE:
                    mNetworkType = radioType;
                    // check if 0xFFFFFFFF for UNKNOWN_CID
                    if (!location.equalsIgnoreCase("FFFFFFFF")) {
                        mCid = Integer.parseInt(location.substring(4), 16);
                        mLac = Integer.parseInt(location.substring(0, 4), 16);
                    }
                    break;
                case NETWORK_TYPE_UMTS:
                case NETWORK_TYPE_HSDPA:
                case NETWORK_TYPE_HSUPA:
                case NETWORK_TYPE_HSPA:
                    mNetworkType = radioType;
                    mPsc = Integer.parseInt(location, 16);
                    break;
            }
        } catch (NumberFormatException e) {
            // parsing location error
            mPsc = UNKNOWN_CID;
            mLac = UNKNOWN_CID;
            mCid = UNKNOWN_CID;
            mNetworkType = NETWORK_TYPE_UNKNOWN;
        }
    }

可以看到只对GPRS和EDGE网络进行了处理,而3G、4G网络都是UNKNOWN_CID,即-1。说明这种方法不支持,已经过时。

官方还有另外一个方式,通过TelephonyManager的getAllCellInfo函数获得。这个函数要求minsdkverison必须在17及以上

需要权限

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

获取基站信息,这里只处理了LTE网络的

val telManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
val cells = telManager.allCellInfo
for(cell in cells){
    if(cell is CellInfoLte) {
        Log.e("celllist", "cid:${cell.cellIdentity.ci} lac:${cell.cellIdentity.tac} dbm:${cell.cellSignalStrength.dbm} isRegistered:${cell.isRegistered}")
    }
}

得到的信息如下:

E/celllist: cid:3912981 lac:4154 dbm:-87 isRegistered:true

E/celllist: cid:2147483647 lac:2147483647 dbm:-101 isRegistered:false

E/celllist: cid:2147483647 lac:2147483647 dbm:-102 isRegistered:false

E/celllist: cid:2147483647 lac:2147483647 dbm:-108 isRegistered:false

E/celllist: cid:2147483647 lac:2147483647 dbm:-101 isRegistered:false

其中第一个是我们正在使用的基站,可以看到正常的返回了信息,而其余的则返回默认的信息(Integer.MAX_VALUE)

说明这个方法也只能拿到当前使用的基站信息。而且据网上的说法,当使用2G网络,getAllCellInfo得到的是NULL。

这样目前没有更好的方式获取多个基站信息了。

当我们拿到附近的基站信息和wifi信息,可以通过http://www.cellocation.com/interfac/ 提供的混合定位接口查询位置信息,与上面类似,这里不细说了。

总结

一般情况下,我们使用系统提供的LocationManager即可获取位置信息,方便简单。如果我们有自己的基站或wifi信息库,也可以获取相关源信息通过接口来实现个性化服务。

源码

关注公众号:BennuCTech,发送“多定位”获取源码

 

  • 6
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
MongoDB 聚合框架提供了一种数据处理方式,它能够对集合的文档进行分组、筛选、投影、排序、限制以及多表关联等操作,从而生成新的文档集合。下面是几种 MongoDB 聚合查询方式详解: 1. $match 查询 $match 查询通过筛选文档的字段来过滤数据,类似于 SQL 的 WHERE 子句。可以使用各种比较运算符、逻辑运算符和正则表达式等条件来实现高级查询。例如,以下代码将返回所有 age 大于等于 18 的文档: ``` db.collection.aggregate([ { $match : { age : { $gte : 18 } } } ]) ``` 2. $group 查询 $group 查询通过将文档分组来聚合数据。可以使用 $sum、$avg、$min、$max 等聚合运算符来计算每个分组的结果。例如,以下代码将返回每个国家的总人口数: ``` db.collection.aggregate([ { $group : { _id : "$country", population: { $sum : "$population" } } } ]) ``` 3. $project 查询 $project 查询用于投影文档的字段,类似于 SQL 的 SELECT 子句。可以使用 $addFields、$subtract、$multiply、$divide 等运算符来进行计算或添加新的字段。例如,以下代码将返回包含 name 和 age 字段的文档: ``` db.collection.aggregate([ { $project : { name : 1, age : 1 } } ]) ``` 4. $sort 查询 $sort 查询用于对文档进行排序,类似于 SQL 的 ORDER BY 子句。可以使用 1 或 -1 来指定升序或降序排列。例如,以下代码将按 age 字段降序排列文档: ``` db.collection.aggregate([ { $sort : { age : -1 } } ]) ``` 以上是 MongoDB 聚合查询的几种方式,它们可以组合使用来实现更复杂的查询。在 Java ,可以使用 MongoDB 的 Java 驱动程序来进行聚合查询。具体的实现方式可以参考 MongoDB 的官方文档。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BennuCTech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值