chapter 9 定位與 google 地圖 -...
Post on 20-Sep-2019
7 Views
Preview:
TRANSCRIPT
Chapter 9 定位與 Google 地圖
作者: 林致宇
定位與 Google地圖的結合產生了許多的應用,除了一般人較為熟知的行車導航
系統之外,位置感知服務(Location-Based Service, LBS)也帶來龐大的商機,位置
感知服務的應用範圍很廣泛,例如找出使用者附近的餐廳、停車場等資訊都是屬
於位置感知服務。此外導覽系統也是很常見的一種應用,例如校園導覽系統、博
物館導覽系統、購物商場導覽系統等,結合了定位技術後,系統可提供使用者更
精確的資訊。本章我們將學習如何在 Android 系統中獲得位置資訊,此外也會學
習結合定位與 Google 地圖做出一個簡易的導覽系統。
需注意的是,本章 9.2至 9.4節的內容屬於 Google Maps Android API v1,此 API
已經於 2012年 12月 3日廢除,2013年 3月 3 日以後讀者就無法再利用 9.2所提
的方法獲得開發金鑰,然而若讀者有舊的開發金鑰,仍可繼續使用。為了讓讀者
快速進入 Google Maps Android API v2,本章會於 9.5節中做說明。
9.1 取得所在位置
在一節中,我們會完成一個應用程式,這個應用程式會將使用者所在位置的經緯
度顯示給使用者,請讀者引進光碟中『\程式範例\Chapter9\MyLocation』這個專
案,然而在深入探討程式碼之前,筆者先介紹Android系統提供的兩種定位方法:
透過 GPS(Global Positioning System,全球定位系統):GPS 藉助衛星來定位
[1],能夠獲得精確的位置,然而無法使用於無法接收衛星訊號的地方,例
如室內。Android 系統裡這種定位方法的定位提供者(Provider)名為"gps"。
透過行動通訊基地台(Cell tower)或者無線區域網路存取點(WiFi access
points)來定位:其是藉助一些定位技術(如三角定位)來估算出使用者所在位
置,獲得的位置資訊較不精確,然而可於室內使用。Android 系統裡這種定
位方法的定位提供者(Provider)名為"network"。
接下來我們便要開始解說這個應用程式是如何設計的,首先我們必須在
AndroidManifest.xml 聲明這個應用程式會希望系統能允許它存取從 GPS 或基地
台得到的位置資訊,這麼做是為了安全的考量,例如假設有一個應用程式,使用
者認為這個應用程式是離線運作的,可是它卻是會連上網路,進行一些資料傳輸,
使用者卻渾然不知,對使用者來說不是一件很沒有安全感的事嗎?因此 Android
系統設計了一個『許可』機制,若應用程式想連上網際網路,它必須在
AndroidManifest.xml 中表明它希望得到連上網際網路的許可,使用者在安裝這個
應用程式時,會得知這個應用程式希望得到哪些許可,如此使用者可決定要不要
安裝這個應用程式,若讀者想瞭解更多關於『許可』的說明或者想知道 Android
系統提供了哪些『許可』,可至 Android 開發者網站閱讀相關文件[2][3]。
在這個應用程式中,我們需要得到兩個許可,第一個是
ACCESS_FINE_LOCATION,應用程式得到許可後會獲得存取精確位置(例如由
GPS 所提供)的權限,第二個是 ACCESS_COARSE_LOCATION,應用程式得到
許可後會獲得存取不精確位置(例如由基地台所提供)的權限,為了得到許可,程
式開發者需要在 AndroidManifest.xml 中使用<uses-permission>標籤聲明,整個
AndroidManifest.xml 的內容如下,讀者可發現我們只需要將<uses-permission>標
籤置於<manifest>標籤下,且利用 android:name 屬性指定欲取得的許可,即可完
成聲明的動作:
1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android=
3 "http://schemas.android.com/apk/res/android"
4 package="lincyu.mylocation"
5 android:versionCode="1"
6 android:versionName="1.0">
7 <application android:icon="@drawable/icon"
8 android:label="@string/app_name">
9 <activity android:name=".MyLocation"
10 android:label="@string/app_name">
11 <intent-filter>
12 <action android:name="android.intent.action.MAIN"/>
13 <category
14 android:name="android.intent.category.LAUNCHER"/>
15 </intent-filter>
16 </activity>
17 </application>
18 <uses-sdk android:minSdkVersion="3" />
19 <uses-permission android:name=
20 "android.permission.ACCESS_FINE_LOCATION">
21 </uses-permission>
22 <uses-permission android:name=
23 "android.permission.ACCESS_COARSE_LOCATION">
24 </uses-permission>
25 </manifest>
接下來我們開始深入探討 Java程式碼,程式碼如下所示:
1 public class MyLocation extends Activity {
2 private MyLocationListener mll;
3 private LocationManager mgr;
4 private TextView tv;
5 private String best;
6
7 @Override
8 public void onCreate(Bundle savedInstanceState) {
9 super.onCreate(savedInstanceState);
10 setContentView(R.layout.main);
11 mgr = (LocationManager)getSystemService(LOCATION_SERVICE);
12 tv = (TextView)findViewById(R.id.loc);
13 mll = new MyLocationListener();
14 Criteria criteria = new Criteria();
15 best = mgr.getBestProvider(criteria, true);
16 Location location = mgr.getLastKnownLocation(
17 LocationManager.GPS_PROVIDER);
18 if (best != null) {
19 location = mgr.getLastKnownLocation(best);
20 }
21 if (location != null) {
22 tv.setText(showLocation(location));
23 } else {
24 tv.setText("Cannot get location!");
25 }
26 }
27
28 @Override
29 protected void onResume() {
30 super.onResume();
31 Criteria criteria = new Criteria();
32 criteria.setAccuracy(Criteria.ACCURACY_FINE);
33 best = mgr.getBestProvider(criteria, true);
34 if (best != null) {
35 mgr.requestLocationUpdates(best, 1000, 1, mll);
36 } else {
37 mgr.requestLocationUpdates(LocationManager.GPS_PROVIDER,
38 1000, 1, mll);
39 }
40 }
41
42 @Override
43 protected void onPause() {
44 super.onPause();
45 mgr.removeUpdates(mll);
46 }
47
48 class MyLocationListener implements LocationListener {
49 @Override
50 public void onLocationChanged(Location location) {
51 if (location != null) {
52 tv.setText(MyLocation.showLocation(location));
53 } else {
54 tv.setText("Cannot get location!");
55 }
56 }
57 @Override
58 public void onProviderDisabled(String provider) {
59 }
60 @Override
61 public void onProviderEnabled(String provider) {
62 }
63 @Override
64 public void onStatusChanged(String provider, int status,
65 Bundle extras) {
66 }
67 }
68
69 public static String showLocation(Location location) {
70 StringBuffer msg = new StringBuffer();
71 msg.append("定位提供者(Provider): \n");
72 msg.append(location.getProvider());
73 msg.append("\n緯度(Latitude): \n");
74 msg.append(Double.toString(location.getLatitude()));
75 msg.append("\n經度(Longitude): \n");
76 msg.append(Double.toString(location.getLongitude()));
77 msg.append("\n高度(Altitude): \n");
78 msg.append(Double.toString(location.getAltitude()));
79 return msg.toString();
80 }
81 }
首先在第 11行,程式利用 Context 類別的 getSystemService方法來取得
LocationManager的物件實體[4]。而在第 13行,程式宣告了一個
MyLocationListener 物件,MyLocationListener類別是定義在 48~67 行,其實作了
LocationListener 介面[5],這個傾聽者可用來處理位置改變或定位提供者改變時
的對應對作,我們可以請 LocationManager 週期性地幫我們取得最新位置並向
LocationManager註冊這個傾聽者,如此當 LocationManager 發現位置改變時,就
會透過傾聽者告知給應用程式,應用程式再做出相對應的動作。第 14行,程式
宣告了一個 Criteria 物件[6],Criteria類別是讓開發者設定選擇定位提供者的“選
擇偏好”,例如希望得到最精確的位置,或者希望能夠以省電為主要考量等。設
定好“選擇偏好”後,Criteria物件就能丟進第 15行的 getBestProvider 方法當參
數,getBestProvider 會回傳一個字串,例如可能是"gps"或"network"。接著程式利
用 getLastKnownLocation 方法取得最新位置,並利用自行定義的 showLocation
方法將位置資訊顯示於 TextView上,showLocation 方法是撰寫於 68~80 行。以
上便是 onCreate方法內的程式說明。
如果使用者想讓 LocationManager 週期性地更新最新位置,必須呼叫
requestLocationUpdates方法,而想停止週期性回報時則呼叫 removeUpdates方法,
我們選擇在 onResume 與 onPause方法呼叫上述方法,理由是因為應用程式可能
會因某些原因(例如有電話打進來)而暫時移至背景處理,此時若還繼續更新位置,
有點浪費電源,因此於 onPause方法裡呼叫 removeUpdates 方法,而於 onResume
方法裡呼叫 requestLocationUpdates 方法,requestLocationUpdates 方法有多重定
義,程式採用了下面這個定義:
public void requestLocationUpdates (String provider, long minTime,
float minDistance, LocationListener listener)
第一個參數填入定位提供者。第二個參數填入位置更新的最短間隔時間,以毫秒
為單位。第三個參數則填入位置更新的最短距離,以公尺為單位,這兩個參數決
定了回報的頻率,如果要獲得即時的位置資訊,可以將這兩個參數都設成 0。最
後一個參數則是填入傾聽者,傾聽者會處理位置更新時的對應動作,程式的傾聽
者是撰寫於 48~67 行,當位置更新時,會呼叫 TextView類別的 setText 方法將最
新位置顯示於 TextView。
至此,讀者對於整個程式已經有一定的瞭解了,唯一沒有細談的是 Criteria 類別,
Criteria類別提供了許多方法讓使用者設定定位提供者的“選擇偏好”,請讀者自
行閱讀相關說明文件[6]。
9.2 Google地圖開發金鑰 (v1)
Google地圖是 Android 系統的一大特點,然而基於某些原因 Google 地圖函式庫
是個選擇性的 API,若想在程式內使用相關 API,必須先取得 Google 地圖開發
金鑰[7],在一節中,我們將學習如何取得 Google地圖開發金鑰。
第一步是先找出憑證檔案並取得憑證檔案的 MD5認證指紋,在第四章我們有介
紹過如何利用 keytool 工具產生憑證檔案,使用者只要執行下面的命令,就能獲
得該憑證檔案的MD5 認證指紋,其中 Lincyu 即為第四章中所產生的憑證檔案:
keytool –list –keystore Lincyu
執行結果則如下圖所示:
然而這個憑證是用於上傳至實機上時使用的,在上傳至實機前,若想先使用模擬
器觀看執行結果,是無法使用這個憑證的。事實上,上傳至模擬器的 apk檔案會
使用一個除錯憑證,於Windows XP 下,這個除錯憑證是位於 C:\Documents and
Settings\Kasim\.android,為了能於模擬器上測試 Google地圖相關應用程式,使
用者也最好利用 keytool 取得除錯憑證的MD5 認證指紋,其中 keystore 密碼為
『android』。
取得MD5認證指紋後,我們接著要取得 Google地圖開發金鑰,我們需要透過下
面這個網址來取得:
http://code.google. com/android/maps-api-signup.html
連結上後,看完條款說明並勾選同意,接著輸入MD5認證指紋並按下『Generate
API Key』的按鈕,便可取得 Google地圖開發金鑰。按下按鈕後,在出現的頁面
中,會有類似下面這樣的程式片段:
<com.google.android.maps.MapView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="0ZwL91MFSujDY3L9Q2cjYL5elUTa8G4QpxLyrCQ"
/>
其中 0ZwL91MFSujDY3L9Q2cjYL5elUTa8G4QpxLyrCQ 即為 Google 地圖開發金
鑰。
9.3 MapView
這一節我們將示範如何於自己的應用程式中使用 Google地圖函式庫,讀者可引
進光碟中『\程式範例\Chapter9\AsiaUniversity1』這個專案閱讀範例程式。整個開
發流程可分成三個步驟來說明:
應用程式描述檔(AndroidManifest.xml)的修改
版面設計描述檔的撰寫
Java程式碼的撰寫
9.3.1應用程式描述檔(AndroidManifest.xml)的修改
由於 Google地圖函式庫是個選擇性的 API,因此必須在<application>標籤內使用
<uses-library>標籤讓應用程式能使用相關的函式庫,寫法如下:
<uses-library android:name="com.google.android.maps"/>
此外,由於應用程式需要連接上網際網路抓取圖資,因此利用<uses-permission>
標籤加上需要網際網路存取的許可:
<uses-permission android:name="android.permission.INTERNET">
9.3.2版面設計描述檔的撰寫
第二個步驟是於版面設計描述檔加上一個MapView介面元件,範例中版面設計
描述檔為/res/layout/main.xml,相關程式碼如下所示:
<com.google.android.maps.MapView
android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:apiKey="0ZwL91MFSujDqqn4vows4Cwmv2Wej5VN6D7LL_g"
android:clickable="true"
/>
其中 android:apiKey屬性的值是填入前一節中所得到的 Google地圖開發金鑰,
要注意的是當於模擬器上測試完畢,欲於實機上測試時,需記得使用正確的
Google地圖開發金鑰。而 android:clickable屬性則是讓這個介面元件可接受點擊
(Click)事件,如此使用者才能拖拉地圖。
9.3.3 Java程式碼的撰寫
最後一個步驟是撰寫相關的 Java程式碼。相關的 Java程式碼內容如下所示:
1 public class AsiaUniversity extends MapActivity {
2 private MapView map;
3 private MapController mc;
4
5 @Override
6 public void onCreate(Bundle savedInstanceState) {
7 super.onCreate(savedInstanceState);
8 setContentView(R.layout.main);
9 map = (MapView)findViewById(R.id.map);
10 mc = map.getController();
11 GeoPoint asiaU = new GeoPoint(24049178, 120687209);
12 mc.animateTo(asiaU);
13 map.setSatellite(true);
14 map.setBuiltInZoomControls(true);
15 }
16
17 @Override
18 protected boolean isRouteDisplayed() {
19 return false;
20 }
21 }
首先,由於這個 Activity會使用到 MapView介面元件[7],這個 Activity應該繼
承MapActivity,它幫我們實作了一些顯示 MapView必要的程式碼。第 9行利用
讀者已經相當熟悉的 findViewByid 方法取得 MapView物件。第 10 行則利用
getController方法取得這個MapView的MapController 物件,MapController類別
提供了許多方法讓我們去控制地圖[7],其中在第 12行我們使用了 animateTo 方
法,使得給定的座標能夠出現在地圖的中央,座標則是使用 GeoPoint 類別來表
示,GeoPoint 建構子的第一個參數是緯度,第二個參數是經度。程式中我們設定
地圖是以亞洲大學為中心呈現。第 13行,程式利用 setSatellite方法將地圖設成
衛星模式,其它類似的方法還包括 setTraffic 與 setStreetView[7]。最後第 14行則
是設定讓地圖出現內建的放大/縮小工具。最後要注意的是,實作MapActivity類
別的類別必須實作 isRouteDisplayed 方法。關於這些類別與方法的使用,可參閱
網站[7]的資料。程式的執行結果如下圖所示,從左側的圖可發現台灣確實位於
地圖的中央,若進一步利用縮放工具調整大小,讀者可發現亞洲大學確實位於地
圖的中央。
9.4 Google地圖進階應用
如果我們只是單純地使用 MapView來顯示地圖資訊,那麼跟直接使用 Google地
圖是一樣的,為了讓自己的應用程式不只是單純地顯示 Google地圖所提供的資
訊,而能顯示出 Google 地圖所沒有提供的資訊,例如附近的停車場、加油站等,
在一節中我們將學習如何在地圖上『畫』出我們想要的資訊。
如果我們想在地圖上畫一些記號,又不想破壞地圖本身,我們該怎麼做呢?我們
可以拿一個透明投影片放在地圖上,額外的記號都畫在透明投影片上。Google
地圖也是採用這樣的做法,我們可以準備好數個透明畫布,稱為 Overlay,把每
個 Overlay畫上記號後,覆蓋在地圖上即可顯示我們想顯示的資訊。
在這一節中我們會設計出三個 Overlay,第一個 Overlay是在使用者最新位置上
畫上記號,第二個 Overlay是將附近幾個重要景點畫上記號,第三個 Overlay是
在地圖上畫上一條直線。讀者可先引進光碟中『\程式範例
\Chapter9\AsiaUniversity2』這個專案,筆者先將程式碼列出,稍後才做深入的探
討:
1 public class AsiaUniversity extends MapActivity {
2 private MapView map;
3 private MapController mc;
4 private MyLocationOverlay mOverlay1;
5 private MarkerOverlay mOverlay2;
6 private DrawOverlay mOverlay3;
7
8 @Override
9 public void onCreate(Bundle savedInstanceState) {
10 super.onCreate(savedInstanceState);
11 setContentView(R.layout.main);
12
13 map = (MapView)findViewById(R.id.map);
14 mc = map.getController();
15 mc.setZoom(17);
16 map.setSatellite(true);
17 map.setBuiltInZoomControls(true);
18
19 List<Overlay> overlays;
20 overlays = map.getOverlays();
21
22 mOverlay1 = new MyLocationOverlay(this, map);
23 mOverlay1.runOnFirstFix(new Runnable() {
24 public void run() {
25 mc.animateTo(mOverlay1.getMyLocation());
26 }
27 });
28 overlays.add(mOverlay1);
29
30 Drawable marker;
31 marker = getResources().getDrawable(R.drawable.asiaicon);
32 mOverlay2 = new MarkerOverlay(marker, this);
33 overlays.add(mOverlay2);
34
35 mOverlay3 = new DrawOverlay();
36 overlays.add(mOverlay3);
37 }
38
39 @Override
40 protected void onResume() {
41 super.onResume();
42 mOverlay1.enableMyLocation();
43 }
44
45 @Override
46 protected void onStop() {
47 mOverlay1.disableMyLocation();
48 super.onStop();
49 }
50
51 @Override
52 protected boolean isRouteDisplayed() {
53 return false;
54 }
55 }
56
57 class MarkerOverlay extends ItemizedOverlay<OverlayItem> {
58
59 Context mCtx;
60
61 private List<OverlayItem> items = new ArrayList<OverlayItem>();
62
63 public MarkerOverlay(Drawable defaultMarker, Context mCtx) {
64 super(boundCenterBottom(defaultMarker));
65 this.mCtx = mCtx;
66 items.clear();
67 items.add(new OverlayItem(new GeoPoint(24045848, 120686010),
68 "資訊大樓", null));
69 items.add(new OverlayItem(new GeoPoint(24047179, 120686011),
70 "管理大樓", null));
71 items.add(new OverlayItem(new GeoPoint(24046066, 120686665),
72 "行政大樓", null));
73 populate();
74 }
75
76 @Override
77 protected OverlayItem createItem(int i) {
78 return items.get(i);
79 }
80
81 @Override
82 public int size() {
83 return items.size();
84 }
85
86 @Override
87 protected boolean onTap(int pIndex) {
88 Toast.makeText(mCtx, "此處是" + items.get(pIndex).getTitle(),
89 Toast.LENGTH_SHORT).show();
90 return true;
91 }
92 }
93
94 class DrawOverlay extends Overlay {
95 GeoPoint gp1, gp2;
96
97 public DrawOverlay() {
98 gp1 = new GeoPoint(24046066, 120686665);
99 gp2 = new GeoPoint(24045848, 120686010);
100 }
101
102 @Override
103 public void draw(Canvas canvas, MapView mapView,
104 boolean shadow) {
105 Projection projection = mapView.getProjection();
106 Point p1 = new Point();
107 Point p2 = new Point();
108 projection.toPixels(gp1, p1);
109 projection.toPixels(gp2, p2);
110
111 Paint paint = new Paint();
112 paint.setColor(Color.RED);
113 canvas.drawLine(p1.x, p1.y, p2.x, p2.y, paint);
114 }
115 }
經過 9.3 節的說明,讀者應該已瞭解 13~17 行的程式,唯一要解說的是第 15行,
MapController類別所提供的 setZoom 方法,可接收值從 1~21的整數,愈接近 1
代表顯示的區域愈大,資料愈粗略;反之,愈接近 21代表顯示的區域愈小,資
料愈詳盡。第 19行我們宣告了一個動態陣列,每個陣列的元素是一個 Overlay
物件[7],一個 Overlay物件就可以視為一張透明畫布。接著在第 20 行,程式利
用MapView類別的 getOverlays 取得動態陣列的物件實體,一開始這個動態陣列
是空的,沒有任何的元素,我們可以利用 add 方法幫這個動態陣列增加元素,在
這個範例程式中我們會增加三個元素,亦即會增加三張透明畫布,這三張透明畫
布會分別做以下的事情:
於目前位置上畫上記號。
將附近幾個重要景點畫上記號。
在地圖上畫上一條直線。
最後當透明畫布畫好後,只要將透明畫布覆蓋在地圖上,我們便能於地圖上顯示
客製化的資訊。下面我們將一一說明每個畫布的設計方法。
9.4.1 於目前位置上畫上記號
Google地圖函式庫提供了一個MyLocationOverlay類別來幫助我們於手機使用者
所在位置上畫上記號。第 22行我們建立了一個 MyLocationOverlay物件,建構
子需要兩參數,第一個參數需要填入一個 Context 物件,填入這個 Activity即可,
第二個參數是需要一個 MapView 物件,將之前利用 findViewById 方法取得的
MapView物件填入即可。
第 23行,我們呼叫了MyLocationOverlay類別的 runOnFirstFix方法,runOnFirstFix
是用來設定當新的位置資訊獲得時,程式應該做些什麼事,runOnFirstFix 方法需
要一個 Runnable物件當參數,實作 Runnable 介面的類別需要實作 run方法,當
程式獲得新的位置時,就會產生一個執行緒去執行 run方法內的程式,程式中我
們設定當新的位置獲得時,會移動地圖使得使用者的最新位置是位於螢幕的正中
央。由於MyLocationOverlay類別繼承了 Overlay類別,因此最後第 28 行我們將
mOverlay1 這個MyLocationOverlay物件加進動態陣列裡,如此地圖上就覆蓋了
一張透明畫布。MyLocationOverlay類別會在透明畫布上畫上一個藍色小圓圈代
表使用者的目前位置,如下圖所示(圖中只顯示了一張透明畫布):
此外,這個類別會試著從定位提供者(不管是 GPS 或基地台)獲取位置資訊,為了
節省電源,我們在 onResume方法內呼叫 enableMyLocation 方法,如此程式就會
隨時更新位置資訊,而於 onStop 方法內呼叫 disableMyLocation 方法以便停止位
置更新,最後要提醒讀者的是:別忘了於 AndroidManifest.xml 去聲明希望得到
ACCESS_FINE_LOCATION 與 ACCESS_COARSE_LOCATION 的許可。
9.4.2將附近幾個重要景點畫上記號
在第二張透明畫布裡,我們希望能幫一些重要景點標上記號,我們是利用 Google
地圖函式庫所提供的 ItemizedOverlay類別來達成,在程式 57~92行的地方我們
定義了MarkerOverlay類別,其繼承並實作了 ItemizedOverlay類別,類別內需要
一個 OverlayItem 的串列,程式第 61行,我們利用 ArrayList 類別產生這個動態
串列,串列中的每一個元素即代表一個景點。
程式在第63~74行的地方定義了MarkerOverlay的建構子,建構子需要兩個參數,
第一個參數是一個 Drawable物件,即我們欲顯示於地圖上的圖示,第二個是一
個 Context 物件,這是之後 Toast 介面元件需要用到的參數。在第 64 行,程式呼
叫了 ItemizedOverlay的建構子,其需要一個 Drawable物件當參數,這個 Drawable
物件必須有明確的邊界,ItemizedOverlay類別提供了 boundCenter方法與
boundCenterBottom方法讓我們調整Drawable物件的邊界。接著67~72行的地方,
我們幫 OverlayItem 加上三個元素,亦即三個景點,OverlayItem 物件的建構子需
要三個參數,第一個參數是景點的座標,第二個參數是景點的標題,第三個參數
是景點的片斷說明。加上三個元素後,我們必須呼叫 ItemizedOverlay 類別所提
供的 populate方法,其會呼叫 createItem方法,如此我們才能真正建立景點圖示。
實作 ItemizedOverlay類別的類別需實作 createItem 方法與 size方法,讀者可自行
參閱 76~84行的程式碼。此外程式於 86~91 行還覆寫了 onTap方法,這個方法是
用來設定當使用者輕敲景點圖示後,應該執行的動作,我們利用 Toast 介面元件
來顯示景點的名稱。最後要說明的是,如果需要顯示兩個圖示,例如一個是便利
商店的圖示,另一個是速食店的圖示,只要準備兩張透明畫布即可。下面為只顯
示第二張透明畫布的執行結果:
9.4.3在地圖上畫上一條直線
有時候我們希望能在地圖上畫上各式各樣的圖形,例如圓形、梯形等,在一節中
我們將示範如何在地圖上畫上一條直線,至於其它的圖形請讀者自行參閱相關類
別的說明文件。
要在地圖上畫圖可按下面的步驟進行,首先定義一個繼承 Overlay類別的類別,
並覆寫 draw方法就可以了。程式在第 94行定義了 DrawOverlay類別,其繼承了
Overlay類別,而 Overlay類別裡的 draw方法有多重定義,我們覆寫了下面這個
定義:
void draw(Canvas canvas, MapView mapView, boolean shadow)
其中 Canvas 物件就當於畫布[8],我們可以利用其所提供的方法(Methods)在上面
畫上一些圖案,程式在第 113行利用 Canvas 類別的 drawLine方法畫出直線,
drawLine方法需要五個參數,第五個參數是畫筆,程式在第 111行宣告了一個畫
筆物件,並且在第 112行將畫筆設成紅色。
drawLine方法的第一個與第二個參數是起點的 X軸與 Y軸座標,第三個與第四
個參數則是終點的 X軸與 Y軸座標,這裡所謂的 X 軸與 Y 軸座標是指『螢幕』
的 X軸與 Y軸座標,如果螢幕大小為 320480,則左上角為(0, 0),右下角為(320,
480),如果現在我們現在想在行政大樓與資訊大樓這兩個景點畫上直線,該如何
把經緯度轉換成螢幕座標呢?我們可利用Google地圖函式庫所提供的 Projection
介面,首先利用MapView 類別的 getProjection 取得 Projection 物件,接著再利用
toPixels 方法,就可將經緯度轉換成螢幕座標。
三張透明畫布都準備好後,只要將這幾張畫布加入 Overlay串列即可將這些透明
畫布疊在地圖上,最後執行結果如下圖所示:
9.5 Google Maps Android API v2
9.5.1開發金鑰的取得
所有關於 v2 的說明讀者可參考[9],首先我們一樣利用『keytool -list -v -keystore
mykeystore』查看憑證檔的資訊,然而我們需要的是 SHA1 而不再是 MD5,取得
SHA1 之後,請讀者利用自己的 Gmail 帳號進入 Google APIs Console 並建立一個
新的專案,如下圖所示:
進入 Services 設定選定的 Project 需要哪些服務,將 Google Maps Android API v2
打開,如下圖所示:
接著進入 API Access,點選 Create new Android key,如下圖所示:
於輸入框內輸入 SHA1,需注意的是 SHA1 後必須跟著一個分號「;」,分號後則
必須加上 Package Name。
經過上面的步驟我們便可順利取得我們所需要的 API Key。
9.5.2本地端應用程式與環境設定
在本地端我們首先要確認我們是否有安裝 Google Play services,我們首先開啓
SDK Manager,觀看是否有安裝(位於 Extras 資料夾),若無安裝則進行安裝動
作。
安裝完畢後,我們就可以匯入所需的專案,點選 FileImport後選擇 Android
Existing Android Code into Workspace。匯入
(SDK_Home)/extras/google/google_play_services/libproject/google-play-services_lib
專案,當專案出現在 Project Explorer子視窗時,就代表成功匯入了。
緊接著我們就可以建立我們自己的地圖應用專案,提醒一下的是,專案的 Package
Name必須和之前取得 API Key所輸入的 Package Name 相同,建立好專案後,
AndroidManifest.xml 有幾個地方需要修改:
1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="lincyu.example.googlemapv2"
4 android:versionCode="1"
5 android:versionName="1.0" >
6
7 <uses-sdk
8 android:minSdkVersion="8"
9 android:targetSdkVersion="17"/>
10
11 <permission
12 android:name="lincyu.example.googlemapv2.permission.MAPS_RECEIVE"
13 android:protectionLevel="signature"/>
14 <uses-permission
15 android:name="lincyu.example.googlemapv2.permission.MAPS_RECEIVE"/>
16 <uses-permission
17 android:name="android.permission.INTERNET"/>
18 <uses-permission
19 android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
20 <uses-permission
21 android:name="com.google.android.providers.gsf.permission.READ_GSERVICES"/>
22 <uses-permission
23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
24 <uses-permission
25 android:name="android.permission.ACCESS_FINE_LOCATION"/>
26 <uses-feature
27 android:glEsVersion="0x00020000" android:required="true"/>
28
29 <application
30 android:allowBackup="true"
31 android:icon="@drawable/ic_launcher"
32 android:label="@string/app_name"
33 android:theme="@style/AppTheme" >
34 <activity
35 android:name="lincyu.example.googlemapv2.MainActivity"
36 android:label="@string/app_name" >
37 <intent-filter>
38 <action android:name="android.intent.action.MAIN" />
39 <category android:name="android.intent.category.LAUNCHER" />
40 </intent-filter>
41 </activity>
42 <meta-data
43 android:name="com.google.android.maps.v2.API_KEY"
44 android:value="AIzaSyCKPTixho4O5db47Xnncq7kUko_xTGuVbM"/>
45 </application>
46 </manifest>
在官方網站中,有一個簡單的範例讓開發者可以快速地測試 Google Maps 是否運
作正常,然而該範例需要支援 API Level 12 (以上)的實機才能執行,因此筆
者於下一節中另外提供了一個簡單範例,於 API Level 8 的實機上就可以執行。
9.5.3範例
1 package lincyu.example.googlemapv2;
2
3 import android.app.Activity;
4 import android.os.Bundle;
5 import android.view.Menu;
6
7 import com.google.android.gms.maps.GoogleMap;
8 import com.google.android.gms.maps.GoogleMapOptions;
9 import com.google.android.gms.maps.MapView;
10 import com.google.android.gms.maps.model.LatLng;
11 import com.google.android.gms.maps.model.MarkerOptions;
12
13 public class MainActivity extends Activity {
14
15 MapView mapview;
16 GoogleMap googlemap;
17
18 @Override
19 protected void onCreate(Bundle savedInstanceState) {
20 super.onCreate(savedInstanceState);
21 setContentView(R.layout.activity_main);
22 mapview = (MapView) findViewById(R.id.map);
23 mapview.onCreate(savedInstanceState);
24 }
25
26 @Override
27 public void onPause() {
28 super.onPause();
29 mapview.onPause();
30 }
31
32 @Override
33 public void onResume() {
34 super.onResume();
35 mapview.onResume();
36
37 googlemap = mapview.getMap();
38 MarkerOptions mo = new MarkerOptions();
39 LatLng ll = new LatLng(24.79702,120.998096);
40 mo.position(ll);
41 mo.title("NCTU");
42 googlemap.addMarker(mo);
43 }
44
45 @Override
46 public void onDestroy() {
47 super.onDestroy();
48 mapview.onDestroy();
49 }
50
51 @Override
52 public boolean onCreateOptionsMenu(Menu menu) {
53 // Inflate the menu; this adds items to the action bar if it is present.
54 getMenuInflater().inflate(R.menu.activity_main, menu);
55 return true;
56 }
57 }
9.6 摘要
本章我們介紹了位置資訊的取得方式與 Google地圖函式庫的使用。Android系統
目前提供了兩種定位方式:GPS 與基地台,我們學習了 LocationManager類別的
使用,此外我們也學會如何於 AndroidManifest.xml 聲明希望得到 GPS 或基地台
所提供的位置資訊。Google地圖函式庫也是本章的重點之一,首先我們學習了
Google地圖開發金鑰的取得方法,接著學習了 MapView類別的使用,而為了讓
地圖能呈現自己設計的地標等資訊,我們也學習了透明畫布的概念與設計方法。
定位與 Google地圖的結合產生了許多新的應用,未來一定會有更多的位置感知
服務被人們所使用。
9.7 作業
1. 撰寫一個台灣大專院校地圖查詢應用程式。這個應用程式一開始會利用列表
介面元件顯示數所大學的名稱,使用者點選下去後,會出現該校的地圖。(提
示:可利用 Google Earth 查詢相關地點的經緯度)
2. 於地圖上標出兩種圖示,例如便利商店的圖示與速食店的圖示。
3. 於地圖上畫上各式個樣的圖形。
9.8 參考資料
[1] Global Positioning System - Wikipedia, the free encyclopedia,
http://en.wikipedia.org/wiki/Global_Positioning_System
[2] Permissions | Android Developers,
http://developer.android.com/guide/topics/security/permissions.html
[3] Manifest.permission | Android Developers,
http://developer.android.com/reference/android/Manifest.permission.html
[4] LocationManager | Android Developers,
http://developer.android.com/reference/android/location/LocationManager.html
[5] LocationListener | Android Developers,
http://developer.android.com/reference/android/location/LocationListener.html
[6] Criteria | Android Developers,
http://developer.android.com/reference/android/location/Criteria.html
[7] Google Maps Android API v2,
https://developers.google.com/maps/documentation/android/start#installing_the_goog
le_maps_android_v2_api
[8] Canvas | Android Developers,
http://developer.android.com/reference/android/graphics/Canvas.html
[9] Google Maps Android API v2 - Google Developers,
https://developers.google.com/maps/documentation/android/
top related