上周学了了一下手机GPS定位以及百度地图的使用,现在对此做一个总结:
个人感觉地图应用可以划分为三个部分:首先是定位,可以是精度高但是速度稍慢的gps定位,也可以是速度快精度低移动网络的定位,这个完全由用户自己选择,定位方法会返回一个包含经度纬度速度方向等很多参数的一个 ”位置“ 。然后就是地图,虽然android提供了google地图但是无法使用,在国内就直接用百度地图吧,通过刚才刚才那个”位置“就可以定位到地图。最后一部分就是百度地图的其他服务功能了,比如附近,路线,导航等。
1.定位
其实百度地图直接提供了定位api的,但我想先用android自带定位api试下。android定位api下面有几个重要的类:LocationManager是一个管理类,对它可以调用getLastKnownLocation()手动得到当前定位,对它调用requestLocationUpdates()可以绑定监听,当位置或者手机状态改变时触发。Location是位置的实体类,还有一个就是定位参数provider包括定位方式(gps还是net),精度,耗电量等等。最后就是一个监听LocationListener(),监听位置改变,gps状态改变等,具体实现是这样的:
首先获得一个provider,就是定位参数:
private void getProvider(){
// 查找到服务信息
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE); // 高精度
criteria.setAltitudeRequired(false);
criteria.setBearingRequired(false);
criteria.setCostAllowed(true);
criteria.setPowerRequirement(Criteria.POWER_LOW); // 低功耗
provider = lm.getBestProvider(criteria, false); // 获取GPS信
Toast.makeText(this, "当前工作的provider是: " + provider, 0)
.show();
}
这里就是返回一个最优方式由系统选择,但有的文章说部分手机不支持getBestProvider(),那可以自己选择,就是根据用户是否开启gps和移动网络,来选择一个provider,当然gps应该优先,其实provider就是3个LocationManager的常量,接下我们可以干两件事:在onStart里面得到当前位置:
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
Log.d("dml", "onStart......provider :" + provider);
Location location2 = lm.getLastKnownLocation(provider);
Location location = MapUtils.getNewLocation(location2); // 通过GPS获取位置
// while(location == null)
// {
// lm.requestLocationUpdates(provider, 3000, 8,listener);
// //mgr.requestLocationUpdates("gps", 60000, 1, locationListener);
// }
if(location!=null){
Toast.makeText(this, "当前经纬度: " + location.getLongitude() + ","
+ location.getLatitude(), 0).show();
setMyPosition(location);
}
else{
Log.d("dml", "getLastKnownLocation 返回null");
}
}
这里有个方法叫getLastKnownLocation(), 它是先判断上次 的定位位置,如果gps或者net没有开启就返回上次位置,否则重新获得当前定位,相当于一个缓存吧。但 实际应用中经常返回null,网上给出的解决办法都一样:注册两个权限,把监听现在onCreate()里面实例化:
listener = new LocationListener() {
@Override
public void onStatusChanged(String provider, int status,
Bundle extras) {
// TODO Auto-generated method stub
}
@Override
public void onProviderEnabled(String provider) {
// TODO Auto-generated method stub
Location location = MapUtils.getNewLocation
(lm.getLastKnownLocation(provider));
setMyPosition(location);
Log.d("dml", "onProviderEnabled--------");
}
@Override
public void onProviderDisabled(String provider) {
// TODO Auto-generated method stub
Log.d("dml", "onProviderDisabled--------");
}
@Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
location = MapUtils.getNewLocation(location);
Toast.makeText(MainActivity.this, "更新经纬度:
" + location.getLongitude() + "," + location.getLatitude(), 0).show();
setMyPosition(location);
Log.d("dml", "onLocationChanged--------");
}
};
接上面说,干第二件事就是在onResume()绑定监听:
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
lm.requestLocationUpdates(provider, 3000, 8,listener);
}
第一个参数就是刚才得到的provider,第二个参数是3000ms定位一次,8 m是精确距离,listener当然就是onCreate实例化的监听,一切准备完毕就可以在onLocationChanged()里面更新地图啦,也可以在地图上放一个按钮,用getLastKnownLocation()手动更新。
2. 百度地图
上面是用android系统自带api获取定位,但最终还是要显示在百度地图上,这里有两个问题:1.上面返回的位置是Location类型,BaiduMap的位置是BDLocation,不过其实就是获取精度这个方法不一样,我们改成Location自己的就OK 2. 用android自带api获得的定位显示在BaiduMap上面位置始终偏移1000M左右,这个不是精度问题而是地图不一样,我们用google api得到的位置放在BaiduMap需要算法纠正:
/**
* 各地图API坐标系统比较与转换;
* WGS84坐标系:即地球坐标系,国际上通用的坐标系。设备一般包含GPS芯片或者北斗芯片获取的经纬度为WGS84地理坐标系,
* 谷歌地图采用的是WGS84地理坐标系(中国范围除外);
* GCJ02坐标系:即火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由WGS84坐标系经加密后的坐标系。
* 谷歌中国地图和搜搜中国地图采用的是GCJ02地理坐标系; BD09坐标系:即百度坐标系,GCJ02坐标系经加密后的坐标系;
* 搜狗坐标系、图吧坐标系等,估计也是在GCJ02基础上加密而成的。 chenhua
*/
/**
* 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System
*
* @param lat
* @param lon
* @return
*/
public static Gps gps84_To_Gcj02(double lat, double lon) {
if (outOfChina(lat, lon)) {
return null;
}
double dLat = transformLat(lon - 105.0, lat - 35.0);
double dLon = transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * pi;
double magic = Math.sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = Math.sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
dLon = (dLon * 180.0) / (a / sqrtMagic * Math.cos(radLat) * pi);
double mgLat = lat + dLat;
double mgLon = lon + dLon;
return new Gps(mgLat, mgLon);
}
/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标
*
* @param gg_lat
* @param gg_lon
*/
public static Gps gcj02_To_Bd09(double gg_lat, double gg_lon) {
double x = gg_lon, y = gg_lat;
double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * pi);
double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * pi);
double bd_lon = z * Math.cos(theta) + 0.0065;
double bd_lat = z * Math.sin(theta) + 0.006;
return new Gps(bd_lat, bd_lon);
}
public static Gps gps84_To_Bd09(double lat, double log){
Gps gps = gps84_To_Gcj02(lat,log);
return gcj02_To_Bd09(gps.getWgLat(),gps.getWgLon());
}
我要用的就是最后一个方法!
这样折腾一番可以准确定位了,如果想简单一点就直接用BaiduMap提供的全套定位地图api吧。代码更简洁定位也更快,答题思路和上面差不多的,只不过不需要坐标转换了,而且这个定位其实是放入一个service更加合理,我们要在manifest注册一个service:
<service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote"></service>
添加权限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
在Aplication初始化 ,当然也要在manifest声明MyAplication
public class MyApplication extends Application {
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
SDKInitializer.initialize(this);
}
}
核心代码,附带实现了一下点击我的位置弹出窗口显示位置:
public class BaiduActivity extends Activity {
/**
* 定位SDK的核心类
*/
public LocationClient mLocationClient = null;
public BDLocationListener myListener = new MyLocationListener();
private MapView mMapView;
private BaiduMap mBaiduMap;
private Button reqLocation;
private BDLocation myLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMapView = (MapView) findViewById(R.id.bmapView);
reqLocation = (Button)findViewById(R.id.request);
// 手动请求定位
reqLocation.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(BaiduActivity.this, MapUtils.getStr(BaiduActivity.this, R.string.is_locate), 0).show();
if (mLocationClient != null && mLocationClient.isStarted())
mLocationClient.requestLocation();
else
Log.d("dml", "locClient is null or not started");
}
});
mBaiduMap = mMapView.getMap();
//普通地图
mBaiduMap.setMapType(BaiduMap.MAP_TYPE_NORMAL);
mBaiduMap.setOnMyLocationClickListener(listener);
mLocationClient = new LocationClient(getApplicationContext()); //声明LocationClient类
LocationClientOption option = new LocationClientOption();
option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy);//设置定位模式 高精度
option.setCoorType("bd09ll");//返回的定位结果是百度经纬度,默认值gcj02
option.setScanSpan(5000);//设置发起定位请求的间隔时间为5000ms
option.setIsNeedAddress(true);//返回的定位结果包含地址信息
option.setNeedDeviceDirect(true);//返回的定位结果包含手机机头的方向
mLocationClient.setLocOption(option);
mLocationClient.registerLocationListener( myListener ); //注册监听函数
mLocationClient.start(); // 调用此方法开始定位
}
private void setMyPosition(BDLocation location ){
if(location==null){
return;
}
// 开启定位图层
mBaiduMap.setMyLocationEnabled(true);
// 构造定位数据
MyLocationData locData = new MyLocationData.Builder()
.accuracy(location.getRadius()) // 获取精度
// 此处设置开发者获取到的方向信息,顺时针0-360
.direction(100).latitude(location.getLatitude())
.longitude(location.getLongitude()).build();
// 设置定位数据
mBaiduMap.setMyLocationData(locData);
// 设置定位图层的配置(定位模式,是否允许方向信息,用户自定义定位图标)
BitmapDescriptor mCurrentMarker = BitmapDescriptorFactory
.fromResource(R.drawable.position);
MyLocationConfiguration config = new MyLocationConfiguration(LocationMode.FOLLOWING, true, mCurrentMarker);
mBaiduMap.setMyLocationConfigeration(config);
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
//退出时销毁定位
if (mLocationClient != null){
mLocationClient.stop();
}
}
public class MyLocationListener implements BDLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
if (location == null)
return ;
myLocation = location;
setMyPosition(location);
}
}
OnMyLocationClickListener listener = new OnMyLocationClickListener() {
/**
* 地图定位图标点击事件监听函数
*/
public boolean onMyLocationClick(){
Log.d("dml", "点击 : 我的位置");
//创建InfoWindow展示的view
Button button = new Button(getApplicationContext());
button.setText(myLocation.getAddrStr());
button.setLayoutParams(new LayoutParams(60, 20));
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
mBaiduMap.hideInfoWindow();
}
});
//定义用于显示该InfoWindow的坐标点
double lon = myLocation.getLongitude();
double la = myLocation.getLatitude();
LatLng pt = new LatLng(la, lon);
//创建InfoWindow , 传入 view, 地理坐标, y 轴偏移量
InfoWindow mInfoWindow = new InfoWindow(button, pt, -47);
//显示InfoWindow
mBaiduMap.showInfoWindow(mInfoWindow);
return false;
}
};
}
3. 丰富的服务功能
BaiduMap里面提供了巨多的类似附近,导航,路线等api,这个我暂时用不到先不做研究。