本文主要介紹如何在Android中引入地圖及並對地圖進行相應的操作。包括如何申請Google Map API密鑰、如何創建包含地圖的項目、地圖的功能使用(地圖縮放、設置視圖、定位到自己的位置、添加標記、獲得屏幕坐標的位置、雙擊放大、位置搜索即經緯度位置及屏幕像素坐標和具體地址的轉換、監聽某個位置)、google map和百度地圖API的差別。
如今lbs正火熱,android開發中地圖的相關操作必不可少。百度地圖API 和google地圖類似,本文以google地圖為例,下面的完整代碼可以見MapDemo@Google Code。示例APK地址可見TrineaAndroidDemo@GoogleCode。
1、申請Google Map API密鑰
a、從debug.keystore文件中提取MD5值
進入debug.keystore文件路徑下,路徑為C:\用戶\當前用戶\.android(xp路徑為C:\Documents and Settings\當前用戶\.android),路徑也可以在Eclipse工具下查看,選擇windows-->Preference-->Android-->Build,其中Default debug keystore的值便是debug.keystore的路徑了
執行命令:keytool -list -keystore debug.keystore -v,這時可能會提示你輸入密碼,這里默認的密碼是“android",這樣即可取得MD5值(16位)。
ps:以上命令必須加上-v,否則獲得的是SHA1碼(長度20位),在下一步申請時輸入后會提示“您輸入的指紋無效”
b. 申請Map API密鑰
打開http://code.google.com/android/maps-api-signup.html,需要用google賬號登陸,填入你的認證指紋(MD5)點擊獲取API key即可獲得API Key。
PS:此key只能作為debug使用,若要發布應用需要重新申請key替換此key。
2、新建項目
新建Android項目,注意Build Target選擇Google APIs而不是Android API。Google APIs包含了Android相應版本,同時加入了google自己的一些服務,目前只有google map。
3、編寫代碼
3.1 layout中添加MapView
<com.google.android.maps.MapView android:layout_width="fill_parent" android:layout_height="fill_parent" android:apiKey="yourAPIKey" />
修改對應的后台Activity繼承自MapActivity而不是Activity,否則會提示MapView只允許在MapActivity之類中使用。
3.2 AndroidManifest.xml文件添加權限
a. 在節點application內添加<uses-library android:name="com.google.android.maps" /> 表示引入maps庫,否則會提示找不到MapView類
b. 在節點manifest內添加<uses-permission android:name="android.permission.INTERNET" /> 表示允許訪問網絡,否則MapView會顯示空白
如下:
如此,運行程序即可
下面我們介紹如何使用google map提供的一些功能
4 map功能
4.1 地圖縮放
在OnCreate函數中添加
MapView gMap = (MapView) findViewById(R.id.mapview); gMap.setBuiltInZoomControls(true);
gMap.displayZoomControls(true)可以顯示縮放控件。
可以手動調用mapView.getController().zoomIn()或zoomOut()進行放大和縮小,或setZoom(zoomLevel)設置倍數。
4.2 設置視圖
gMap.setStreetView(true); // 街道視圖 gMap.setSatellite(true); // 衛星視圖 gMap.setTraffic(true); // 交通狀況可見
4.3 定位到自己的位置
AndroidManifest文件中添加權限 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />,本文以GPS獲取位置為例,其他如WIFI方式需要添加其他權限
使用LocationManager的getLastKnownLocation獲得自己的位置,MapController的animateTo定位到固定位置
MapView gMap = (MapView)findViewById(R.id.googleMapView); MapController gMapController = gMap.getController(); LocationManager lm = (LocationManager)getSystemService(LOCATION_SERVICE); Location l = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (l == null) { Toast.makeText(getApplication(), "無法獲取自己的位置", Toast.LENGTH_SHORT); } else { GeoPoint gp = new GeoPoint((int)(l.getLatitude() * 1E6), (int)(l.getLongitude() * 1E6)); gMapController.animateTo(gp); gMapController.setZoom(13); }
也可以使用locationManager的getBestProvider獲得最好的提供商再去獲取自己的位置
隨着移動更新GPS信息 ,定義自己的LocationListener

public class GMapLocationListener implements LocationListener { @Override public void onLocationChanged(Location location) { GeoPoint gp = new GeoPoint((int)(location.getLatitude() * 1E6), (int)(location.getLongitude() * 1E6)); gMapController.animateTo(gp); gMapController.setZoom(13); } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public void onProviderEnabled(String provider) { } @Override public void onProviderDisabled(String provider) { } }
重寫onLocationChanged方法在里面定位到最新的位置,下面注冊位置監聽器
LocationManager lm = (LocationManager)getSystemService(LOCATION_SERVICE); lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10 * 1000, 2, new GMapLocationListener());
其中10*1000為收到更新通知的最小時間,以毫秒為單位,2為收到更新通知的最小距離,以米為單位。
4.4 添加標記
定義自己的覆蓋層

