Google 地圖與定位服務

Download Report

Transcript Google 地圖與定位服務

第14章 Google地圖與定位服務





14-1 定位服務-我在哪裡
14-2 地圖解碼服務-找出景點座標
14-3 本地服務與定位應用-GPS景點防撞雷達
14-4 使用Google Maps API-My地圖
14-5 標記Google地圖-追蹤個人行蹤
14-1 定位服務-我在哪裡
 14-1-1 Android的定位服務與座標
 14-1-2 使用定位服務-我在哪裡
14-1-1 Android的定位服務與座標-說明
 Android行動裝置結合定位功能和Google地圖建立
的「位置感知服務」(Location-based Service,
LBS),這是一項十分實用的功能,LBS應用程式
可以追蹤你的位置和提供一些額外服務,例如:
找出附近的咖啡廳、停車場、自動櫃員機或加油
站等。
 Android作業系統提供LocationManager類別的定位
服務來幫助我們存取行動裝置目前的定位資料,
包含:緯度(Latitude)、經度(Longitude)和高
度(Altitude)等。
14-1-1 Android的定位服務與座標-種類
 GPS定位提供者:提供者名稱字串為"gps",它是
使用GPS(Global Positioning System)的衛星訊號
來定位,可以提供精確的位置資訊,但是無法收
到衛星訊號的室內並無法使用。
 網路定位提供者;提供者名稱字串為"network",
它是直接使用電信公司基地台來執行三角定位,
其提供的位置資訊較不精確,但是可以在室內使
用。
14-1-1 Android的定位服務與座標-座標
 定位服務最主要的目的是找出行動裝置目前位置的經緯度
座標,經緯度是經度與緯度合稱的座標系統,也稱為地理
座標系統,它是使用三度空間的球面來定義地球表面各點
的座標系統,能夠標示地球表面上的任何一個位置。經度
與緯度的說明,如下所示:
• 緯度:地球表面某一點距離地球赤道以南或以北的度數,其值為0
至90度,赤道以北的緯度叫北緯(符號為N);赤道以南的緯度稱
南緯(符號為S)。
• 經度:地球表面上某一點距離本初子午線(一條南北方向經過倫
敦格林威治天文台舊址的子午線)以東或以西的度數,簡單的說
,本初子午線的經度是0度,其他地點的經度是向東從0到180度,
即東經(符號為W)或向西從0到180度,即西經(符號為E)。
14-1-2 使用定位服務-我在哪裡(說明)
 我在哪裡是定位服務的最簡單應用,可以顯示目
前行動裝置的經緯度座標。
14-1-2 使用定位服務-我在哪裡
步驟一:開啟和執行Android專案
 請啟動Eclipse IDE開啟Android專
案Ch14_1_2,內含1個Java類別
檔和版面配置檔main.xml,執行
可以看到程式顯示目前的位置座
標,按【顯示Google地圖】鈕,
可以啟動Google地圖顯示此座標
附近的地圖,即台北火車站,如
下圖所示:
14-1-2 使用定位服務-我在哪裡
步驟二:建立我在哪裡使用介面的版面配置
 我在哪裡使用介面的版面配置是定義在main.xml檔,使用
LinearLayout垂直編排1個TextView和Button元件,如下所示
:
<TextView android:id="@+id/output"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="顯示Google地圖"
android:onClick="button1_Click"/>
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-1
 在Ch14_1_2Activity活動類別的開頭宣告成員的
LocationManager和Location物件變數,如下所示:
public class Ch14_1_2Activity extends Activity {
private LocationManager manager;
private Location currentLocation;
private String best;
…
}
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-2
onCreate()方法
 在覆寫的onCreate()方法載入版面配置後,可以取得系統服務的
LocationManager物件,if條件檢查是否有啟用GPS,如下所示:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
manager = (LocationManager)getSystemService(
LOCATION_SERVICE);
if (!manager.isProviderEnabled(
LocationManager.GPS_PROVIDER)) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("定位管理")
.setMessage("GPS目前狀態是尚未啟用.\n"
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-3
+"請問你是否現在就設定啟用GPS?")
.setPositiveButton("啟用", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent i = new Intent(Settings.
ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(i);
}
})
.setNegativeButton("不啟用", null).create().show();
}
}
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-4
onResume()方法
 在覆寫onResume()方法建立Criteria物件設定如何選擇提供者,以便取
得最佳或符合你需求的定位提供者,然後就可以呼叫
getrBestProvider()方法取得最佳提供者字串,參數是Criteria物件,如
下所示:
@Override
protected void onResume() {
super.onResume();
Criteria criteria = new Criteria();
best = manager.getBestProvider(criteria, true);
int minTime = 5000; // 毫秒
float minDistance = 5; // 公尺
if (best != null) {
currentLocation = manager.getLastKnownLocation(best);
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-5
manager.requestLocationUpdates(best, minTime,
minDistance, listener);
}
else {
currentLocation = manager.getLastKnownLocation(
LocationManager.GPS_PROVIDER);
manager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
minTime, minDistance, listener);
}
updatePosition();
}
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-6
onPause()方法
 在覆寫onPause()方法呼叫removeUpdates()方法取
