osmdroidを使ってMapを表示してみる

MatsuribaTech（祭り場Tech） Advent Calendar 2023 25日を担当しますはるちろです！！
よろしくお願いします。

# はじめに
皆さんはAndroidでMapを使用したことはありますでしょうか？
基本的には、AndroidでMapを使うなら、GoogleMapを使えばAPIもたくさんあるし、記事もたくさんあって便利なのだが、いちいちAPIKeyを用意したりするのがめんどくさいと思います。
そこで、オープンソースのマップを使うとKeyの設定などが必要ないため、初期設定なしで使用することができる。
したがって、今回はオープンソースのMapを使用してみた所感と使用方法をまとめてみようと思う。

# 自分が知っているオープンソースのマップ

オープンソースでできているMapは世の中にたくさんあります。
その中で自分が知っているMapを少しまとめてみようと思います。

### leaflet
実際便利そうなのだが、JSのライブラリということもあり、Androidのネイティブに対応したAPIはなさそう。
いちいちWebViewを使って、JSを用いて操作するのは流石に違うと思う。

https://leafletjs.com/examples/mobile/

### Open Street Map(OSM)→osmdroid
誰でも地図を使うことができるように作られた地図ライブラリらしい。
今回はAndroidのネイティブ対応のMapとしてライブラリが提供されていたのでありがたく使用させてもらおうと思う。

https://openstreetmap.jp/

https://github.com/osmdroid/osmdroid


# 実装をしてみる

今回は、以下のQiitaの記事を参考にしながら実装をしてみました。

https://qiita.com/ohwada/items/c3741fb14a5142035314

## Gradleの設定

app側のGradleに以下の内容を記載する。
2023年12月現在は6.1.17が最新でした。バージョンに関しては調べて一番最新のものを設定するといいだろう。

Qiitaの記事ではjcenterではなくmavenContralを使用するように追加をしていたが、デフォルトでmavenContralを使用するように変わっているため、特に気にする必要はない。

```kotlin:app/build.gradle
dependencies {
    implementation("org.osmdroid:osmdroid-android:6.1.17")
}
```

## パーミッションの追加

パーミッション関係はバージョンアップがかかって、より細かく位置情報の設定をしなくてはいけなくなってた。
今回は、おおよその位置情報と、正確な位置情報の両方を取得できるように設定をしなくてはいけなくなってた。

詳しくは、以下の記事から位置情報に関するパーミッションの設定を見てもらえるとより学べると思う。

https://developer.android.com/training/location/permissions?hl=ja

```xml:AndroidManifest

<!-- 大まかな位置情報の取得 -->
<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" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"  />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

```

 
## Mapを表示してみる

MainActivityで表示をするために、xmlの記載を行う。
特に何も考えず、`<org.osmdroid.views.MapView>`のタグを追加すれば大丈夫


```xml:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="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=".MainActivity">

    <org.osmdroid.views.MapView
        android:id="@+id/mapView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

    </org.osmdroid.views.MapView>
</androidx.constraintlayout.widget.ConstraintLayout>
```

今回は最低限の実装として、中心位置の決定と、Mapのサイズを指定してみる。
中心位置は、東京駅にする。

```kotlin:MainActivity.kt

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // setContentViewより前に置いておく、
        Configuration.getInstance().load(
            applicationContext,PreferenceManager.getDefaultSharedPreferences(applicationContext)
        )

        setContentView(R.layout.activity_main)

        // MapViewを取得する
        val mapView = findViewById<MapView>(R.id.mapView)

        // 中心位置を決める 今回は東京駅
        val centerPoint = GeoPoint(35.6812362,139.7671248)

        mapView.setTileSource(TileSourceFactory.MAPNIK)
        val mapController: IMapController? = mapView.controller

        // Zoomサイズを指定する
        mapController?.setZoom(17)
        mapController?.setCenter(centerPoint)

    
    }
}
```

![スクリーンショット 2023-12-29 10.42.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1209385/794211c9-b17d-0e27-c625-0bb2542ee57e.png)

これで簡単にですが、Mapの表示をすることができました！！

## コンパスの表示と、位置情報の表示をしてみる

