MapboxのAndroid SDKを少しだけ触ってみた

MapboxのAndroid SDKを少しだけ触ってみました＼(^o^)／

- https://www.mapbox.com/

実装方法や使い方を触った範囲で書こうと思います。

# [Mapbox](https://www.mapbox.com/)って何？
Mapboxは、簡単に言うとGoogle mapみたいなやつです（笑）
アプリやWebに簡単に地図を実装できます。
あと、地図上にマーカー置いたり線も引けたりできます。
今のところWeb, iOS, Androidなど一通りのプラットフォームで動くみたいです。

ちなみに、お値段は、こんな感じです。
https://www.mapbox.com/pricing/
![スクリーンショット 2016-04-05 15.40.35.png](https://qiita-image-store.s3.amazonaws.com/0/53996/60731173-bf57-880d-b08c-37ca979d0d2e.png)

# アカウント登録
Mapboxを使うには、**はじめにアカウント登録**する必要があります。
登録完了すると**アクセストークン**がもらえます。

トップページの右上らへんにログインする所あるので、ここからアカウント登録。
![スクリーンショット 2016-04-05 15.44.12.png](https://qiita-image-store.s3.amazonaws.com/0/53996/41236fba-9a04-bb33-5e30-6bda98d22b37.png)

登録が完了すると、こんな管理画面みたいなものが表示されると思うので、**「Access token」**の所にトークンがあると思うので、コピーします。

![スクリーンショット 2016-04-05 15.44.53.png](https://qiita-image-store.s3.amazonaws.com/0/53996/bf1a5e16-d9a9-ae69-fa00-d09ea5de93c1.png)

# AndroidでMapboxを使う

実際にAndroidアプリでMapBoxを使ってみます。（ライブラリは4.1.0を使用しています）


## Gradle
GradleでMapboxのライブラリをいれます。

```
repositories {
    mavenCentral()
}

dependencies {
    compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:4.0.0@aar'){
        transitive=true
    }
}
```

## AndroidManifest

AndroidManifest.xmlに以下のpermissionと...

```xml

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />

```

このserviceを入れます。

```xml

<service android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService" />

```

全体は、こんな感じ。

```AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="パッケージ名">

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService" />

    </application>

</manifest>

```

## activity_main.xml

MainActivity.javaとactivity_main.xmlの２つ用意してください。

activityのxmlにcom.mapbox.mapboxsdk.maps.MapViewを追加します。

```activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:mapbox="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.mapbox.mapboxsdk.maps.MapView
        android:id="@+id/map_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        mapbox:access_token="アクセストークン"
        mapbox:style_url="@string/style_mapbox_streets"
        mapbox:center_latitude="33.590098"
        mapbox:center_longitude="130.419484"
        mapbox:zoom="15"/>

</RelativeLayout>

```

## MainActivity.java

MainActivity.javaの方は、こんな感じです。

```MainActivity.java

public class MainActivity extends AppCompatActivity {

    private MapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mapView = (MapView) findViewById(R.id.map_view);
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(new OnMapReadyCallback() {
           @Override
           public void onMapReady(@NonNull final MapboxMap mapboxMap) {
              //地図読込完了...
           }
        });
    }

    @Override
    public void onResume() {
        super.onResume();
        mapView.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        mapView.onPause();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

}

```

これで、完了です。
実行すると、こんな感じに地図が表示されるはずです。

![Screenshot_.png](https://qiita-image-store.s3.amazonaws.com/0/53996/e42f15f1-5ddc-5426-f838-c55323997cd1.png)


ちなみに、この辺のxmlの設定は、

```xml

  mapbox:access_token="アクセストークン"
  mapbox:style_url="@string/style_mapbox_streets"
  mapbox:center_latitude="33.590098"
  mapbox:center_longitude="130.419484"
  mapbox:zoom="15"

```

こんな感じにJavaでも可能です。

```java

  MapView mapView = (MapView) findViewById(R.id.map_view);
  mapView.setAccessToken("アクセストークン");
  mapView.setStyle(Style.MAPBOX_STREETS);
  mapView.getMapAsync(new OnMapReadyCallback() {
     @Override
     public void onMapReady(@NonNull MapboxMap mapboxMap) {
         mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(
                 new CameraPosition.Builder()
                          .target(new LatLng(33.583549, 130.393819))
                          .zoom(14)
                          .bearing(0)
                          .tilt(0)
                          .build()));
     }
  });
       
```

# Mapboxの機能

## マーカー

こんなやつを表示させる方法です。

![スクリーンショット 2016-04-06 11.25.24.png](https://qiita-image-store.s3.amazonaws.com/0/53996/3288379a-4d74-5e58-669f-6fdfce3fc682.png)


```java

mapView.getMapAsync(new OnMapReadyCallback() {
    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {
        
        mapboxMap.addMarker(new MarkerOptions()
                .position(new LatLng(33.583549, 130.393819))
                .title("たいとる だよ")
                .snippet("すにぺっと"));

    }
});

```

マーカーの画像を変更する場合は、このようにします。

```java

IconFactory iconFactory = IconFactory.getInstance(MainActivity.this);
Drawable iconDrawable = ContextCompat.getDrawable(MainActivity.this, R.drawable.icon);
Icon icon = iconFactory.fromDrawable(iconDrawable);

mapboxMap.addMarker(new MarkerOptions()
        .position(new LatLng(33.583549, 130.393819))
        .title("たいとる だよ")
        .snippet("すにぺっと")
        .icon(icon));
```

![スクリーンショット 2016-04-06 11.27.33.png](https://qiita-image-store.s3.amazonaws.com/0/53996/d851c21e-a97c-5f84-7670-ed912ea293df.png)

マーカーをタップしたかの判定は、こんな感じみたいです。

```java

mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() {
   @Override
   public boolean onMarkerClick(@NonNull Marker marker) {
      Toast.makeText(getApplicationContext(), "マーカークリック: " + marker.getTitle(), Toast.LENGTH_LONG).show();
      return false;
   }
});

```

![スクリーンショット 2016-04-06 11.28.47.png](https://qiita-image-store.s3.amazonaws.com/0/53996/7ddc0518-643e-53d4-4bc7-2ebe66472b76.png)

## 線

地図上に線をひく方法は、これらしい。

```java

mapView.getMapAsync(new OnMapReadyCallback() {
    @Override
    public void onMapReady(@NonNull MapboxMap mapboxMap) {

        List<LatLng> line = new ArrayList<>();
        line.add(new LatLng(33.586319, 130.421886));
        line.add(new LatLng(33.590018, 130.374715));
      
        mapboxMap.addPolyline(new PolylineOptions().addAll(line));

    }
});

```

![スクリーンショット 2016-04-06 11.30.45.png](https://qiita-image-store.s3.amazonaws.com/0/53996/4054ff85-0c9f-8991-64be-eb34b53bb9b1.png)

色や太さも変更できるみたいです。

```java
  mapboxMap.addPolyline(new PolylineOptions().addAll(line).width(1).color(Color.BLUE));

```

![スクリーンショット 2016-04-06 11.31.00.png](https://qiita-image-store.s3.amazonaws.com/0/53996/f97fccd6-9c6d-1cde-ac99-ef33762094d1.png)

## オフライン使用

事前に地図をダウンロードしておくことで、オフラインになった時でも利用できるみたいです。

```java
public class MainActivity extends AppCompatActivity {

    private MapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mapView = (MapView) findViewById(R.id.map_view);
        mapView.setAccessToken("アクセストークン");
        mapView.setStyle(Style.MAPBOX_STREETS);
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(@NonNull final MapboxMap mapboxMap) {
                mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(
                        new CameraPosition.Builder()
                                .target(new LatLng(33.583549, 130.393819))
                                .zoom(14)
                                .bearing(0)
                                .tilt(0)
                                .build()));
            }
        });

        OfflineManager mOfflineManager = OfflineManager.getInstance(this);
        mOfflineManager.setAccessToken("アクセストークン");

        LatLngBounds latLngBounds = new LatLngBounds.Builder()
                .include(new LatLng(33.597793, 130.431185))
                .include(new LatLng(33.576200, 130.406466))
                .build();

        OfflineTilePyramidRegionDefinition definition = new OfflineTilePyramidRegionDefinition(
                mapView.getStyleUrl(), latLngBounds, 10, 20, this.getResources().getDisplayMetrics().density);

        byte[] metadata;
        try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("FIELD_REGION_NAME", "Fukuoka");
            String json = jsonObject.toString();
            metadata = json.getBytes("UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
            metadata = null;
        }

        mOfflineManager.createOfflineRegion(definition, metadata, new OfflineManager.CreateOfflineRegionCallback() {
            @Override
            public void onCreate(OfflineRegion offlineRegion) {
                offlineRegion.setDownloadState(OfflineRegion.STATE_ACTIVE);

                offlineRegion.setObserver(new OfflineRegion.OfflineRegionObserver() {
                    @Override
                    public void onStatusChanged(OfflineRegionStatus status) {
                        //進捗
                        double percentage = status.getRequiredResourceCount() >= 0 ?
                                (100.0 * status.getCompletedResourceCount() / status.getRequiredResourceCount()) :
                                0.0;

                        if (status.isComplete()) {
                            //ダウンロード完了
                            return;
                        }

                    }

                    @Override
                    public void onError(OfflineRegionError error) {
                        //エラー
                    }

                    @Override
                    public void mapboxTileCountLimitExceeded(long limit) {

                    }
                });

            }

            @Override
            public void onError(String error) {
                //エラー
            }
        });

    }

    @Override
    public void onResume() {
        super.onResume();
        mapView.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        mapView.onPause();
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

}
```

オフラインになったら自動で切り替わるみたいです。
ちなみにこのデータは**mbgl-offline.db**に保存されてるみたいです。

あ！
こちらのmetadataを使うと、ダウンロードした地図と一緒に別の値を保存することができます。

```java
byte[] metadata;
try {
    JSONObject jsonObject = new JSONObject();
    jsonObject.put("FIELD_REGION_NAME", "Fukuoka");
    String json = jsonObject.toString();
    metadata = json.getBytes("UTF-8");
} catch (Exception e) {
    e.printStackTrace();
    metadata = null;
}
```

ここでは、jsonの形で「Fukuoka」という値をいれています。(別にjsonでする必要はないです)
地図の名前やIDなど、そういったものに使うと良いと思います。

で、指定したmetadataは、**offlineRegion.getMetadata()**で取得できます。

```java

mOfflineManager.listOfflineRegions(new OfflineManager.ListOfflineRegionsCallback() {
    @Override
    public void onList(OfflineRegion[] offlineRegions) {
       if (offlineRegions == null || offlineRegions.length == 0) {
            return;
       }

       for (OfflineRegion offlineRegion : offlineRegions) {
            //metadata取得
            offlineRegion.getMetadata();
       }
   }

   @Override
   public void onError(String error) {
     //エラー
   }
});

```

# まとめ

ただ地図を表示するくらいなら、Mapboxでも良いかも。
個人的にはMapboxの地図のデザインはGoogle mapより好き（笑）

ライブラリが頻繁に更新されているみたいで、仕様変更がよくあるらしい。。。

ちなみに、Mapboxのサンプル集は、こちらです。
https://github.com/mapbox/mapbox-gl-native/tree/11f7db293e5488690b87ee0260f910e49e5e0275/platform/android/MapboxGLAndroidSDKTestApp