public class GoogleMapOverlay extends Overlay { private GeoPoint gp; GoogleMapOverlay(GeoPoint gp){ super(); this.gp = gp; } GoogleMapOverlay(int latitudeE6, int longitudeE6){ super(); gp = new GeoPoint(latitudeE6, longitudeE6); } @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) { super.draw(canvas, mapView, shadow, when); Point p = new Point(); gMap.getProjection().toPixels(gp, p); Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.mark); canvas.drawBitmap(bmp, p.x, p.y, null); return true; } }
繼承Overlay重寫draw方法,其中的gMap.getProjection()表示得到地圖經緯度坐標和手機像素坐標之間的轉換對象,toPixels函數表示將經緯度坐標轉換為手機像素坐標。在canvas上繪制R.drawable.mark的圖像,添加自己的標記,可能需要根據圖片大小自己精確調整標記位置。下面顯示圖像

// 顯示標記 GoogleMapOverlay overlay = new GoogleMapOverlay(gp); List<Overlay> overlays = gMap.getOverlays(); overlays.clear(); overlays.add(overlay); gMap.invalidate();
先獲得map的overlays然后清楚添加自己的圖層並刷新。
注意Overlay和ItemizedOverlay的區別,Overlay更通用,可用來標記某條路線或某個區域等面狀,如某條公交路線或某個城市的區域;ItemizedOverlay是Overlay的子類,可用來標記一系列的點,如一些列餐館。對於百度地圖API還有RouteOverlay和TransitOverlay等,ItemizedOverlay定義舉例如下:

public class GoogleMapItemOverlay extends ItemizedOverlay<OverlayItem> { public GoogleMapItemOverlay(Drawable defaultMarker){ super(boundCenterBottom(defaultMarker)); populate(); } @Override protected OverlayItem createItem(int i) { return new OverlayItem(new GeoPoint((int)(1.3333 * 1E6), (int)(1.3333 * 1E6)), "title", "snippet"); } /** * 返回標記的數目 * * @return */ @Override public int size() { return 1; } }
4.5 獲得觸摸的位置
重寫上面自定義的Overlay的onTouchEvent方法,使用類Projection的fromPixels轉換屏幕像素坐標和經緯度坐標。

@Override public boolean onTouchEvent(MotionEvent e, MapView mapView) { if (e.getAction() == MotionEvent.ACTION_UP) { GeoPoint p = gMap.getProjection().fromPixels((int)e.getX(), (int)e.getY()); Toast.makeText(getApplication(), "點擊位置坐標為" + p.getLatitudeE6() / 1E6 + ", " + p.getLongitudeE6() / 1E6, Toast.LENGTH_SHORT).show(); } return super.onTouchEvent(e, mapView); }
4.6 雙擊放大
其實只要判斷雙擊即可,因為放大直接調用mapView.getController().zoonIn()函數即可