消周期更新位置,如下所示:
@Override
protected void onPause() {
super.onPause();
manager.removeUpdates(listener);
}
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-7
updatePosition()方法
 在自訂updatePosition()方法更新TextView元件顯示的位置
資訊,如下所示:
private void updatePosition() {
TextView output;
output = (TextView) findViewById(R.id.output);
if (currentLocation == null) {
output.setText("取得定位資訊中...");
} else {
output.setText(getLocationInfo(currentLocation));
}
}
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-8
LocationListener傾聽者物件
 使用匿名內層類別實作LocationListener介面來建立此物件,需要實作4
個方法,如下所示:
private LocationListener listener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
currentLocation = location;
updatePosition();
}
@Override
public void onProviderDisabled(String provider) { }
@Override
public void onProviderEnabled(String provider) { }
@Override
public void onStatusChanged(String provider,
int status, Bundle extras) { }
};
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-9
getLocationInfo()方法
 在自訂的getLocationInfo()方法可以從參數Loaction物件取得定位資訊
,如下所示:
public String getLocationInfo(Location location) {
StringBuffer str = new StringBuffer();
str.append("定位提供者(Provider): "+
location.getProvider());
str.append("\n緯度(Latitude): " +
Double.toString(location.getLatitude()));
str.append("\n經度(Longitude): " +
Double.toString(location.getLongitude()));
str.append("\n高度(Altitude): " +
Double.toString(location.getAltitude()));
return str.toString();
}
14-1-2 使用定位服務-我在哪裡
步驟三:建立Activity活動類別使用定位服務-10
button1_Click()事件處理方法
 button1_Click()事件處理方法可以依據目前的位置座標來啟
動Google地圖,它是使用Intent物件啟動Google地圖程式,
首先取得經緯度座標longitude和latitude,如下所示:
public void button1_Click(View view) {
float latitude = (float) currentLocation.getLatitude();
float longitude = (float) currentLocation.getLongitude();
String uri = String.format("geo:%f,%f?z=18", latitude, longitude);
Intent geoMap = new Intent(
Intent.ACTION_VIEW,Uri.parse(uri));
startActivity(geoMap);
}
14-1-2 使用定位服務-我在哪裡
步驟四:在AndroidManifest.xml新增權限
 因為需要使用定位服務,所以在
AndroidManifest.xml檔新增2個權限,
ACCESS_COURSE_LOCATION是網路定位服務,
ACCESS_FINE_LOCATION是GPS定位服務,如下所示
:
<uses-permission android:name=
"android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name=
"android.permission.ACCESS_FINE_LOCATION"/>
14-2 地圖解碼服務-找出景點座標(說明)
 地圖解碼服務(Geocoding Services)可以從位置
名稱、郵遞區號等資訊來找出經緯度座標,或反
過來,從經緯度座標找出位置名稱或地址。
 Android是使用Geocoder類別來處理座標轉換,相
關方法的說明,如下表所示:
方法
說明
getFromLocation()
將經緯度座標轉換成地址資訊,目前台灣
只能轉換成所屬鄉鎮區和郵遞區號
getFromLocationName()
將位置名稱或地址轉換成經緯度座標
14-2 地圖解碼服務-找出景點座標
步驟一:開啟和執行Android專案
 請啟動Eclipse IDE開啟Android專案Ch14_2,內含1
個Java類別檔和版面配置檔main.xml,因為
Android模擬器不支援地圖解碼服務,筆者是使用
2.3版的實機來測試,其執行結果如下圖所示:
14-2 地圖解碼服務-找出景點座標
步驟二:建立找出景點座標使用介面的版面配置-1
 找出景點座標使用介面的版面配置是定義在main.xml檔,
使用LinearLayout垂直編排數個水平的LinearLayout,內含
TextView和EditText元件輸入座標和景點名稱,如下所示:
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="緯度(Latitude):"/>
14-2 地圖解碼服務-找出景點座標
步驟二:建立找出景點座標使用介面的版面配置-2
<EditText android:id="@+id/txtLat"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="25.047924"
android:inputType="numberDecimal"/>
</LinearLayout>
 EditText元件輸入緯度,另有2個是輸入經度和景點名稱,
查詢結果的地址是顯示在Spinner元件,如下所示:
<Spinner android:id="@+id/addresslist"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
 至於查詢的經緯度是顯示在TextView元件,Button元件
button1~3的事件處理方法為button1~3_Click()。
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-1
 在Ch14_2Activity活動類別的開頭宣告成員的
ArrayAdapter和Geocoder等物件變數,如下所示:
public class Ch14_2Activity extends Activity {
private final int maxResult = 3;
private String addressList[] = new String[maxResult];
private ArrayAdapter<String> adapter;
private TextView output;
private Geocoder geocoder;
private EditText lat, lon;
…
}
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-2
onCreate()方法
 在覆寫的onCreate()方法載入版面配置後,可以取得
