Android + Google Maps 建國科技大學 資管系 饒瑞佶 2017/3 V1.

Slides:



Advertisements
Similar presentations
第四章 类、对象和接口.
Advertisements

第13章 繪圖與多媒體 13-1 顯示圖檔-行動相簿 13-2 音樂播放-音樂播放器 13-3 影片播放-視訊播放器
MVC Servlet与MVC设计模式.
實驗五:多媒體播放器選單介面.
Part 2 開發Android應用程式的流程
位置與地圖應用 此投影片為講解Android如何取得定位經緯度和使用Google Map地圖.
第二章 JAVA语言基础.
Android + Web Service 建國科技大學 資管系 饒瑞佶 2017/3 V1.
期末專題報告 MyPosition - 我在何處報你知 組員:曾哲浩 蔡承翰 指導老師:陳朝鈞.
Location Based Services - LBS
Chapter 13 Android 實戰演練.
Android + JUnit 單元測試 建國科技大學資管系 饒瑞佶 2012/8/19V4.
2.1 基本資料型別 2.2 變數 2.3 運算式與運算子 2.4 輸出與輸入資料 2.5 資料型別轉換 2.6 實例
實驗十三:顯示目前經緯度位置.
API设计实例分析 通用IO API.
LINQ 建國科技大學 資管系 饒瑞佶.
JAVA vs. SQL Server 建國科技大學 資管系 饒瑞佶 2013/4 V1.
第10章 App微信分享的实现 倚动实验室.
Android資料庫處理 Android智慧型手機程式設計 程式設計與應用班 建國科技大學 資管系 饒瑞佶 2012/4 V1
第11章 Android GPS位置服务与地图编程
第9章 位置服务与地图应用.
第8章 Android内容提供者(ContentProvider)应用
Chapter 6 Advanced UI Design.
Android智慧型手機程式設計實務應用班
Android介面設計 Android智慧型手機程式設計 建國科技大學 資管系 饒瑞佶 2012/4 V1 2012/8 V2
Chapter 6 進階UI設計.
厦门大学数据库实验室 MapReduce 连接
第4章 Android生命周期.
Google Data API Spreadsheet
第9章 使用意圖啟動活動與內建應用程式 9-1 意圖的基礎 9-2 使用意圖啟動活動
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
Android + Service 建國科技大學 資管系 饒瑞佶.
Java语言程序设计 第五部分 Java异常处理.
專題報告 組員:吳家齊,江弘喻.
實驗十四:顯示與控制地圖.
王豐緒 銘傳大學資訊工程學系 問題:JAVA 物件檔輸出入.
建立Android新專案 Android智慧型手機程式設計 程式設計與應用班 建國科技大學 資管系 饒瑞佶 2012/4 V1
App Inventor2呼叫PHP存取MySQL
Java程序设计 第2章 基本数据类型及操作.
生活智慧王 樹德科技大學 資訊工程系 指導教授 : 陳毓璋 教授 小組成員: 劉上緯 翁維廷 洪文財.
第10章 GPS位置服务与地图编程.
FileUpload控制項 建國科技大學 資管系 饒瑞佶 2007年.
實驗十一:待辦事項程式 (儲存在手機上).
主编:钟元生 赵圣鲁.
透過YouTuBe API取得資料 建國科技大學 資管系 饒瑞佶 2018/1 V1.
软件测试 (四)静态测试与动态测试.
實驗十五:標記目前位置.
讓Emulator可以 使用Android Market
實驗九:延續實驗八, 製作一個完整音樂播放器
Location Based Services - LBS
IIS Internet Information Services
第二章 Java基本语法 讲师:复凡.
第二章 Java语法基础.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
進階UI元件:ListView元件以及複選 靜宜大學資管系 楊子青
RecyclerView and CardView
專案建置與封裝程式 建國科技大學 資管系 饒瑞佶.
Android Speech To Text(STT)
Activity的生命週期: 播放音樂與影片 靜宜大學資管系 楊子青
第2章 Java语言基础.
多國語系 建國科技大學 資管系 饒瑞佶.
讀取網路資料及JSON開放資料 靜宜大學資管系 楊子青
加速感測器 靜宜大學資管系 楊子青.
NFC (近場通訊, Near Field Communication) 靜宜大學資管系 楊子青
SQLite資料庫 靜宜大學資管系 楊子青.
第二章 Java基础语法 北京传智播客教育
Develop and Build Drives by Visual C++ IDE
Summary
InputStreamReader Console Scanner
Presentation transcript:

Android + Google Maps 建國科技大學 資管系 饒瑞佶 2017/3 V1

建立新Android專案

選擇Google Maps Activity

專案建立後 檔案google_maps_api.xml內有申請API Key的網址 最後API KEY填入這

申請API Key

建立憑證

完成API KEY建立 專案要用的API Key

確認資訊

將API Key貼入專案內的google_maps_api.xml

AndroidManifest.xml

準備一個座標點 緯度 經度

activity_maps.xml

取得layout上對應的fragment MainActivity.java 取得layout上對應的fragment

onMapReady-當Google Map服務載入後

將地標改成前面取得的座標 改成我們自己的 預設在雪梨

執行如果有錯誤 修改build.gradle(app) 加入:multiDexEnabled = true

執行 確認基本的地圖元件沒問題

放大地圖 mMap.animateCamera(CameraUpdateFactory.zoomTo(16));

執行

加入功能選項

執行 旋轉地圖時出現

改用自訂marker 原來預設的紅氣球

LatLng place = new LatLng(24.066516, 120.549871); //建國科大 BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.drawable.school); MarkerOptions markerOptions = new MarkerOptions(); markerOptions.position(place) .title("學校") .snippet("建國科技大學") .icon(icon); mMap.addMarker(markerOptions);

執行

畫線

// 畫線 ArrayList<LatLng> points= new ArrayList<>(); // 所有點集合 PolylineOptions lineOptions = new PolylineOptions(); // 多邊形 double lat = 24.066516; // 建國科大 double lng = 120.549871; LatLng position = new LatLng(lat, lng); points.add(position); // 加入座標點到points物件 double lat1 = 24.050731; // 鹿港龍山寺 double lng1 = 120.435614; LatLng position1 = new LatLng(lat1, lng1); points.add(position1); // 加入座標點到points物件 lineOptions.addAll(points); // 加入所有座標點到多邊形 lineOptions.width(10); lineOptions.color(Color.RED); if(lineOptions != null) { mMap.addPolyline(lineOptions); // 畫出多邊形 } else { Log.d("onPostExecute","draw line error!");

執行

設定地圖點擊事件 setOnMapClickListener

加入點擊後儲存座標點的ArrayList

加入點擊地圖後,在點擊位置標出marker

// 設定點擊事件 mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() { @Override public void onMapClick(LatLng latLng) { if (MarkerPoints.size() > 1) { //超過一個點時,清空地圖版面 MarkerPoints.clear(); mMap.clear(); } // 將點擊位置加入MarkerPoints MarkerPoints.add(latLng); // 加入marker設定 MarkerOptions options = new MarkerOptions(); options.position(latLng); // 設定地點 if (MarkerPoints.size() == 1) { // 起始點顏色 options.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)); } else if (MarkerPoints.size() == 2) { // 後續點的顏色 options.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)); // 將點畫在地圖上 mMap.addMarker(options); });

改進兩點間連線,依照路畫線,而非直線

// 依照實際路畫出路徑圖 if (MarkerPoints.size() >= 2) { // 需要確定有2點 LatLng origin = MarkerPoints.get(0); // 第1個點 LatLng dest = MarkerPoints.get(1); // 第2個點 // 取得 Google Directions API String url = getUrl(origin, dest); // 組合呼叫API需要的URL FetchUrl FetchUrl = new FetchUrl(); // 解析URL回傳結果 FetchUrl.execute(url); // 移動座標視窗 mMap.moveCamera(CameraUpdateFactory.newLatLng(origin)); mMap.animateCamera(CameraUpdateFactory.zoomTo(16)); }