private static long DOUBLE_CLICK_INTERVAL = 300; private long lastTouchTime = 0; public class GoogleMapOverlay extends Overlay { @Override public boolean onTouchEvent(MotionEvent e, MapView mapView) { if (e.getAction() == MotionEvent.ACTION_UP) { // 判斷是否是有效雙擊,是則放大 long t = System.currentTimeMillis(); if (t - lastTouchTime < DOUBLE_CLICK_INTERVAL) { gMapController.zoomIn(); } lastTouchTime = t; } return super.onTouchEvent(e, mapView); } …… }
以上類對於添加1個GoogleMapOverlay可行,若添加多個GoogleMapOverlay,雙擊會造成多個GoogleMapOverlay同時觸發onTouchEvent函數造成指數級放大,因為點擊區域被多個overlay響應。如下

List<Overlay> overlays = gMap.getOverlays(); overlays.clear(); GoogleMapOverlay overlay1 = new GoogleMapOverlay(new GeoPoint((int)(1.3333 * 1E6), (int)(1.3333 * 1E6))); overlays.add(overlay1); GoogleMapOverlay overlay2 = new GoogleMapOverlay(new GeoPoint((int)(1.3334 * 1E6), (int)(1.3343 * 1E6))); overlays.add(overlay2); GoogleMapOverlay overlay3 = new GoogleMapOverlay(new GeoPoint((int)(1.3335 * 1E6), (int)(1.3353 * 1E6))); overlays.add(overlay3); gMap.invalidate();
若要解決可將多個待標記位置放在同一個圖層中,如此修改

public class GoogleMapOverlay extends Overlay { private List<GeoPoint> gpList = new ArrayList<GeoPoint>(); public GoogleMapOverlay(GeoPoint gp){ super(); gpList.add(gp); } public GoogleMapOverlay(List<GeoPoint> gpList){ super(); if (gpList != null && gpList.size() > 0) { this.gpList.addAll(gpList); } } public GoogleMapOverlay(int latitudeE6, int longitudeE6){ super(); gpList.add(new GeoPoint(latitudeE6, longitudeE6)); } @Override public boolean draw(Canvas canvas, MapView mapView, boolean shadow, long when) { super.draw(canvas, mapView, shadow, when); Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.mark); for (GeoPoint gp : gpList) { Point p = new Point(); gMap.getProjection().toPixels(gp, p); // 顯示標記,可以自己精確調整顯示 canvas.drawBitmap(bmp, p.x, p.y, null); } return true; } @Override public boolean onTouchEvent(MotionEvent e, MapView mapView) { if (e.getAction() == MotionEvent.ACTION_UP) { /** * 判斷是否是有效雙擊,是則放大 */ long t = System.currentTimeMillis(); if (t - lastTouchTime < DOUBLE_CLICK_INTERVAL) { gMapController.zoomIn(); } lastTouchTime = t; } return super.onTouchEvent(e, mapView); } }
調用如下

List<Overlay> overlays = gMap.getOverlays(); overlays.clear(); List<GeoPoint> gpList = new ArrayList<GeoPoint>(); gpList.add(new GeoPoint((int)(1.3333 * 1E6), (int)(1.3333 * 1E6))); gpList.add(new GeoPoint((int)(1.3334 * 1E6), (int)(1.3343 * 1E6))); gpList.add(new GeoPoint((int)(1.3335 * 1E6), (int)(1.3353 * 1E6))); overlays.add(new GoogleMapOverlay(gpList)); gMap.invalidate();
4.7 位置搜索即經緯度位置、屏幕像素坐標和具體地址的轉換
已經經緯度位置或屏幕像素坐標如何得到具體的地址呢會是已知木某個地名如何得到屏幕像素讓其在屏幕上顯示呢,如點擊了屏幕某個位置想獲得他的具體名字並顯示。這時可以使用Geocoder的getFromLocation或getFromLocationName進行轉換。

/** * 從經緯度坐標獲得地址列表 **/ public String getAddressFromGeoPoint(GeoPoint p) { StringBuilder s = new StringBuilder(); Geocoder gc = new Geocoder(getApplication(), Locale.getDefault()); try { // 根據坐標地址搜索地址 List<Address> l = gc.getFromLocation(p.getLatitudeE6() / 1E6, p.getLongitudeE6() / 1E6, MAX_ADDR_RESULT_NUM); if (l != null && l.size() > 0) { for (Address a : l) { for (int i = 0; i < a.getMaxAddressLineIndex(); i++) { s.append(a.getAddressLine(i)); } s.append("\n"); } } } catch (IOException e) { Toast.makeText(getApplication(), "獲取地址異常", Toast.LENGTH_SHORT).show(); } return s.toString(); } /** * 從地址獲得經緯度坐標 **/ public List<GeoPoint> getGeoPointFromAddress(String address) { List<GeoPoint> gpList = new ArrayList<GeoPoint>(); Geocoder gc = new Geocoder(getApplication(), Locale.getDefault()); try { // 根據具體位置搜索地址 List<Address> l = gc.getFromLocationName(address, MAX_ADDR_RESULT_NUM); if (l != null && l.size() > 0) { for (Address a : l) { for (int i = 0; i < a.getMaxAddressLineIndex(); i++) { gpList.add(new GeoPoint((int)(a.getLatitude() * 1E6), (int)(a.getLongitude() * 1E6))); } } } } catch (IOException e) { Toast.makeText(getApplication(), "獲取地址異常", Toast.LENGTH_SHORT).show(); } return gpList; }
其中的MAX_ADDR_RESULT_NUM表示最多返回的結果數目,本例中為5
getAddressFromGeoPoint可以根據某個經緯度坐標找到對應的地址,可以先使用4.5 獲得觸摸的位置得到經緯度坐標再獲取地址。
getGeoPointFromAddress可以根據某個地名得到其經緯度坐標,然后可以使用4.4中添加標記的方法進行標記,如getGeoPointFromAddress("上海圖書館").
4.8 監聽某個位置
使用locationManager的addProximityAlert函數
5、google map和百度地圖API的差別
在定位、縮放上API幾乎沒有區別但在搜索、路線、覆蓋物圖層方面區別巨大,百度地圖開發的API較多,可以方便的搜索某個位置、某類地點、繪制公交路線、駕駛路線等等,關於如何使用可以參見百度地圖API介紹,很詳細,而google map暫時貌似沒有開放。
6、解決Installation error: INSTALL_FAILED_MISSING_SHARED_LIBRARY錯誤,見http://www.cnblogs.com/trinea/archive/2012/11/14/2770408.html
參考:
Google地圖Android平台開發指南:https://developers.google.com/maps/documentation/android/hello-mapview
百度地圖Android平台開發指南:http://developer.baidu.com/map/sdkandev-1.htm