基于MapBox在Android應(yīng)用中集成地圖支持
譯文一、 簡(jiǎn)介
MapBox是一個(gè)開(kāi)源的矢量地圖SDK。此框架的承諾是在開(kāi)發(fā)視頻游戲方面將提供優(yōu)質(zhì)的渲染速度和平滑度。如果你有興趣將地圖功能集成到你的應(yīng)用程序,那么MapBox將是值得你考慮的選擇方案之一。
二、權(quán)限設(shè)置
首先需要說(shuō)明的是,你可以從GitHub網(wǎng)站下載到本文提供的源代碼,地址是https://github.com/sitepoint-editors/MapBox-App。
要想在你的應(yīng)用程序如使用Mapbox,你需要一個(gè)API訪問(wèn)令牌。為此,你需要先創(chuàng)建一個(gè)Mapbox帳戶,你可以在網(wǎng)站https://www.mapbox.com/studio/account/tokens處找到你需要的有類數(shù)據(jù)。
然后,把令牌添加到你的Android程序的文件strings.xml中:
- <string name="accessToken">Your access token</string>
接下來(lái),在配置文件AndroidManifest.xml中加入如下所示的Internet和位置訪問(wèn)權(quán)限:
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <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"/>
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
請(qǐng)注意:在Android Marshmallow (API 23)及更高的版本中在運(yùn)行時(shí)是需要上述權(quán)限的。
三、安裝MapBox
接下來(lái),打開(kāi)配置文件build.gradle(對(duì)應(yīng)于Module: app文件夾下的那個(gè)),添加對(duì)MapBox的依賴。請(qǐng)參考如下所示配置代碼:
- repositories {
- mavenCentral()
- }
- dependencies {
- . . .
- compile('com.mapbox.mapboxsdk:mapbox-android-sdk:3.2.0@aar') {
- transitive = true
- }
- compile ('com.mapbox.mapboxsdk:mapbox-android-directions:1.0.0@aar'){
- transitive=true
- }
- }
上面代碼中的***處配置描述了Mapbox,后面的配置則對(duì)應(yīng)于目錄庫(kù)(Directions library)描述,用于向應(yīng)用程序提供駕車、步行以及跨自行車等調(diào)用函數(shù),并支持在地圖中繪制其行蹤。
四、MapBox布局
接下來(lái),打開(kāi)布局文件content_main.xml,使用如下內(nèi)容替換原來(lái)的內(nèi)容:
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:mapbox="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.example.valdio.mapboxintegration.MainActivity"
- tools:showIn="@layout/activity_main">
- <com.mapbox.mapboxsdk.views.MapView
- android:id="@+id/mapview"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- mapbox:access_token="@string/accessToken" />
- </RelativeLayout>
接下來(lái),初始化你需要在MainActivity文件中使用的變量:
- public class MainActivity extends AppCompatActivity {
- private MapView mapView = null;
- private String MAPBOX_ACCESS_TOKEN = "";
- private DirectionsRoute currentRoute = null;
- ...
接下來(lái),在MainActivity.java文件中,把onCreate()方法中的創(chuàng)建工具欄和浮動(dòng)按鈕的代碼刪除,添加如下代碼來(lái)初始化地圖:
- String MAPBOX_ACCESS_TOKEN = getResources().getString(R.string.accessToken);
- // Set up a standard Mapbox map
- MapView mapView = (MapView) findViewById(R.id.mapview);
- mapView.setAccessToken(MAPBOX_ACCESS_TOKEN);
- mapView.setStyleUrl(Style.MAPBOX_STREETS); // specify the map style
- mapView.setZoom(14); // zoom level
- mapView.onCreate(savedInstanceState);
Mapbox需要實(shí)現(xiàn)Activity的生命同期方法以避免運(yùn)行時(shí)錯(cuò)誤;因此,需要添加如下的重寫函數(shù):
- @Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
- @Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mapView.onDestroy();
- }
- @Override
- protected void onResume() {
- super.onResume();
- mapView.onResume();
- }
- @Override
- protected void onPause() {
- super.onPause();
- mapView.onPause();
- }
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mapView.onSaveInstanceState(outState);
- }
好了?,F(xiàn)在配置好了Mapbox,可以構(gòu)建應(yīng)用程序了。
五、把標(biāo)記添加到地圖中
現(xiàn)在,請(qǐng)把如下代碼添加到MainActivity的onCreate函數(shù)的***部:
- @Override
- protected void onStart() {
- super.onStart();
- mapView.onStart();
- }
- @Override
- protected void onStop() {
- super.onStop();
- mapView.onStop();
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mapView.onDestroy();
- }
- @Override
- protected void onResume() {
- super.onResume();
- mapView.onResume();
- }
- @Override
- protected void onPause() {
- super.onPause();
- mapView.onPause();
- }
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- mapView.onSaveInstanceState(outState);
- }
這段代碼中的CameraPosition是一個(gè)非常有用的Mapbox類,可用于設(shè)置用戶視圖的位置、角度、縮放和傾斜度,等等信息。
到現(xiàn)在,我們的地圖看起來(lái)是如下所示的模樣:
六、獲取設(shè)備位置
為了使Mapbox能夠訪問(wèn)到設(shè)置的位置信息,必須啟動(dòng)設(shè)置的位置服務(wù),程序應(yīng)當(dāng)有使用它們的權(quán)限。如前面所提到的,在Android Marshmallow (API 23)及后續(xù)更高的版本中,在運(yùn)行時(shí)是需要這些權(quán)限的。
現(xiàn)在,我們來(lái)創(chuàng)建一個(gè)新的函數(shù),在其中加入取得當(dāng)前位置的代碼:
- private void myLocation() {
- if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, 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;
- }
- mapView.setMyLocationEnabled(true);
- mapView.setMyLocationTrackingMode(MyLocationTracking.TRACKING_FOLLOW);
- mapView.getMyLocation();
- }
然后,在地圖初始化代碼后添加如下函數(shù)調(diào)用:
- ...
- mapView.onCreate(savedInstanceState);
- myLocation();
在現(xiàn)在的情況中,我們把跟蹤方式設(shè)置為TRACKING_FOLLOW,這意味著用戶的位置將持續(xù)不斷地被監(jiān)控,而且地圖會(huì)不斷地隨著變化不斷更新。
七、在地圖上繪制線路
現(xiàn)在,我們創(chuàng)建了標(biāo)記與位置,接下來(lái)就是在地圖上繪制線路了。
這要使用前面在依賴性設(shè)置時(shí)所導(dǎo)入的第二個(gè)庫(kù)(Directions library),這也是Mapbox團(tuán)隊(duì)自行開(kāi)發(fā)的。
下面描述了其工作原理:
1、創(chuàng)建兩個(gè)航點(diǎn)位置,一個(gè)是出發(fā)點(diǎn),一個(gè)對(duì)應(yīng)目的地。
2、創(chuàng)建一個(gè)MapboxDirections生成器對(duì)象,用于向Mapbox API發(fā)出網(wǎng)絡(luò)請(qǐng)求,其中的數(shù)據(jù)包含對(duì)應(yīng)于出發(fā)點(diǎn)和目的點(diǎn)的航點(diǎn)位置信息,還有方向路線有關(guān)的配置信息(駕駛、步行或騎自行車等)。
3、異步執(zhí)行方向要求。其中,MapboxDirections類提供了一個(gè)使用Retrofit API的內(nèi)置的異步請(qǐng)求。具體地說(shuō),是使用enqueue()函數(shù)來(lái)執(zhí)行請(qǐng)求。
4、OnResponse()方法返回Retrofit響應(yīng)結(jié)果,這是一個(gè)標(biāo)準(zhǔn)的JSON API響應(yīng)。
5、響應(yīng)正文中包含位置坐標(biāo)對(duì)信息,稍后這些坐標(biāo)將繪制在地圖上。
6、采用Mapbox Polyline函數(shù)把坐標(biāo)繪制在地圖上。折線(Polyline)是一種幾何特征,通過(guò)多條線段首尾相連而形成一條不封閉的折線。
為了使用Direction庫(kù),我們需要獲得設(shè)備的位置作為起源航點(diǎn),以及由用戶通過(guò)長(zhǎng)按方式來(lái)指定目的地航點(diǎn)的位置信息。
八、加入航點(diǎn)位置并在目標(biāo)地添加標(biāo)記
現(xiàn)在,請(qǐng)把如下代碼添加到onCreate方法的***面:
- mapView.setOnMapLongClickListener(new MapView.OnMapLongClickListener() {
- @Override
- public void onMapLongClick(LatLng point) {
- //Remove previously added markers
- //Marker is an annotation that shows an icon image at a geographical location
- //so all markers can be removed with the removeAllAnnotations() method.
- mapView.removeAllAnnotations();
- // Set the origin waypoint to the devices location
- Waypoint origin = new Waypoint(mapView.getMyLocation().getLongitude(), mapView.getMyLocation().getLatitude());
- // Set the destination waypoint to the location point long clicked by the user
- Waypoint destination = new Waypoint(point.getLongitude(), point.getLatitude());
- // Add marker to the destination waypoint
- mapView.addMarker(new MarkerOptions()
- .position(new LatLng(point))
- .title("Destination Marker")
- .snippet("My destination"));
- // Get route from API
- getRoute(origin, destination); }
- });
九、創(chuàng)建MapboxDirections網(wǎng)絡(luò)請(qǐng)求并異步運(yùn)行
接下來(lái),再創(chuàng)建一個(gè)如下所示的方法,以便取得***路由信息:
- private void getRoute(Waypoint origin, Waypoint destination) {
- MapboxDirections directions = new MapboxDirections.Builder()
- .setAccessToken(MAPBOX_ACCESS_TOKEN)
- .setOrigin(origin)
- .setDestination(destination)
- .setProfile(DirectionsCriteria.PROFILE_WALKING)
- .build();
- directions.enqueue(new Callback<DirectionsResponse>() {
- @Override
- public void onResponse(Response<DirectionsResponse> response, Retrofit retrofit) {
- // Display some info about the route
- currentRoute = response.body().getRoutes().get(0);
- showToastMessage(String.format("You are %d meters \nfrom your destination", currentRoute.getDistance()));
- // Draw the route on the map
- drawRoute(currentRoute);
- }
- @Override
- public void onFailure(Throwable t) {
- showToastMessage("Error: " + t.getMessage());
- }
- });
- }
十、使用地圖上的坐標(biāo)點(diǎn)繪制折線
再添加一個(gè)如下方法來(lái)實(shí)現(xiàn)路由繪制:
- private void drawRoute(DirectionsRoute route) {
- // Convert List<Waypoint> into LatLng[]
- List<Waypoint> waypoints = route.getGeometry().getWaypoints();
- LatLng[] point = new LatLng[waypoints.size()];
- for (int i = 0; i < waypoints.size(); i++) {
- point[i] = new LatLng(
- waypoints.get(i).getLatitude(),
- waypoints.get(i).getLongitude());
- }
- // Draw Points on MapView
- mapView.addPolyline(new PolylineOptions()
- .add(point)
- .color(Color.parseColor("#38afea"))
- .width(5));
- }
- private void showToastMessage(String message) {
- Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
- }
好了,現(xiàn)在運(yùn)行你的工程。在地圖上選擇兩個(gè)點(diǎn),你會(huì)觀察到類似于下圖所示結(jié)果:
十一、小結(jié)
本文中,我們介紹了使用MapBox SDK及其Directory庫(kù)的基本知識(shí)。其實(shí),MapBox還有更多更豐富的內(nèi)容可應(yīng)用于你的程序中,例如不同的地圖風(fēng)格,實(shí)現(xiàn)定制的矢量地圖繪制等等。
作為本文補(bǔ)充,我還推薦另一個(gè)庫(kù)Geocoding(https://github.com/mapbox/mapbox-geocoder-android)。這個(gè)庫(kù)可以把坐標(biāo)信息轉(zhuǎn)換成地圖,或者實(shí)現(xiàn)相反的轉(zhuǎn)換。當(dāng)然,要想了解更多的有關(guān)MapBox信息,建議學(xué)習(xí)其移動(dòng)應(yīng)用有關(guān)實(shí)例(https://www.mapbox.com/mobile/)。在這個(gè)網(wǎng)址中你會(huì)發(fā)現(xiàn)更多的庫(kù)可用。