建立Google Directions URL

// 取得 Google Directions API private String getUrl(LatLng origin, LatLng dest) { // 路徑起點 String str_origin = "origin=" + origin.latitude + "," + origin.longitude; // 路徑終點 String str_dest = "destination=" + dest.latitude + "," + dest.longitude; // Sensor enabled String sensor = "sensor=false"; // 建立參數 String parameters = str_origin + "&" + str_dest + "&" + sensor; // 設定輸出格式 String output = "json"; // 建立完整的URL String url = "https://maps.googleapis.com/maps/api/directions/" + output + "?" + parameters; return url; }

解析URL回傳結果 解析JSON並畫出路徑

// 解析URL回傳結果 private class FetchUrl extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... url) { // 接收從Google Directions回傳的資料(JSON Format) String data = ""; try { data = downloadUrl(url[0]); //呼叫downloadUrl取得路徑json資料 } catch (Exception e) { Log.d("Background Task", e.toString()); } return data; protected void onPostExecute(String result) { super.onPostExecute(result); ParserTask parserTask = new ParserTask(); // 解析JSON 資料 parserTask.execute(result);

下載Google Directions回傳json資料

// 下載Google Directions回傳json資料 private String downloadUrl(String strUrl) throws IOException { String data = ""; InputStream iStream = null; HttpURLConnection urlConnection = null; try { URL url = new URL(strUrl); // 建立 http連線 urlConnection = (HttpURLConnection) url.openConnection(); // 連線 url urlConnection.connect(); // 讀取資料 iStream = urlConnection.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(iStream)); StringBuffer sb = new StringBuffer(); String line = ""; while ((line = br.readLine()) != null) { sb.append(line); } data = sb.toString(); br.close(); } catch (Exception e) { Log.d("Exception", e.toString()); } finally { iStream.close(); urlConnection.disconnect(); return data;

解析JSON並畫出路徑 需要DataParser類別 接下頁

接下頁

// Parsing the data in non-ui thread @Override // 解析JSON並畫出路徑 private class ParserTask extends AsyncTask<String, Integer, List<List<HashMap<String, String>>>> { // Parsing the data in non-ui thread @Override protected List<List<HashMap<String, String>>> doInBackground(String... jsonData) { JSONObject jObject; List<List<HashMap<String, String>>> routes = null; try { jObject = new JSONObject(jsonData[0]); DataParser parser = new DataParser(); // DataParser類別 // 開始解析 routes = parser.parse(jObject); } catch (Exception e) { e.printStackTrace(); } return routes; 接下頁