EditText物件來取得輸入座標,如下所示:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
lat = (EditText) findViewById(R.id.txtLat);
lon = (EditText) findViewById(R.id.txtLong);
output = (TextView)findViewById(R.id.output);
geocoder = new Geocoder(this, Locale.TAIWAN);
}
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-3
button1_Click()事件處理方法
 在button1_Click()事件處理方法將經緯度座標轉換成地址,
首先將經緯度的輸入字串轉換成float浮點數,如下所示:
public void button1_Click(View view) {
float latitude = Float.parseFloat(lat.getText().toString());
float longitude = Float.parseFloat(lon.getText().toString());
try {
List<Address> listAddress = geocoder.getFromLocation(
latitude, longitude, maxResult);
 程式碼呼叫Geocoder物件的getFromLocation()方法取得地
址清單的List物件,參數依序是緯度、經度和最多傳回的
地址數。
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-4
 如果有傳回地址,就在下方if條件的程式區塊建立ArrayAdapter結合器物件,然後顯示
在Spinner元件,如下所示:
if (listAddress != null) {
Spinner spinner = (Spinner)findViewById(R.id.addresslist);
for (int j = 0; j < maxResult; j++) addressList[j] = "N/A";
int index = 0;
for (int j = 0; j < maxResult; j++) {
Address findAddress = listAddress.get(j);
StringBuilder strAddress = new StringBuilder();
for (int i = 0; i <
findAddress.getMaxAddressLineIndex(); i++) {
String str = findAddress.getAddressLine(i);
strAddress.append(str).append("\n");
}
if (strAddress.length() > 0) {
addressList[index++] = strAddress.toString();
}
}
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-5
 在建立addressList[]陣列後,可以將此陣列建立成ArrayAdapter結合器物件,如
下所示:
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, addressList);
adapter.setDropDownViewResource(android.R.layout.
simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
}
else {
output.setText("注意: 沒有傳回地址資料!");
}
} catch (Exception ex) {
output.setText("錯誤:" + ex.toString());
}
}
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-6
button2_Click()事件處理方法
 在button2_Click()事件處理方法將地址轉換成經緯度座標,
首先取得使用者在EditText元件輸入的名稱,如下所示:
public void button2_Click(View view) {
EditText address = (EditText) findViewById(R.id.txtAddress);
String addressName = address.getText().toString();
try {
List<Address> listGPSAddress = geocoder.
getFromLocationName(addressName, 1);
 上述程式碼呼叫Geocoder物件的getFromLocationName()方
法搜尋經緯度座標,第1個參數是名稱,第2個參數最多傳
回幾個座標。
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-7
 如果有傳回座標,就取出緯度和經度且將它顯示出來,如下所示:
if (listGPSAddress != null) {
double latitude = listGPSAddress.get(0).getLatitude();
double longitude = listGPSAddress.get(0).getLongitude();
output.setText("緯度: " + latitude +
"\n經度: " + longitude);
lat.setText(String.valueOf(latitude));
lon.setText(String.valueOf(longitude));
}
} catch (Exception ex) {
output.setText("錯誤:" + ex.toString());
}
}
14-2 地圖解碼服務-找出景點座標
步驟三:建立Activity活動類別找出景點座標-8
button3_Click()事件處理方法
 button3_Click()事件處理方法使用Intent物件啟動
Google地圖,它和第14-1-2節的button1_Click()事
件處理方法相同,筆者就不重複說明。
14-2 地圖解碼服務-找出景點座標
步驟四:在AndroidManifest.xml新增存取Internet權限
 找出景點座標因為需要連線Internet,所以在
AndroidManifest.xml檔需要新增INTERNET權限,如
下所示:
<uses-permission
android:name="android.permission.INTERNET"/>
14-3 本地服務與定位應用-GPS景點防撞雷達
 GPS景點防撞雷達是本地服務與定位服務的應用,
可以在行動裝置接近輸入的景點座標時,發出警
告,其他可能的應用包含危險區域警示,和標示
目標區域來檢查是否已經到達其範圍之中。
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟一:開啟和執行Android專案
 請啟動Eclipse IDE開啟Android專案Ch14_3,內含3
個Java類別檔和版面配置檔main.xml,在執行前,
請先在Android模擬器新增GPS硬體支援,然後就
可以執行此專案,其執行結果如下圖所示:
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟二:建立GPS景點防撞雷達使用介面的版面配置
 GPS景點防撞雷達使用介面的版面配置是定義在main.xml檔,使用
LinearLayout垂直編排3個水平LinearLayout,內含TextView和EditText元
件輸入座標和Button按鈕元件,如下所示:
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="經度(Longitude):"/>
<EditText android:id="@+id/txtLong"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="121.51617"
android:inputType="numberDecimal"/>
</LinearLayout>
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟三:建立Activity活動類別的事件處理方法-1
 在Ch14_3Activity活動類別的開頭宣告成員的
