目前主流定位技术分为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();
}
}
参考资料:
《手机定位技术概述》