Android應用中使用百度地圖API定位自己的位置(二)
百度地圖SDK為開發者們提供瞭如下型別的地圖覆蓋物:
-
我的位置圖層(MyLocationOverlay):用於顯示使用者當前位置的圖層(支援自定義位置圖示);
-
Poi搜尋結果圖層(PoiOverlay):用於顯示興趣點搜尋結果的圖層;
-
路線圖層(RouteOverlay):公交、步行和駕車線路圖層,將公交、步行和駕車出行方案的路線及關鍵點顯示在地圖上(起、終點圖示使用者可自定義);
-
公交換乘圖層(TransitOverlay):公交換乘線路圖層,將某一特定地區的公交出行方案的路線及換乘位置顯示在地圖上(起、終點圖示使用者可自定義);
-
自定義圖層(ItemizedOverlay):可將一個或多個興趣點繪製到地圖上,且支援自定義圖示(支援動態更新Item位置、圖示);
-
彈出窗圖層(PopupOverlay):在地圖上顯示一個彈出視窗;
-
幾何圖形繪製圖層(GraphicsOverlay):用於繪製點、折線段、弧線、圓、矩形、多邊形等幾何圖形的圖層;
-
文字繪製圖層(TextOverlay):用於繪製文字的圖層。
-
圖片圖層(GroundOverlay):用於展示使用者傳入圖片的圖層。
-
全景圖圖層(PanoramaOverlay):在全景圖內標繪興趣點,支援自定義圖示樣式。
注:除彈出窗圖層外,其他各個圖層均已實現多例項。全景圖圖層是針對全景圖所使用的特殊圖層。
MapView使用一個List管理覆蓋物,通過向MapView.getOverlays() add或remove上述類或其基類的例項即可向地圖新增或刪除覆蓋物。在更新地圖覆蓋物後,需呼叫MapView.refresh() 使更新生效。
定位原理
使用百度Android定位SDK必須註冊GPS和網路使用許可權。定位SDK採用GPS、基站、Wi-Fi訊號進行定位。當應用程式向定位SDK發起定位請求時,定位SDK會根據應用的定位因素(GPS、基站、Wi-Fi訊號)的實際情況(如是否開啟GPS、是否連線網路、是否有訊號等)來生成相應定位依據進行定位。
使用者可以設定滿足自身需求的定位依據:
若使用者設定GPS優先,則優先使用GPS進行定位,如果GPS定位未開啟或者沒有可用位置資訊,且網路連線正常,定位SDK則會返回網路定位(即Wi-Fi與基站)的最優結果。為了使獲得的網路定位結果更加精確,請開啟手機的Wi-Fi開關。
下面我們將利用
一 . 匯入庫檔案
在使用百度定位SDKv4.0之前,我們要下載最新的庫檔案,下載地址:點選下載相關庫檔案,將liblocSDK4.so檔案拷貝到libs/armeabi目錄下。將locSDK4.0.jar檔案拷貝到工程的libs目錄下
目錄結構如下:
二 . 佈局檔案,一個百度地圖控制元件,加一個手動點選實現定位的按鈕
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<com.baidu.mapapi.map.MapView
android:id="@+id/bmapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:clickable="true"
/>
<Button
android:id="@+id/request"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginRight="10dp"
android:layout_marginTop="10dip"
android:background="@drawable/mylocation"
/>
</RelativeLayout>
然後是主 Activity
package com.majianjie.baidumap;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.MeasureSpec;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.mapapi.BMapManager;
import com.baidu.mapapi.MKGeneralListener;
import com.baidu.mapapi.map.LocationData;
import com.baidu.mapapi.map.MKEvent;
import com.baidu.mapapi.map.MKMapViewListener;
import com.baidu.mapapi.map.MapController;
import com.baidu.mapapi.map.MapPoi;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.MyLocationOverlay;
import com.baidu.mapapi.map.PopupClickListener;
import com.baidu.mapapi.map.PopupOverlay;
import com.baidu.platform.comapi.basestruct.GeoPoint;
import com.example.baidumap.R;
public class MainActivity extends Activity {
//宣告控制元件
private Button request;
private Toast mToast=null;
private BMapManager mBMapManager=null;
private MapView mMapView = null; //MapView 是地圖主控制元件
private MapController mMapController = null;//用MapController完成地圖控制
private LocationClient mLocClient;
public LocationData mLocData = null;
private LocationOverlay myLocationOverlay = null;//定點陣圖層
private boolean isRequest = false;//是否手動觸發請求定位
private boolean isFirstLoc = true;//是否首次定位
private PopupOverlay mPopupOverlay = null;//彈出泡泡圖層,瀏覽節點時使用
private View viewCache=null;
public BDLocation location = new BDLocation();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//**使用地圖sdk前需先初始化BMapManager,這個必須在setContentView()先初始化
mBMapManager = new BMapManager(this);
//第一個引數是API key, 第二個引數是常用事件監聽,用來處理通常的網路錯誤,授權驗證錯誤等,你也可以不新增這個回撥介面
mBMapManager.init("LDtH1sVwr7kygaF0aTqaVwWU", new MKGeneralListener() {
//授權錯誤的時候呼叫的回撥函式
@Override
public void onGetPermissionState(int iError) {
if (iError == MKEvent.ERROR_PERMISSION_DENIED) {
showToast("API KEY錯誤, 請檢查!");
}
}
//一些網路狀態的錯誤處理回撥函式
@Override
public void onGetNetworkState(int iError) {
if (iError == MKEvent.ERROR_NETWORK_CONNECT) {
Toast.makeText(getApplication(), "您的網路出錯啦!", Toast.LENGTH_LONG).show();
}
}
});
//初始化
init();
//單擊事件
click();
}
//* 顯示Toast訊息
private void showToast(String msg){
if(mToast == null){
mToast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
}else{
mToast.setText(msg);
mToast.setDuration(Toast.LENGTH_SHORT);
}
mToast.show();
}
private void click() {
request.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
requestLocation();
}
});
}
@Override
protected void onResume() {
//MapView的生命週期與Activity同步,當activity掛起時需呼叫MapView.onPause()
mMapView.onResume();
mBMapManager.start();//重新啟動
super.onResume();
}
@Override
protected void onPause() {
//MapView的生命週期與Activity同步,當activity掛起時需呼叫MapView.onPause()
mMapView.onPause();
super.onPause();
}
private void init() {
//使用自定義的title,注意順序
setContentView(R.layout.activity_main); //activity的佈局 //這裡是新增自己定義的titlebtn.xml
//通過id找到他們
mMapView = (com.baidu.mapapi.map.MapView) findViewById(R.id.bmapView);
mMapController=mMapView.getController(); //獲取地圖控制器
mMapController.enableClick(true); //設定地圖是否響應點選事件
request=(Button)findViewById(R.id.request);
viewCache = LayoutInflater.from(this).inflate(R.layout.pop_layout, null);
mPopupOverlay= new PopupOverlay(mMapView, new PopupClickListener() {// * 點選彈出視窗圖層回撥的方法
@Override
public void onClickedPopup(int arg0) {
//隱藏彈出視窗圖層
mPopupOverlay.hidePop();
}
});
mMapController.enableClick(true); //* 設定地圖是否響應點選事件 .
mMapController.setZoom(12); // * 設定地圖縮放級別
mMapView.setBuiltInZoomControls(true); // * 顯示內建縮放控制元件
mMapView.setTraffic(true);
mLocData = new LocationData();
mLocClient = new LocationClient(getApplicationContext()); // * 定位SDK的核心類
//例項化定位服務,LocationClient類必須在主執行緒中宣告
mLocClient.registerLocationListener(new BDLocationListenerImpl());//註冊定位監聽介面
/**
* 設定定位引數
*/
LocationClientOption option = new LocationClientOption();
option.setOpenGps(true); //開啟GPRS
option.setAddrType("all");//返回的定位結果包含地址資訊
option.setCoorType("bd09ll");//返回的定位結果是百度經緯度,預設值gcj02
option.setScanSpan(5000); //設定發起定位請求的間隔時間為5000ms
option.disableCache(false);//禁止啟用快取定位
option.setPoiNumber(5); //最多返回POI個數
option.setPoiDistance(1000); //poi查詢距離
option.setPoiExtraInfo(true); //是否需要POI的電話和地址等詳細資訊
mLocClient.setLocOption(option);
mLocClient.start(); // 呼叫此方法開始定位
myLocationOverlay = new LocationOverlay(mMapView);//定點陣圖層初始化
//將定位資料設定到定點陣圖層裡
myLocationOverlay.setMarker(getResources().getDrawable(R.drawable.set));
//新增定點陣圖層
mMapView.getOverlays().add(myLocationOverlay);
myLocationOverlay.enableCompass();
//更新圖層資料執行重新整理後生效
mMapView.refresh();
/*
//準備要新增的Overlay
double mLat1 = 39.910159;
double mLon1 = 119.544697;
// 用給定的經緯度構造GeoPoint,單位是微度 (度 * 1E6)
GeoPoint p1 = new GeoPoint((int) (mLat1 * 1E6), (int) (mLon1 * 1E6));
//準備overlay影象資料,根據實情情況修復
Drawable mark= getResources().getDrawable(R.drawable.set);
//用OverlayItem準備Overlay資料
OverlayItem item1 = new OverlayItem(p1,"item1","item1");
//使用setMarker()方法設定overlay圖片,如果不設定則使用構建ItemizedOverlay時的預設設定
//建立IteminizedOverlay
CustomItemizedOverlay itemOverlay = new CustomItemizedOverlay(mark, mMapView);
//將IteminizedOverlay新增到MapView中
mMapView.getOverlays().clear();
mMapView.getOverlays().add(itemOverlay);
//現在所有準備工作已準備好,使用以下方法管理overlay.
//新增overlay, 當批量新增Overlay時使用addItem(List<OverlayItem>)效率更高
itemOverlay.addItem(item1);
//刪除overlay .
//itemOverlay.removeItem(itemOverlay.getItem(0));
//mMapView.refresh();
//清除overlay
// itemOverlay.removeAll();
// mMapView.refresh();
mMapView.refresh();
*/
// mMapController.setCenter(p1);
mMapView.regMapViewListener(mBMapManager, new MKMapViewListener() {
// * 地圖移動完成時會回撥此介面 方法
@Override
public void onMapMoveFinish() {
showToast("地圖移動完畢!");
}
//* 地圖載入完畢回撥此介面方法
@Override
public void onMapLoadFinish() {
showToast("地圖載入完畢!");
}
//* 地圖完成帶動畫的操作(如: animationTo())後,此回撥被觸發
@Override
public void onMapAnimationFinish() {
}
//當呼叫過 mMapView.getCurrentMap()後,此回撥會被觸發 可在此儲存截圖至儲存裝置
@Override
public void onGetCurrentMap(Bitmap arg0) {
}
//* 點選地圖上被標記的點回調此方法
@Override
public void onClickMapPoi(MapPoi arg0) {
if (arg0 != null){
showToast(arg0.strText);
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
CreateMenu(menu);
return true;
}
private void CreateMenu(Menu menu){
MenuItem mnu1 =menu.add(0,0,0,"顯示衛星地圖");
{
mnu1.setAlphabeticShortcut('a');//設定快捷鍵
//mnu1.serIcon(R.drawable.icon);//設定圖片
}
MenuItem mnu2 =menu.add(0,1,1,"顯示街道地圖");
{
mnu2.setAlphabeticShortcut('b');//設定快捷鍵
//mnu1.serIcon(R.drawable.icon);//設定圖片
}
MenuItem mnu3 =menu.add(0,2,2,"3D地圖");
{
mnu3.setAlphabeticShortcut('c');//設定快捷鍵
//mnu1.serIcon(R.drawable.icon);//設定圖片
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId() == 0){
mMapView.setSatellite(true); //設定顯示為衛星地圖:
mMapView.setTraffic(false);
}else if(item.getItemId() == 1){
mMapView.setTraffic(true); //顯示街道地圖
mMapView.setSatellite(false);
}else if(item.getItemId() == 2){
//mMapView.se
}
return true;
}
public class BDLocationListenerImpl implements BDLocationListener {
// * 接收非同步返回的定位結果,引數是BDLocation型別引數
@Override
public void onReceiveLocation(BDLocation location) {
if (location == null) {
return;
}
/*
StringBuffer sb = new StringBuffer(256);
sb.append("time : ");
sb.append(location.getTime());
sb.append("\nerror code : ");
sb.append(location.getLocType());
sb.append("\nlatitude : ");
sb.append(location.getLatitude());
sb.append("\nlontitude : ");
sb.append(location.getLongitude());
sb.append("\nradius : ");
sb.append(location.getRadius());
if (location.getLocType() == BDLocation.TypeGpsLocation){
sb.append("\nspeed : ");
sb.append(location.getSpeed());
sb.append("\nsatellite : ");
sb.append(location.getSatelliteNumber());
} else if (location.getLocType() == BDLocation.TypeNetWorkLocation){
sb.append("\naddr : ");
sb.append(location.getAddrStr());
}
*/
MainActivity.this.location = location;
mLocData.latitude = location.getLatitude();
mLocData.longitude = location.getLongitude();
//如果不顯示定位精度圈,將accuracy賦值為0即可
mLocData.accuracy = location.getRadius();
mLocData.direction = location.getDerect();
//將定位資料設定到定點陣圖層裡
myLocationOverlay.setData(mLocData);
//更新圖層資料執行重新整理後生效
mMapView.refresh();
if(isFirstLoc || isRequest){
//將給定的位置點以動畫形式移動至地圖中心
mMapController.animateTo(new GeoPoint(
(int) (location.getLatitude() * 1e6), (int) (location.getLongitude() * 1e6)));
showPopupOverlay(location); //載入時候就彈出
isRequest = false;
}
isFirstLoc = false;
}
// 接收非同步返回的POI查詢結果,引數是BDLocation型別引數
@Override
public void onReceivePoi(BDLocation poiLocation) {
}
}
private void requestLocation() {
isRequest = true;
if(mLocClient != null && mLocClient.isStarted()){
showToast("正在定位......");
mLocClient.requestLocation();
}
}
//繼承MyLocationOverlay重寫dispatchTap方法
private class LocationOverlay extends MyLocationOverlay{
public LocationOverlay(MapView arg0) {
super(arg0);
}
// * 在“我的位置”座標上處理點選事件。
@Override
protected boolean dispatchTap() {
//點選我的位置顯示PopupOverlay
showPopupOverlay(location);
return super.dispatchTap();
}
@Override
public void setMarker(Drawable arg0) {
super.setMarker(arg0);
}
}
@Override
protected void onDestroy() {
//MapView的生命週期與Activity同步,當activity銷燬時需呼叫MapView.destroy()
mMapView.destroy();
//退出應用呼叫BMapManager的destroy()方法
if(mBMapManager != null){
mBMapManager.destroy();
mBMapManager = null;
}
//退出時銷燬定位
if (mLocClient != null){
mLocClient.stop();
}
super.onDestroy();
}
//* 顯示彈出視窗圖層PopupOverlay
private void showPopupOverlay(BDLocation location){
TextView popText = ((TextView)viewCache.findViewById(R.id.location_tips));
popText.setText("[我的位置]\n" + location.getAddrStr());
mPopupOverlay.showPopup(getBitmapFromView(popText),
new GeoPoint((int)(location.getLatitude()*1e6), (int)(location.getLongitude()*1e6)),
15);
}
// * 將View轉換成Bitmap的方法
public static Bitmap getBitmapFromView(View view) {
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
return bitmap;
}
}
大家注意:這裡我把上一次的新增marker的程式碼註釋了,原因是當我這兩個同時弄的時候會無法給當前位置新增標記。。。這個問題稍後會得到解決。
下面的佈局檔案是彈出框的佈局,一個 很簡單的檔案:pop_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/location_tips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/location_tips"
android:maxWidth="200.0dip"
android:minWidth="40.0dip"
android:textColor="@android:color/white" />
/>
</RelativeLayout>
- LocationClient 定位SDK的核心類,LocationClient類必須在主執行緒中宣告。需要Context型別的引數。Context需要時全程序有效的context,推薦用getApplicationConext獲取全程序有效的context,我們呼叫registerLocationListener(BDLocationListener)方法來註冊定位監聽介面,BDLocationListener裡面有兩個方法,onReceiveLocation()(接收非同步返回的定位結果),onReceivePoi()(接收非同步返回的POI查詢結果,POI是“Point of Interest”的縮寫,可以翻譯成“資訊點”,每個POI包含四方面資訊,名稱、類別、經度、緯度、附近的酒店、飯店,商鋪等資訊。我們可以叫它為“導航地圖資訊”,導航地圖資料是整個導航產業的基石),我們這裡只需要重寫onReceiveLocation就行了
- BDLocation 封裝了定位SDK的定位結果,在BDLocationListener的onReceive方法中獲取。通過該類使用者可以獲取error code,位置的座標,精度半徑,地址等資訊,對於其getLocType ()方法獲取的error code一些情況
- 61 : GPS定位結果
- 62 : 掃描整合定位依據失敗。此時定位結果無效。
- 63 : 網路異常,沒有成功向伺服器發起請求。此時定位結果無效。
- 65 : 定位快取的結果。
- 66 : 離線定位結果。通過requestOfflineLocaiton呼叫時對應的返回結果
- 67 : 離線定位失敗。通過requestOfflineLocaiton呼叫時對應的返回結果
- 68 : 網路連線失敗時,查詢本地離線定位時對應的返回結果
- 161: 表示網路定位結果
- 162~167: 服務端定位失敗
- LocationClientOption 用來設定定位SDK的定位方式,比如設定開啟GPS,設定是否需要地址資訊,設定發起定位請求的間隔時間等等,引數設定完後呼叫LocationClient 的setLocOption方法
- LocationOverlay MyLocationOverlay的子類,重寫裡面的dispatchTap()方法,顯示彈出視窗圖層PopupOverlay,呼叫mMapView.getOverlays().add(myLocationOverlay)就將我的位置圖層新增到地圖裡面
- PopupOverlay 彈出圖層,這個類還是比較簡單,裡面只有三個方法,hidePop() (隱藏彈出圖層)showPopup(Bitmap pop, GeoPoint point, int yOffset) (顯示彈出圖層)和showPopup顯示多張圖片的過載方法,由於showPopup方法只接受Bitmap物件,所以我們必須將我們的彈出圖層View物件轉換成Bitmap物件,我們呼叫getBitmapFromView方法就實現這一轉換
- BDLocationListener介面的onReceiveLocation(BDLocation location) 方法我還要重點講解下,我們會發現onReceiveLocation方法會反覆執行,他執行的間隔跟LocationClientOption類的setScanSpan()方法設定的值有關,我們設定的是5000毫秒,則onReceiveLocation方法每隔5秒執行一次,注意,當我們設定的值大於1000(ms),定位SDK內部使用定時定位模式。呼叫requestLocation( )後,每隔設定的時間,定位SDK就會進行一次定位。如果定位SDK根據定位依據發現位置沒有發生變化,就不會發起網路請求,返回上一次定位的結果;如果發現位置改變,就進行網路請求進行定位,得到新的定位結果。如果你只需要定位一次的話,這個設定小於1000,或者不用設定就可以了,定時定位時,呼叫一次requestLocation,會定時監聽到定位結果
<span style="color:#ff6666;"> </span><span style="color:#333333;"> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_LOGS" /></span>
還有很重要的一點就是在AndroidManifest.xml中新增下面的內容 <service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>
<meta-data
android:name="com.baidu.lbsapi.API_KEY"
android:value="6KOX4mXHeBRzgriV6OP1T2Hw"
/>
<!-- 致命的一句 -->
其中的meta-data問題:網上這樣說,大家記住就得了。。。。
因為單獨的定位sdk需要一個key值,而定位sdk的值又不像mapManager中可以直接賦值
所以就需要在登錄檔單中註冊
執行圖示:
定位自己的位置基本就這些內容,出現的問題隨後會補充上。。今天就這樣吧。。吃飯去了。。