EditText和TextView物件變數,如下所示:
public class Ch14_3Activity extends Activity {
private EditText lat, lon;
private TextView output;
…
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟三:建立Activity活動類別的事件處理方法-2
onCreate()方法
 在覆寫的onCreate()方法載入版面配置後,可以取得
EditText和TextView物件,如下所示:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
lat = (EditText) findViewById(R.id.txtLat);
lon = (EditText) findViewById(R.id.txtLong);
output = (TextView) findViewById(R.id.output);
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟三:建立Activity活動類別的事件處理方法-3
start_Click()事件處理方法
 在start_Click()事件處理方法建立Intent物件後,呼叫
startService()方法啟動GPSService.class服務,並且傳遞經緯
度座標LATITUDE和LONGITUDE,如下所示:
public void start_Click(View view) {
float latitude = Float.parseFloat(lat.getText().toString());
float longitude = Float.parseFloat(lon.getText().toString());
Intent intent = new Intent(this, GPSService.class);
intent.putExtra("LATITUDE", latitude);
intent.putExtra("LONGITUDE", longitude);
startService(intent);
output.setText("服務啟動中...");
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟三:建立Activity活動類別的事件處理方法-4
stop_Click()事件處理方法
 在stop_Click()事件處理方法建立Intent物件後,呼
叫stopService()方法停止GPSService.class服務,如
下所示:
public void stop_Click(View view) {
Intent intent = new Intent(this, GPSService.class);
stopService(intent);
output.setText("服務停止中...");
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟三:建立Activity活動類別的事件處理方法-5
finish_Click()事件處理方法
 在finish_Click()事件處理方法呼叫finish()方法結束
活動,如下所示:
public void finish_Click(View view) {
finish();
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-1
 在GPSService服務類別使用GPS定位服務來檢查是否距離景
點100公尺以內,如果是,送出廣播給GPSReceiver類別。
 GPSService服務類別繼承Service類別且實作LocationListener
介面,在開頭宣告LocationManager物件變數,如下所示:
public class GPSService extends Service
implements LocationListener {
private LocationManager manager;
private boolean isInArea;
private double latitude, longitude;
….
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-2
onCreate()方法
 在onCreate()方法取得LOCATION_SERVICE系統服務的
LocationManager物件後,註冊類別本身為傾聽者物件來定
時更新位置資訊,如下所示:
@Override
public void onCreate() {
manager = (LocationManager)
getSystemService(LOCATION_SERVICE);
manager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 10000, 1, this);
isInArea = false;
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-3
onStartCommand()方法
 在onStartCommand()方法取得Intent物件傳遞的經緯度座標LATITUDE和
LONGITUDE,如下所示:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
latitude = (double) intent.getFloatExtra(
"LATITUDE", 40.422005f);
longitude = (double) intent.getFloatExtra(
"LONGITUDE", -122.084095f);
Log.d("GPSService", "lat/long: "+latitude+": "+longitude);
return START_STICKY;
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-4
onDestroy()方法
 在onDestroy()方法呼叫removeUpdates()方法移除
定位服務的位置更新,如下所示:
@Override
public void onDestroy() {
manager.removeUpdates(this);
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-5
onBind()方法
 onBind()方法並沒有使用,但是因為是抽象方法,
類別一定要實作,所以傳回null,如下所示:
@Override
public IBinder onBind(Intent intent) {
return null;
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-6
實作LocationListener介面的4個方法
 因為GPSService服務類別本身是實作LocationListener介面的傾聽者物件
,所以需要實作介面的4個方法,不過,我們只有使用
onLocationChanged()方法,如下所示:
@Override
public void onLocationChanged(Location current) {
if (current == null) return;
Location dest = new Location(current);
dest.setLatitude(latitude);
dest.setLongitude(longitude);
float distance = current.distanceTo(dest);
Log.d("Ch14_3", "距離: " + distance);
 程式碼在建立目標座標的Location物件後,呼叫參數current目前
Location物件的distanceTo()方法,參數是目標的Location物件,可以計
算2個座標之間的距離是多少公尺。
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟四:建立GPSService服務類別-7
 在下方if條件檢查距離是否小於100公尺,如果是,建立Intent物件送
出廣播,如下所示:
if (distance < 100.0) {
if (isInArea == false) {
Intent intent = new Intent(
"android.broadcast.LOCATION");
sendBroadcast(intent);
isInArea = true;
}
} else {
isInArea = false;
}
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟五:建立GPSReceiver廣播接收器類別
 GPSReceiver廣播接收器類別是用來接收GPSService服務送出的
android.broadcast.LOCATION廣播,可以使用Toast類別顯示訊息,和振
動提醒已經接近景點範圍,如下所示:
public class GPSReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "已經接近景點範圍",
Toast.LENGTH_LONG).show();
Vibrator vibrator = (Vibrator) context.getSystemService(
Context.VIBRATOR_SERVICE);
vibrator.vibrate(500); // 半秒
}
}
14-3 本地服務與定位應用-GPS景點防撞雷達
步驟六:在AndroidManifest.xml註冊元件和新增權限
 GPSService服務和GPSReceiver廣播接收器需要在AndroidManifest.xml檔
註冊,可以處理android.broadcast.LOCATION的廣播,如下所示:
<receiver android:name=".GPSReceiver">
<intent-filter>
<action android:name="android.broadcast.LOCATION" />
</intent-filter>
</receiver>
<service android:name=".GPSService"/>
 GPS景點防撞雷達需要使用定位服務和振動,所以需要新增2個權限,
如下所示:
<uses-permission android:name=
"android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.VIBRATE"/>
14-4 使用Google Maps API-My地圖
 14-4-1 取得Google Maps API金鑰
 14-4-2 使用MapView元件和MapActivity類別-My地
圖
14-4 使用Google Maps API-My地圖
 Google Map(地圖)是一套內建Android作業系統
的應用程式,在本節前我們已經使用Intent物件啟
動地圖應用程式,顯示指定經緯度座標的地圖。
 在實務上,我們可能需要將Google Map內嵌在
Android應用程式來建立所需的應用,不過,因為
Google Map並不包含在Android SDK,請注意!我
們需要取得Google Maps API金鑰,和在Build
Target選Google API,才能在Android應用程式使用
Google Map。
14-4-1 取得Google Maps API金鑰
 在將Google Map整合至你的Android應用程式之前
,我們需要先取得免費的Google Maps API金鑰。
對於在Android模擬器或連接開發電腦實機上測試
執行應用程式所需的金鑰,我們是使用SDK偵錯憑
證(Debug Certificate)來申請金鑰。
• 步驟一:取得SDK偵錯憑證的路徑
• 步驟二:取出申請所需MD5認證指紋碼
• 步驟三:上網申請金鑰
14-4-2 使用MapView元件和MapActivity類別My地圖
 在取得Google Maps API金鑰後,我們就可以使用
MapView元件和繼承MapActivity抽象類別來建立
自己的地圖程式,My地圖是使用GPS定位服務,
當位置座標改變時,馬上更新地圖顯示的位置。
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟一:開啟和執行Android專案
 請啟動Eclipse IDE開啟Android專案Ch14_4_2,然
後就可以執行此專案,內含1個Java類別檔和版面
配置檔main.xml,其執行結果如下圖所示:
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟二:建立MapView元件的版面配置
 My地圖使用介面的版面配置是定義在main.xml檔,使用
LinearLayout垂直編排1個TextView和MapView元件,如下所
示:
<TextView android:id="@+id/output"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="在地圖上顯示你的位置"/>
<com.google.android.maps.MapView
android:id="@+id/mapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="0JNEnTMsG9nVME5Lbx2LxFQr1zTh65aObDEbzsw"/>
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務1
 在Ch14_4_2Activity活動類別因為使用MapView元件,所以
需要繼承MapActivity抽象類別,而不是Activity類別,因為
MapActivity抽象類別已經實作一些顯示MapView元件所需
的方法,如下所示:
public class Ch14_4_2Activity extends MapActivity {
private MapView mapView;
private TextView output;
private MapController mc;
private GeoPoint gp;
private LocationManager manager;
private LocationListener locationListener;
…
}
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務2
onCreate()方法
 在覆寫的onCreate()方法載入版面配置後,就可以取得系統服務的
LocationManager物件,if條件檢查是否有啟用GPS,如下所示:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
output = (TextView) findViewById(R.id.output);
manager = (LocationManager)
getSystemService(LOCATION_SERVICE);
if (!manager.isProviderEnabled(
LocationManager.GPS_PROVIDER)) {
…
}
 上述if條件的程式碼和第14-1-2節的onCreate()方法相同,筆者就不重複說明。
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務3
 在下方取得MapView物件後,呼叫
setBuiltInZoomControls()方法,參數true是使用內
建縮放控制,如下所示:
mapView = (MapView)
findViewById(R.id.mapView);
mapView.setBuiltInZoomControls(true);
mapView.setSatellite(false);
 setSatellite()方法可以指定成衛星空照圖的顯示方
式,因為參數是false,就是使用標準方式來顯示
。
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務4
 在下方呼叫getController()方法取得MapController
物件後,取得預設的經緯度座標lng和lat,如下所
示:
mc = mapView.getController();
double lat = Double.parseDouble("25.06924");
double lng = Double.parseDouble("121.51617");
gp = new GeoPoint((int) (lat * 1E6),(int) (lng * 1E6));
mc.animateTo(gp);
mc.setZoom(18);
}
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務5
onResume()方法
 在覆寫的onResume()方法建立定位服務的傾聽者物件,即之後的內層類別
GPSLocationListener,在設定更新位置頻率的條件後,呼叫
requestLocationUpdates()方法註冊傾聽者物件來周期性回報目前的位置,如下
所示:
@Override
protected void onResume() {
super.onResume();
locationListener = new GPSLocationListener();
int minTime = 1000; // 毫秒
loat minDistance = 1; // 公尺
manager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
minTime, minDistance,locationListener);
manager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
minTime, minDistance,locationListener);
}
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務6
實作LocationListener介面的內層類別
 實作LocationListener介面的內層類別就是用來建立定位服務更新位置的傾聽者物件,介
面需實作4個方法,不過,我們只有使用onLocationChanged()方法,如下所示:
class GPSLocationListener implements LocationListener {
@Override
public void onLocationChanged(Location current) {
double lat, lng;
if (current != null) {
lat = current.getLatitude();
lng = current.getLongitude();
Toast.makeText(Ch14_4_2Activity.this,
"經緯度座標變更....", Toast.LENGTH_SHORT).show();
output.setText("緯度: " + lat + " 經度: " + lng);
gp = new GeoPoint((int) (lat * 1E6),(int) (lng * 1E6));
mc.animateTo(gp);
mc.setZoom(18);
}
}
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務7
 後面的3個實作方法並沒有使用,如下所示:
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onStatusChanged(String provider,
int status, Bundle extras) {
}
}
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟三:建立MapActivity活動類別和使用定位服務8
isRouteDisplayed()方法
 因為MapActivity是抽象類別,繼承的類別一定要
實作isRouteDisplayed()抽象方法,傳回是否顯示任
何規劃路徑,沒有傳回false,如下所示:
@Override
protected boolean isRouteDisplayed() {
return false;
}
14-4-2 使用MapView元件和MapActivity類別-My地圖
步驟四:在AndroidManifest.xml註冊函數庫和新增權限
 因為Google Maps API不是Android內建函數庫,所以當
Android應用程式使用Google Maps API時,我們需要在
application元素新增uses-library子元素,讓應用程式可以使
用此函數庫,如下所示:
<uses-library android:name="com.google.android.maps"/>
 My地圖需要存取網路和使用定位服務,所以需要新增2個
權限,如下所示:
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission android:name=
"android.permission.ACCESS_FINE_LOCATION"/>
14-5 標記Google地圖-追蹤個人行蹤
 實務上,我們不只可以使用MapView元件和
MapActivity類別來建立地圖程式,更可以在地圖
上作記號,例如:標記加油站、停車場位置或個
人行蹤等。
 標記Google地圖是使用Overlay類別,它如同是一
片透明投影片,可以讓我們在上面作記號,然後
如同圖層般一層一層疊在地圖上。
14-5 標記Google地圖-追蹤個人行蹤
步驟一:開啟和執行Android專案
 請啟動Eclipse IDE開啟
Android專案Ch14_5,就可
以執行此專案,內含3個
Java類別檔和2個版面配置
檔,其執行結果如下圖所
示:
14-5 標記Google地圖-追蹤個人行蹤
步驟二:建立主活動使用介面的版面配置-1
 主活動使用介面的版面配置是定義在main.xml檔,使用
LinearLayout垂直編排2個TextView元件和1個水平的
LinearLayout,內含2個Button元件,如下所示:
<TextView android:id="@+id/output"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
14-5 標記Google地圖-追蹤個人行蹤
步驟二:建立主活動使用介面的版面配置-2
android:text="顯示Google地圖"
android:onClick="button1_Click"/>
<Button android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="追蹤個人行蹤"
android:onClick="button2_Click"/>
</LinearLayout>
<TextView android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-1
 在Ch14_5Activity活動類別的開頭宣告常數與成員變數,使
用GPLat[]和GPLng[]陣列儲存個人行蹤的座標,如下所示:
public class Ch14_5Activity extends Activity {
private final int MAX_RECORDS = 10;
private LocationManager manager;
private Location currentLocation;
private String best;
private int index = 0, count = 0;
private double[] GPLat = new double[MAX_RECORDS];
private double[] GPLng = new double[MAX_RECORDS];
…
}
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-2
onCreate()方法
 在覆寫的onCreate()方法載入版面配置後,取得系
統服務的LocationManager物件,如下所示:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
manager = (LocationManager)
getSystemService(LOCATION_SERVICE);
}
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-3
onResume()方法
 onResume()方法和第14-1-2節步驟三的同名方法相
同,只是更新位置頻率的條件不同,如下所示:
int minTime = 5000; // 毫秒
float minDistance = 15; // 公尺
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-4
onPause()方法
 在覆寫onPause()方法呼叫removeUpdates()方法取
消周期更新位置,此方法和第14-1-2節步驟三的同
名方法相同。
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-5
updatePosition()方法
 在自訂updatePosition()方法更新TextView元件顯示的位置資訊和個人行蹤的座
標清單,如下所示:
@Override
private void updatePosition() {
TextView output, list;
String str = "最近個人行蹤的座標清單:\n";
output = (TextView) findViewById(R.id.output);
list = (TextView) findViewById(R.id.list);
if (currentLocation == null) {
output.setText("取得定位資訊中...");
} else {
output.setText(getLocationInfo(currentLocation));
for (int i = 0; i < MAX_RECORDS; i++) {
if (GPLat[i] != 0.0)
str += GPLat[i] + "/" + GPLng[i] +"\n";
}
list.setText(str);
}
}
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-6
LocationListener傾聽者物件
 在requestLocationUpdates()方法需要註冊定位服務
的傾聽者物件,即使用匿名內層類別實作
LocationListener介面來建立此物件,此物件和第
14-1-2節步驟三的同名物件相同。
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-7
getLocationInfo()方法
 在自訂getLocationInfo()方法可以從參數Loaction物件取得定位資訊和
檢查是否需要儲存個人行蹤的座標,如下所示:
public String getLocationInfo(Location location) {
boolean isSave = true;
double lat, lng;
lat = location.getLatitude();
lng = location.getLongitude();
StringBuffer str = new StringBuffer();
str.append("定位提供者(Provider): "+
location.getProvider());
str.append("\n緯度(Latitude): " +
Double.toString(lat));
str.append("\n經度(Longitude): " +
Double.toString(lng));
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-8
 如果是,就可以計算第2個座標和前一個座標的距離,首先建立目地GPS座標
的Location物件,如下所示:
if (count >= 1) {
Location dest = new Location(location);
int preIndex = index - 1;
if (preIndex < 0 ) preIndex = GPLat.length - 1;
dest.setLatitude(GPLat[preIndex]);
dest.setLongitude(GPLng[preIndex]);
 在下方使用distanceTo()方法計算與目地座標的距離,如果距離小於20公尺就
不儲存,此時旗標變數isSave的值為false,如下所示:
float distance = location.distanceTo(dest);
Toast.makeText(this, "距離: " + distance + "公尺",
Toast.LENGTH_SHORT).show();
if (distance < 20.0) isSave = false;
}
if (isSave) {
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-9
 上述if條件的isSave旗標變數值為true,表示需要新增座標
,count變數記錄共儲存幾個座標,如果超過陣列尺寸,
就指定為陣列尺寸的個數,如下所示:
GPLat[index] = lat;
GPLng[index] = lng;
count++;
if (count >= MAX_RECORDS)
count = MAX_RECORDS;
index++;
if (index >= MAX_RECORDS) index = 0;
}
return str.toString();
}
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-10
button1_Click()事件處理方法
 button1_Click()事件處理方法可以依據目前的位置
座標來啟動Google地圖,它和第14-1-2節步驟三的
同名方法相同。
14-5 標記Google地圖-追蹤個人行蹤
步驟三:建立Activity主活動類別記錄座標清單-11
button2_Click()事件處理方法
 button2_Click()事件處理方法可以在MapView元件顯示個人
行蹤,這是使用Intent物件啟動MyMapActivity活動類別,
附件是個人行蹤座標的2個陣列,和座標數的count變數值
,如下所示:
public void button2_Click(View view) {
Intent mapView = new Intent(this, MyMapActivity.class);
mapView.putExtra("GPSLATITUDE", GPLat);
mapView.putExtra("GPSLONGITUDE", GPLng);
mapView.putExtra("MAX_INDEX", count);
startActivity(mapView);
}
14-5 標記Google地圖-追蹤個人行蹤
步驟四:建立MapView元件的版面配置
 MyMapActivity活動類別的版面配置是定義在map.xml檔,
使用LinearLayout垂直編排1個MapView元件,如下所示:
<com.google.android.maps.MapView
android:id="@+id/mapView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:enabled="true"
android:clickable="true"
android:apiKey="0JNEnTMsG9nVME5Lbx2LxFQr1zTh65aObDEbzsw"/>
14-5 標記Google地圖-追蹤個人行蹤
步驟五:建立MyMapActivity活動類別來標記地圖-1
 MyMapActivity活動類別是繼承MapActivity抽象類
別,在類別開頭宣告MapView和MapController物
件變數,如下所示:
public class MyMapActivity extends MapActivity {
private MapView mapView;
private MapController mc;
…
}
14-5 標記Google地圖-追蹤個人行蹤
步驟五:建立MyMapActivity活動類別來標記地圖-2
onCreate()方法
 在覆寫的onCreate()方法載入版面配置後,就可以取得
MapView元件和MapController物件,如下所示:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.map);
mapView = (MapView)findViewById(R.id.mapView);
mc = mapView.getController();
ArrayList<GeoPoint> locations = new ArrayList<GeoPoint>();
ArrayList<Drawable> images = new ArrayList<Drawable>();
double[] GPLat, GPLng;
int max_index;
14-5 標記Google地圖-追蹤個人行蹤
步驟五:建立MyMapActivity活動類別來標記地圖-3
 然後在下方取得Intent物件傳遞的座標陣列,如下所示:
GPLat = getIntent().getDoubleArrayExtra("GPSLATITUDE");
GPLng = getIntent().getDoubleArrayExtra("GPSLONGITUDE");
max_index = getIntent().getIntExtra("MAX_INDEX", 10);
for (int i = 0; i < max_index; i++) {
int gpLat, gpLng;
gpLat = (int) (GPLat[i]*1000000);
gpLng = (int) (GPLng[i]*1000000);
locations.add(new GeoPoint(gpLat,gpLng));
images.add(getResources().getDrawable(R.drawable.pin));
}
 for迴圈一一將陣列座標轉換成GeoPoint座標後,新增至ArrayList集合
物件,標記圖示都是同一個pin.png,換句話說,如果需要,每一個座
標可以使用不同的標記圖示。
14-5 標記Google地圖-追蹤個人行蹤
步驟五:建立MyMapActivity活動類別來標記地圖-4
 在下方建立LocationOverlay物件,其宣告在下一步驟,這
是一個繼承ItemizedOverlay類別的物件,建構子參數是預
設標記圖示和Context物件this,如下所示:
LocationOverlay myOverlay = new LocationOverlay(
getResources().getDrawable(R.drawable.icon), this);
myOverlay.setItems(locations, images);
mapView.getOverlays().add(myOverlay);
 上述程式碼呼叫setItems()方法指定標記的座標和圖示的