@Override protected void onPostExecute(List<List<HashMap<String, String>>> result) { ArrayList<LatLng> points; PolylineOptions lineOptions = null; // 取出路徑 for (int i = 0; i < result.size(); i++) { points = new ArrayList<>(); lineOptions = new PolylineOptions(); List<HashMap<String, String>> path = result.get(i); // 取出路徑上所有點 for (int j = 0; j < path.size(); j++) { HashMap<String, String> point = path.get(j); double lat = Double.parseDouble(point.get("lat")); double lng = Double.parseDouble(point.get("lng")); LatLng position = new LatLng(lat, lng); points.add(position); } // 將所有點加入lineOptions lineOptions.addAll(points); lineOptions.width(10); lineOptions.color(Color.RED); // 畫出路徑 if(lineOptions != null) { mMap.addPolyline(lineOptions); else { Log.d("onPostExecute","without Polylines drawn");

DataParser類別 public class DataParser { /** Receives a JSONObject and returns a list of lists containing latitude and longitude */ public List<List<HashMap<String,String>>> parse(JSONObject jObject){ List<List<HashMap<String, String>>> routes = new ArrayList<>() ; JSONArray jRoutes; JSONArray jLegs; JSONArray jSteps; try { jRoutes = jObject.getJSONArray("routes"); /** Traversing all routes */ for(int i=0;i<jRoutes.length();i++){ jLegs = ( (JSONObject)jRoutes.get(i)).getJSONArray("legs"); List path = new ArrayList<>(); 接下頁

接下頁 /** Traversing all legs */ for(int j=0;j<jLegs.length();j++){ jSteps = ( (JSONObject)jLegs.get(j)).getJSONArray("steps"); /** Traversing all steps */ for(int k=0;k<jSteps.length();k++){ String polyline = ""; polyline = (String)((JSONObject)((JSONObject)jSteps.get(k)).get("polyline")).get("points"); List<LatLng> list = decodePoly(polyline); /** Traversing all points */ for(int l=0;l<list.size();l++){ HashMap<String, String> hm = new HashMap<>(); hm.put("lat", Double.toString((list.get(l)).latitude) ); hm.put("lng", Double.toString((list.get(l)).longitude) ); path.add(hm); } routes.add(path); } catch (JSONException e) { e.printStackTrace(); }catch (Exception e){ return routes; 接下頁

接下頁 private List<LatLng> decodePoly(String encoded) { List<LatLng> poly = new ArrayList<>(); int index = 0, len = encoded.length(); int lat = 0, lng = 0; while (index < len) { int b, shift = 0, result = 0; do { b = encoded.charAt(index++) - 63; result |= (b & 0x1f) << shift; shift += 5; } while (b >= 0x20); int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); lat += dlat; shift = 0; result = 0; int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); lng += dlng; LatLng p = new LatLng((((double) lat / 1E5)), (((double) lng / 1E5))); poly.add(p); } return poly; 接下頁

畫面加入其他物件

加入TextView

<LinearLayout xmlns:android="http://schemas. android xmlns:map="http://schemas.android.com/apk/res-auto" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" map:mapType="normal"> <TextView android:text="Google Maps 測試" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/textView" /> <fragment android:id="@+id/map" android:name="com.google.android.gms.maps.SupportMapFragment" android:layout_weight="1"/> </LinearLayout>

整合位置服務 Google Play Services Location API GPS或WiFi

準備工作 開啟Android SDK Manager,檢查「Extras -> Google Play services」 是否已經安裝。如果還沒有安裝的話,勾選並執行安裝的工作

修改或確認build.gradle(Module:app) 是否有:compile 'com.google.android.gms:play-services:x.x.x‘ 版本會依據寫程式的時間點而不同

修改ManifestAndroid.xml 加入: <meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" /> 前面的Google Maps服務

修改ManifestAndroid.xml 加入: <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

Android取得位置資料 使用Google Services下的Location API 先使用「com.google.android.gms.common.api」套件下的 「GoogleApiClient」,連線與使用Google Services服務 再使用「com.google.android.gms.location」套件下的 API,取得裝置目前的位置資訊

複製前面Google Maps使用的Activity 複製前面Google Maps使用的MainActivity.java,成為新的 GPSMainActivity.java,讓兩者不會互相干擾 GPSMainActivity.java中只要保留地圖與marker部分程式,原來畫 線與路徑規劃(Directions API)部分可以刪除 此處最重要的是取得目前地點座標(GPS或WiFi網路),將其以 marker方式顯示到Google Maps上

GPSMainActivity.java中只要保留地圖與marker部分程式 取消 後面會移到onResume 取消 只保留mMap

GPSMainActivity.java加入需要的物件宣告 // Location API // Google API用戶端物件 private GoogleApiClient googleApiClient; // Location請求物件 private LocationRequest locationRequest; // 取得裝置目前最新的位置 private Location currentLocation; // 目前位置對應的marker private Marker currentMarker; 前面的Google Maps服務

GPSMainActivity.java加入需要繼承 GoogleApiClient.ConnectionCallbacks與GoogleApiClient.OnConnectionFailedListener 需要加入對應的事件與方法

對應繼承需要加入的3個事件與方法

GPSMainActivity.java加入接收位置更新資訊的繼承 LocationListener 對應繼承需要加入的事件與方法

先使用「com. google. android. gms. common 先使用「com.google.android.gms.common.api」套件下的「GoogleApiClient」,連線與使用Google Services服務 建立Google API client物件,並在onCreate中呼叫

建立Google API client物件 // 建立Google API client物件 private synchronized void configGoogleApiClient() { googleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .addApi(LocationServices.API) .build(); } 該物件會執行ConnectionCallbacks與OnConnectionFailedListener兩個繼承對應的3個方法

再使用「com.google.android.gms.location」套件下的API,取得裝置目前的位置資訊 建立LocationRequest物件與呼叫

建立LocationRequest物件 // 建立Location request物件 private void configLocationRequest() { locationRequest = new LocationRequest(); // 設定讀取位置資訊更新的間隔時間為一秒(1000ms) locationRequest.setInterval(1000); // 設定從API讀取位置資訊的間隔時間為一秒(1000ms) locationRequest.setFastestInterval(1000); // 設定優先讀取高精確度的位置資訊(GPS) locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); }

修改「onConnected」、「onConnectionFailed」與「onLocationChanged」方法 在onConnected(也就是已經連線到Google Services後)加入啟動位 置更新服務,會對應到onLocationChanged事件 這裡需要搭配Android 6.0之後的Permission政策做修正

onConnected // 已經連線到Google Services @Override public void onConnected(@Nullable Bundle bundle) { // 啟動位置更新服務,位置資訊更新的時候,應用程式會自動呼叫LocationListener.onLocationChanged if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } LocationServices.FusedLocationApi.requestLocationUpdates( googleApiClient, locationRequest, GPSMapsActivity.this);

加入Permission詢問視窗 Android 6.0之後的Permission政策,雖然在Manifest.xml中有加入Permissions,但仍還是要求執行時需要出現詢問視窗,再次跟使用者確認!

int MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION; // 詢問視窗 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, MY_PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION); // 接收permission詢問視窗回傳值 @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { Log.i("GPS", "requestCode=" + requestCode); switch (requestCode) { case 0: { // location if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the } else { // permission denied, boo! Disable the } return;

onConnectionFailed方法

onConnectionFailed方法 // Google Services連線失敗,ConnectionResult參數是連線失敗的資訊 @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { int errorCode = connectionResult.getErrorCode(); // 裝置沒有安裝Google Play服務 if (errorCode == ConnectionResult.SERVICE_MISSING) { Toast.makeText(this, "裝置沒有安裝Google Play服務",Toast.LENGTH_LONG).show(); }

onLocationChanged方法

onLocationChanged方法 // 位置改變實會觸發,Location參數是目前的位置 @Override public void onLocationChanged(Location location) { currentLocation = location; Log.i("GPS", "onLocationChanged"); // 取得目前位置 LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); // 設定目前位置的marker if (currentMarker == null) { currentMarker = mMap.addMarker(new MarkerOptions().position(latLng)); } else { currentMarker.setPosition(latLng); } // 移動地圖到目前的位置 mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng)); //放大地圖16倍 mMap.animateCamera(CameraUpdateFactory.zoomTo(16));

加入onResume、onPause與onStop @Override protected void onResume() { super.onResume(); if (mMap == null) { //取得地圖 ((SupportMapFragment) getSupportFragmentManager(). findFragmentById(R.id.map)).getMapAsync(this); } // 連線到Google API用戶端 if (!googleApiClient.isConnected()) { Log.i("GPS", "onResume"); googleApiClient.connect();

@Override protected void onPause() { super.onPause(); // 取消位置請求服務 if (googleApiClient.isConnected()) { LocationServices.FusedLocationApi.removeLocationUpdates( googleApiClient, this); } protected void onStop() { super.onStop(); // 移除Google API用戶端連線 googleApiClient.disconnect();

執行 onCreate  onResume onConnected  onLocationChanged onResume:取得地圖與GoogleApiClient onConnected:請求連線與使用Google Services服務 onLocationChanged:取得最新的位置

執行