今のままのマップだと、ただMapが表示されているだけでかなり寂しいですね。
そこで、GoogleMapなどでよくある、自分の位置情報を表示してくれる矢印と向きを表示するコンパスを表示してみましょう。

```kotlin:MainActivity.kt


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // setContentViewより前に置いておく、
        Configuration.getInstance().load(
            applicationContext,PreferenceManager.getDefaultSharedPreferences(applicationContext)
        )

        setContentView(R.layout.activity_main)

        // MapViewを取得する
        val mapView = findViewById<MapView>(R.id.mapView)

        // 中心位置を決める 今回は東京駅
        val centerPoint = GeoPoint(35.6812362,139.7671248)

        mapView.setTileSource(TileSourceFactory.MAPNIK)
        val mapController: IMapController? = mapView.controller

        // Zoomサイズを指定する
        mapController?.setZoom(17)
        mapController?.setCenter(centerPoint)


        //↓ここに追加する↓
        // 自分の位置を表示する矢印が出てきてくれる
        val locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(context), mapView)
        locationOverlay.enableMyLocation()
        mapView.overlays.add(locationOverlay)

        // 自分の向きを表示するコンパスが表示される
        val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
        compassOverlay.enableCompass()
        mapView.overlays.add(compassOverlay)

    
    }
}
```

Mapがにぎやかになってきましたね！！

![スクリーンショット 2023-12-29 10.49.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1209385/8088d787-ab14-d0fb-1b3e-a21d4806283d.png)


## 位置情報を自動で取得して、中心位置を決めてもらう
位置情報をもらうコードも一緒に書いたバージョンです。
よくGoogleMapなどは、自分の位置情報を自動で取得して中心位置を変更してくれます。
そのコードを書いてみましょう。
今回はパーミッションを取得するコードは書いていないため、手動で設定側からオンにしておく。

`fusedLocationClient`を用いてアプリが起動して一度だけ位置情報を取得するようにしてみました。

位置情報の取得はこの辺りが参考になります。

https://developer.android.com/training/location/retrieve-current?hl=ja

```kotlin:MainActivity.kt


class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // setContentViewより前に置いておく、
        Configuration.getInstance().load(
            applicationContext,PreferenceManager.getDefaultSharedPreferences(applicationContext)
        )

        setContentView(R.layout.activity_main)

        // MapViewを取得する
        val mapView = findViewById<MapView>(R.id.mapView)

        // ロケーションの取得するクライアントを取得
        val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)

        // パーミッションチェック
        if (ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
                context,
                Manifest.permission.ACCESS_COARSE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            return
        }
        // 位置情報をもらう
        fusedLocationClient.lastLocation
            .addOnSuccessListener { location: Location? ->
                if (location != null) {
                    val centerPoint = GeoPoint(location.latitude, location.longitude)
            
                    mapView.setTileSource(TileSourceFactory.MAPNIK)
                    val mapController: IMapController? = mapView.controller
            
                    // Zoomサイズを指定する
                    mapController?.setZoom(17)
                    mapController?.setCenter(centerPoint)

                   
                } else {
                    Log.d(TAG, "Location not available")
                }
            }

        // 自分の位置を表示する矢印が出てきてくれる
        val locationOverlay = MyLocationNewOverlay(GpsMyLocationProvider(context), mapView)
        locationOverlay.enableMyLocation()
        mapView.overlays.add(locationOverlay)

        // 自分の向きを表示するコンパスが表示される
        val compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), mapView)
        compassOverlay.enableCompass()
        mapView.overlays.add(compassOverlay)
    }
}
```

## ソースコードについて

今回、しっかりと動作を別クラスにまとめてわかりやすくしたり、他の機能を追加したソースコードを作成しました。
一度こちらのソースコードをクローンして使用してもらった方がわかりやすいかもしれないです。

https://github.com/harutiro/osmdroidTest


# 終わりに
今回オープンソースのMapシステムOpenStreatMapのライブラリosmdroidを使用しました。
ApiKeyなどが必要ないため、ちょっとした検証や勉強をするだけの利用でしたら簡単に行えるため便利だと感じました。
しかし、Apiとして提供している機能が少なかったり、記事が少ない関係で、企業で開発するアプリとしてはあまり向いていないのかなと感じました。

ぜひ一度、さまざまなMapを使用してみてはいかがでしょうか？