ArrayList物件後,呼叫MapView物件的getOverlays()方法取
得圖層Overlay的List集合物件,即可呼叫add()方法新增
Overlay物件。
14-5 標記Google地圖-追蹤個人行蹤
步驟五:建立MyMapActivity活動類別來標記地圖-5
最後在下方指定MapView參數為內建縮放控制、衛
星空照圖顯示,和使用MapController物件指定地
圖置中和放大15,如下所示:
mapView.setBuiltInZoomControls(true);
mapView.setSatellite(true);
mc.setCenter(locations.get(0));
mc.setZoom(15);
}
14-5 標記Google地圖-追蹤個人行蹤
步驟五:建立MyMapActivity活動類別來標記地圖-6
isRouteDisplayed()方法
 繼承類別一定要實作isRouteDisplayed()抽象方法,
傳回是否顯示任何規劃路徑,沒有傳回false,如
下所示:
@Override
protected boolean isRouteDisplayed() {
return false;
}
14-5 標記Google地圖-追蹤個人行蹤
步驟六:建立LocationOverlay類別-1
 LocationOverlay類別繼承ItemizedOverlay類別,這是一種項
目化的Overlay類別,可以使用List集合物件儲存多個
GeoPoint座標和標記圖示,以便在Google地圖上同時使用
不同圖示來標示多個座標。
 在LocationOverlay類別開頭宣告List集合物件變數myItems
