目前主流定位技术分为3种:GPS定位、基站定位和Wifi定位。
GPS篇
GPS是英文Global Positioning System(全球定位系统)的简称,而其中文简称为“球位系”。GPS是20世纪70年代由美国陆海空三军联合研制的新一代空间卫星导航定位系统 。其主要目的是为陆、海、空三大领域提供实时、 全天候和全球性的导航服务,并用于情报收集、核爆监测和应急通讯等一些军事目的,经过20余年的研究实验,耗资300亿美元,到1994年3月,全球覆盖率高达98%的24颗GPS卫星星座己布设完成。GPS功能必须具备GPS终端、传输网络和监控平台三个要素;这三个要素缺一不可;通过这三个要素,可以提供车辆防盗、反劫、行驶路线监控及呼叫指挥等功能。最初的GPS计划在联合计划局的领导下诞生了,该方案将24颗卫星放置在互成120度的三个轨道上。每个轨道上有8颗卫星,地球上任何一点均能观测到6至9颗卫星。这样,粗码精度可达100m,精码精度为10m。由于预算压缩,GPS计划不得不减少卫星发射数量,改为将18颗卫星分布在互成60度的6个轨道上。然而这一方案使得卫星可靠性得不到保障。1988年又进行了最后一次修改:21颗工作星和所3颗备用星工作在互成30度的6条轨道上。这也是现在GPS卫星使用的工作方式。
在Android系统下面,对GPS的支持还是很好的。废话不多说,直接看看与实现Android定位有关的API吧。这些API都在android.location包下,一共有三个接口和八个类。它们配合使用即可实现定位功能。
三个接口:
GpsStatus.Listener: 这是一个当GPS状态发生改变时,用来接收通知的接口。
GpsStatus.NmeaListener:这是一个用来从GPS里接收Nmea-0183(为海用电子设备制定的标准格式)信息的接口。
LocationListener:位置监听器,用于接收当位置信息发生改变时从LocationManager接收通知的接口。
八个类:
Address:描述地址的类,比如:北京天安门
Criteria:用于描述Location Provider标准的类,标准包括位置精度水平,电量消耗水平,是否获取海拔、方位信息,是否允许接收付费服务。
GeoCoder:用于处理地理位置的编码。
GpsSatellite:和GpsStatus联合使用,用于描述当前GPS卫星的状态。
GpsStatus:和GpsStatus.Listener联合使用,用于描述当前GPS卫星的状态。
Location:用于描述位置信息。
LocationManager:通过此类获取和调用系统位置服务
LocationProvider:用于描述Location Provider的抽象超类,一个LocationProvider应该能够周期性的报告当前设备的位置信息。
这里通过一个代码示例,演示一下如何实现定位。
首先,在AndroidManifest.xml清单文件里需要加入ACCESS_FINE_LOCATION权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
其次,实现代码如下:
package com.veer; import android.app.Activity; import android.content.Context; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.util.Log; import android.widget.TextView; public class LocationGPSActivity extends Activity { private String tag = "LocationGPSActivity"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LocationManager loctionManager; String contextService = Context.LOCATION_SERVICE; // 通过系统服务,取得LocationManager对象 loctionManager = (LocationManager) getSystemService(contextService); // 得到位置提供器,通过位置提供器,得到位置信息,可以指定具体的位置提供器,也可以提供一个标准集合,让系统根据 // 标准匹配最适合的位置提供器,位置信息是由位置提供其提供的。 // a. 通过GPS位置提供器获得位置(指定具体的位置提供器) String provider = LocationManager.GPS_PROVIDER; Location location = loctionManager.getLastKnownLocation(provider); // b. 使用标准集合,让系统自动选择可用的最佳位置提供器,提供位置 // Criteria criteria = new Criteria(); // criteria.setAccuracy(Criteria.ACCURACY_FINE);// 高精度 // criteria.setAltitudeRequired(false);// 不要求海拔 // criteria.setBearingRequired(false);// 不要求方位 // criteria.setCostAllowed(true);// 允许有花费 // criteria.setPowerRequirement(Criteria.POWER_HIGH);//高功耗 // // 从可用的位置提供器中,匹配以上标准的最佳提供器 // String provider = loctionManager.getBestProvider(criteria, true); // // 获得最后一次变化的位置 // Location location = loctionManager.getLastKnownLocation(provider); Log.v(tag, "location===" + location); // 使用新的location更新TextView显示 updateWithNewLocation(location); // 监听位置变化,2秒一次,距离1000米以上 loctionManager.requestLocationUpdates(provider, 2 * 1000, 1000, locationListener); } // 位置监听器 private final LocationListener locationListener = new LocationListener() { @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } // 当位置变化时触发 @Override public void onLocationChanged(Location location) { // 使用新的location更新TextView显示 updateWithNewLocation(location); } }; // 通过改变位置经纬度,程序会自动更新TextView显示的位置信息 private void updateWithNewLocation(Location location) { String latLongString; TextView myLoctionText; myLoctionText = (TextView) findViewById(R.id.myLoctionText); if (location != null) { double lat = location.getLatitude(); double lng = location.getLongitude(); latLongString = "Lat(纬度): " + lat + "\nLong(经度): " + lng; } else { latLongString = "没有获取到经纬度,悲剧啊。"; } myLoctionText.setText("我当前的位置是:\n" + latLongString); } }
基站篇
基站定位一般应用于手机用户,手机基站定位服务又叫做移动位置服务(LBS——Location Based Service),它是通过电信移动运营商的网络(如GSM网)获取移动终端用户的位置信息(经纬度坐标),在电子地图平台的支持下,为用户提供相应服务的一种增值业务,例如目前中国移动动感地带提供的动感位置查询服务等。其大致原理为:移动电话测量不同基站的下行导频信号,得到不同基站下行导频的TOA(Time of Arrival,到达时刻)或TDOA(Time Difference of Arrivalm,到达时间差),根据该测量结果并结合基站的坐标,一般采用三角公式估计算法,就能够计算出移动电话的位置。实际的位置估计算法需要考虑多基站(3个或3个以上)定位的情况,因此算法要复杂很多。一般而言,移动台测量的基站数目越多,测量精度越高,定位性能改善越明显。
实现代码如下:
package com.veer; import java.io.BufferedReader; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONObject; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.telephony.TelephonyManager; import android.telephony.gsm.GsmCellLocation; import android.util.Log; import android.widget.TextView; public class LocationCellActivity extends Activity { private SCell cell = null; private SItude itude = null; private String userLongitude = ""; private String userLatitude = ""; private String tag = "LocationCellActivity"; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); try { cell = getCellInfo(); } catch (Exception e1) { Log.v(tag, "获取基站数据 失败 **************************************"); } try { itude = getItude(cell); Log.v(tag, "itude===" + itude); } catch (Exception e1) { Log.v(tag, "根据基站数据获取经纬度 失败 **************************************"); } /* 获取用户当前位置信息 */ if (itude != null) { userLongitude = itude.longitude; userLatitude = itude.latitude; } else { userLongitude = ""; userLatitude = ""; } Log.v(tag, "赋值后的经度====" + userLongitude); Log.v(tag, "赋值后到的纬度====" + userLatitude); TextView tv = (TextView) findViewById(R.id.info); String info = "经度为:" + userLongitude + "\n" + "纬度为:" + userLatitude; tv.setText(info); } /** 基站信息结构体 */ public class SCell { public int MCC; public int MNC; public int LAC; public int CID; } /** 经纬度信息结构体 */ public class SItude { public String latitude; public String longitude; } /** * 获取基站信息 * * @throws Exception */ private SCell getCellInfo() throws Exception { SCell cell = new SCell(); /** 调用API获取基站信息 */ TelephonyManager mTelNet = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); GsmCellLocation location = (GsmCellLocation) mTelNet.getCellLocation(); if (location == null) throw new Exception("获取基站信息失败"); String operator = mTelNet.getNetworkOperator(); int mcc = Integer.parseInt(operator.substring(0, 3)); int mnc = Integer.parseInt(operator.substring(3)); int cid = location.getCid(); int lac = location.getLac(); /** 将获得的数据放到结构体中 */ cell.MCC = mcc; cell.MNC = mnc; cell.LAC = lac; cell.CID = cid; return cell; } /** * 获取经纬度 * * @throws Exception */ private SItude getItude(SCell cell) throws Exception { SItude itude = new SItude(); /** 采用Android默认的HttpClient */ HttpClient client = new DefaultHttpClient(); /** 采用POST方法 */ HttpPost post = new HttpPost("http://www.google.com/loc/json"); try { /** 构造POST的JSON数据 */ JSONObject holder = new JSONObject(); holder.put("version", "1.1.0"); holder.put("host", "maps.google.com"); holder.put("address_language", "zh_CN"); holder.put("request_address", true); holder.put("radio_type", "gsm"); holder.put("carrier", "HTC"); JSONObject tower = new JSONObject(); tower.put("mobile_country_code", cell.MCC); tower.put("mobile_network_code", cell.MNC); tower.put("cell_id", cell.CID); tower.put("location_area_code", cell.LAC); JSONArray towerarray = new JSONArray(); towerarray.put(tower); holder.put("cell_towers", towerarray); StringEntity query = new StringEntity(holder.toString()); post.setEntity(query); /** 发出POST数据并获取返回数据 */ HttpResponse response = client.execute(post); HttpEntity entity = response.getEntity(); BufferedReader buffReader = new BufferedReader( new InputStreamReader(entity.getContent())); StringBuffer strBuff = new StringBuffer(); String result = null; while ((result = buffReader.readLine()) != null) { strBuff.append(result); } /** 解析返回的JSON数据获得经纬度 */ JSONObject json = new JSONObject(strBuff.toString()); JSONObject subjosn = new JSONObject(json.getString("location")); itude.latitude = subjosn.getString("latitude"); itude.longitude = subjosn.getString("longitude"); Log.v(tag, "刚刚获取到的经度====" + itude.longitude); Log.v(tag, "刚刚获取到的纬度====" + itude.latitude); } catch (Exception e) { throw new Exception("获取经纬度出现错误:" + e.getMessage()); } finally { post.abort(); client = null; } Log.v(tag, "方法返回的经度====" + itude.longitude); Log.v(tag, "方法返回的纬度====" + itude.latitude); return itude; } }
Wifi篇
与手机基站定位方式类似,都需要采集wifi接入点的位置信息。
最早开发这个技术的是Skyhook公司。这个技术的原理是利用下面三条事实:wifi热点(也就是AP,或者无线路由器)越来越多,在城市中更趋向于空间任何一点都能接收到至少一个AP的信号。(在美国,每个点收到3、5个AP信号的情况相当多见。中国也会越来越多的) 热点只要通电,不管它怎么加密的,都一定会向周围发射信号。信号中包含此热点的唯一全球ID。即使距离此热点比较远,无法建立连接,但还是可以侦听到它的存在。 热点一般都是很少变位置的,比较固定。这样,定位端只要侦听一下附近都有哪些热点,检测一下每个热点的信号强弱,然后把这些信息发送给Skyhook的服务器。服务器根据这些信息,查询每个热点在数据库里记录的坐标,进行运算,就能知道客户端的具体位置了,再把坐标告诉客户端。可以想想,只要收到的AP信号越多,定位就会越准。原理就是这么简单。
不过,一次成功的定位还需要两个先决条件:第二,客户端能上网。侦听到的热点的坐标在Skyhook的数据库里有第一条不用说了,不管是wifi还是edge,只要能连上Skyhook的服务器就行。第三条是Skyhook的金矿所在。它怎么知道每个AP的坐标信息的呢?有一种说法是靠网友自己搜集,然后发给Skyhook,Skyhook会付钱。不过官方网站上的说法是开着车满大街转悠,边走边采集AP信号,并用GPS定位,从而就有了坐标信息。
package com.veer; import java.io.IOException; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.ScanResult; import android.net.wifi.WifiManager; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.widget.TextView; public class LocationWifiActivity extends Activity { /** Called when the activity is first created. */ WifiManager mainWifi; WifiReceiver receiverWifi; List<ScanResult> wifiList; TextView textview; StringBuilder sb = new StringBuilder(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); textview = (TextView) findViewById(R.id.textView1); mainWifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); receiverWifi = new WifiReceiver(); registerReceiver(receiverWifi, new IntentFilter( WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); mainWifi.startScan(); } public boolean onKeyUp(int KeyCode, KeyEvent envent) { if (KeyCode == KeyEvent.KEYCODE_0) onDestroy(); else super.onKeyUp(KeyCode, envent); return true; } public void onDestroy() { Log.e("wifi", "onDestroy"); super.onDestroy(); } class WifiReceiver extends BroadcastReceiver { public void onReceive(Context c, Intent intent) { wifiList = mainWifi.getScanResults(); for (int i = 0; i < wifiList.size(); i++) { Log.e("wifi", wifiList.get(i).toString()); } HttpPost httpRequest = new HttpPost( "http://www.google.com/loc/json"); JSONObject holder = new JSONObject(); JSONArray array = new JSONArray(); try { holder.put("version", "1.1.0"); holder.put("host", "maps.google.com"); holder.put("address_language", "zh_CN"); holder.put("request_address", true); for (int i = 0; i < wifiList.size(); i++) { JSONObject current_data = new JSONObject(); current_data.put("mac_address", wifiList.get(i).BSSID); current_data.put("ssid", wifiList.get(i).SSID); current_data.put("signal_strength", wifiList.get(i).level); array.put(current_data); } holder.put("wifi_towers", array); Log.e("wifi", holder.toString()); StringEntity se = new StringEntity(holder.toString()); httpRequest.setEntity(se); HttpResponse resp = new DefaultHttpClient() .execute(httpRequest); if (resp.getStatusLine().getStatusCode() == 200) { /* 取出响应字符串 */ String strResult = EntityUtils.toString(resp.getEntity()); textview.setText(strResult); } } catch (JSONException e) { textview.setText(e.getMessage().toString()); e.printStackTrace(); } catch (ClientProtocolException e) { textview.setText(e.getMessage().toString()); e.printStackTrace(); } catch (IOException e) { textview.setText(e.getMessage().toString()); e.printStackTrace(); } catch (Exception e) { textview.setText(e.getMessage().toString()); e.printStackTrace(); } } } }
另一个封装类:
package com.veer; import java.util.List; import android.content.Context; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; public class WifiAdmin { // 定义WifiManager对象 private WifiManager mWifiManager; // 定义WifiInfo对象 private WifiInfo mWifiInfo; // 扫描出的网络连接列表 private List<ScanResult> mWifiList; // 网络连接列表 private List<WifiConfiguration> mWifiConfiguration; // 定义一个WifiLock WifiLock mWifiLock; // 构造器 public WifiAdmin(Context context) { // 取得WifiManager对象 mWifiManager = (WifiManager) context .getSystemService(Context.WIFI_SERVICE); // 取得WifiInfo对象 mWifiInfo = mWifiManager.getConnectionInfo(); } // 打开WIFI public void OpenWifi() { if (!mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(true); } } // 关闭WIFI public void CloseWifi() { if (!mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(false); } } // 锁定WifiLock public void AcquireWifiLock() { mWifiLock.acquire(); } // 解锁WifiLock public void ReleaseWifiLock() { // 判断时候锁定 if (mWifiLock.isHeld()) { mWifiLock.acquire(); } } // 创建一个WifiLock public void CreatWifiLock() { mWifiLock = mWifiManager.createWifiLock("Test"); } // 得到配置好的网络 public List<WifiConfiguration> GetConfiguration() { return mWifiConfiguration; } // 指定配置好的网络进行连接 public void ConnectConfiguration(int index) { // 索引大于配置好的网络索引返回 if (index > mWifiConfiguration.size()) { return; } // 连接配置好的指定ID的网络 mWifiManager.enableNetwork(mWifiConfiguration.get(index).networkId, true); } public void StartScan() { mWifiManager.startScan(); // 得到扫描结果 mWifiList = mWifiManager.getScanResults(); // 得到配置好的网络连接 mWifiConfiguration = mWifiManager.getConfiguredNetworks(); } // 得到网络列表 public List<ScanResult> GetWifiList() { return mWifiList; } // 查看扫描结果 public StringBuilder LookUpScan() { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < mWifiList.size(); i++) { stringBuilder .append("Index_" + new Integer(i + 1).toString() + ":"); // 将ScanResult信息转换成一个字符串包 // 其中把包括:BSSID、SSID、capabilities、frequency、level stringBuilder.append((mWifiList.get(i)).toString()); stringBuilder.append("\n"); } return stringBuilder; } // 得到MAC地址 public String GetMacAddress() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.getMacAddress(); } // 得到接入点的BSSID public String GetBSSID() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.getBSSID(); } // 得到IP地址 public int GetIPAddress() { return (mWifiInfo == null) ? 0 : mWifiInfo.getIpAddress(); } // 得到连接的ID public int GetNetworkId() { return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId(); } // 得到WifiInfo的所有信息包 public String GetWifiInfo() { return (mWifiInfo == null) ? "NULL" : mWifiInfo.toString(); } // 添加一个网络并连接 public void AddNetwork(WifiConfiguration wcg) { int wcgID = mWifiManager.addNetwork(wcg); mWifiManager.enableNetwork(wcgID, true); } // 断开指定ID的网络 public void DisconnectWifi(int netId) { mWifiManager.disableNetwork(netId); mWifiManager.disconnect(); } }
参考资料:
《手机定位技术概述》