和myMarkers,如下所示:
public class LocationOverlay extends
ItemizedOverlay<OverlayItem> {
private List<GeoPoint> myItems;
private List<Drawable> myMarkers;
private Context context;
…
}
14-5 標記Google地圖-追蹤個人行蹤
步驟六:建立LocationOverlay類別-2
建構子
 在類別建構子呼叫父類別的建構子,參數是預設
標示圖示,然後指定成員物件變數context的值,
如下所示:
public LocationOverlay(Drawable defaultMarker, Context
context) {
super(boundCenterBottom(defaultMarker));
this.context = context;
}
14-5 標記Google地圖-追蹤個人行蹤
步驟六:建立LocationOverlay類別-3
setItems()方法
 在自訂setItems()方法指定各項目的座標和標記圖
示,參數是ArrayList集合物件,如下所示:
public void setItems(ArrayList<GeoPoint> items,
ArrayList<Drawable> drawables) {
myItems = items;
myMarkers = drawables;
populate();
}
14-5 標記Google地圖-追蹤個人行蹤
步驟六:建立LocationOverlay類別-4
createItem()方法
 在實作createItem()抽象方法建立OverlayItem項目物件,和
呼叫setMarker()方法指定標記圖示,方法參數是索引,此
方法是由populate()方法來呼叫,如下所示:
@Override
protected OverlayItem createItem(int i) {
OverlayItem item = new OverlayItem(
myItems.get(i), null, null);
item.setMarker(boundCenterBottom(
myMarkers.get(i)));
return item;
}
14-5 標記Google地圖-追蹤個人行蹤
步驟六:建立LocationOverlay類別-4
size()方法
 在實作size()抽象方法傳回項目數,如下所示:
@Override
public int size() {
return myItems.size();
}
14-5 標記Google地圖-追蹤個人行蹤
步驟六:建立LocationOverlay類別-5
onTap()方法
 覆寫onTap()方法來處理指定項目的Tap事件,也就是說,
觸摸一下標記圖示,可以使用Toast類別顯示此點的座標,
如下所示:
@Override
protected boolean onTap(int i) {
GeoPoint gp = myItems.get(i);
Toast.makeText(context,
"座標: "+
gp.getLatitudeE6() / 1E6 + "," +
gp.getLongitudeE6() /1E6 ,
Toast.LENGTH_SHORT).show();
return true;
}
14-5 標記Google地圖-追蹤個人行蹤
步驟七:在AndroidManifest.xml註冊函數庫和新增權限
 因為Google Maps API不是Android內建函數庫,所以當
Android應用程式使用Google Maps API時,我們需要在
application元素新增uses-library子元素,讓應用程式可以使
用此函數庫,如下所示:
<uses-library android:name="com.google.android.maps"/>
 程式需要存取網路和使用定位服務,所以需要新增3個權
限,如下所示:
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission android:name=
"android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name=
"android.permission.ACCESS_FINE_LOCATION"/>