THETA X でOpenStreetMapを利用する（前編）

## はじめに

リコーの @KA-2 です。

弊社では[RICOH THETA](https://theta360.com/ja/)という全周囲360度撮れるカメラを出しています。
<B>RICOH THETA V</B>, <b>RICOH THETA Z1</b>, <b>RICOH THETA X</b>は、OSに<B>Android</B>を採用しています。Androidアプリを作る感覚でTHETAをカスタマイズすることもでき、そのカスタマイズ機能を「プラグイン」と呼んでいます（詳細は本記事の末尾を参照）。

前回の記事「[THETA X で衛星測位の結果を利用する方法](https://qiita.com/KA-2/items/7b27979ebeacb86b2295)」では、衛星測位結果をダイレクトに取得する方法をお伝えしました。<br>今回は、[OpenStreetMap](https://openstreetmap.jp/#zoom=5&lat=38.06539&lon=139.04297&layers=000B)という地図情報を利用し、測位した位置（指定した位置の座標）を地図に重ねて表示する方法をお伝えします。

このプラグインが動作している様子は以下ツイートのとおり。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">◤Qiita記事公開◢<br>THETA X はGNSS(GPS)を内蔵しています。衛星測位結果をOpenStreetMapの地図に表示するプラグインを作成しました！<br>サンプルはストアにも公開済みです！<a href="https://t.co/82EUzxmpJU">https://t.co/82EUzxmpJU</a><a href="https://twitter.com/hashtag/THETA?src=hash&amp;ref_src=twsrc%5Etfw">#THETA</a> <a href="https://twitter.com/hashtag/THETAX?src=hash&amp;ref_src=twsrc%5Etfw">#THETAX</a> <a href="https://twitter.com/hashtag/thetaplugin?src=hash&amp;ref_src=twsrc%5Etfw">#thetaplugin</a> <a href="https://twitter.com/hashtag/android?src=hash&amp;ref_src=twsrc%5Etfw">#android</a> <a href="https://twitter.com/hashtag/OpenStreetMap?src=hash&amp;ref_src=twsrc%5Etfw">#OpenStreetMap</a> <a href="https://twitter.com/hashtag/osmdroid?src=hash&amp;ref_src=twsrc%5Etfw">#osmdroid</a> <a href="https://t.co/2Q0KoeaS5t">pic.twitter.com/2Q0KoeaS5t</a></p>&mdash; THETAプラグイン開発者コミュニティ (@thetaplugin) <a href="https://twitter.com/thetaplugin/status/1580030548008873984?ref_src=twsrc%5Etfw">October 12, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


なお、この記事で紹介する事例は、THETA PLUG-IN STORE に公開してあります。
https://pluginstore.theta360.com/plugins/skunkworks.currentlocation/

[![000_ストア画像案.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265455/00ad7b75-74bf-44c4-0301-abddc9bbbffd.png)](https://pluginstore.theta360.com/plugins/skunkworks.currentlocation/)

[前回の記事](https://qiita.com/KA-2/items/7b27979ebeacb86b2295)のプラグインよりも、衛星測位が動いているか視覚的にすぐにわかります。<br>RICOH THETA Xをお持ちの方は、ぜひ試してみてください！


## なぜ、Google Mapでなく、OpenStreetMapなのか？

幾らかの制約が生じるかもしれませんが、「実験のため、開発者個人が、Google Mapを埋め込んだTHETAプラグインを作成し動作させる」ということは可能と思われます。（私自身は作成していませんが、スマートフォン用でGoogle Mapを埋め込んだアプリの中に動作するものがあったので、可能と思われます）

しかし、作成にあたり「Google Maps API key」が必要です。
このAPIキーは、GoogleがAPIキーの地図利用頻度（データ量）を測定するために利用され、利用頻度によっては、APIキーの持ち主に[課金](https://mapsplatform.google.com/intl/ja/pricing/)も生じます。
また、Google Map をアプリに埋め込んだ場合、「Google Play 開発者サービス」が動く（＝Google Playに対応した）Android機器で動作させる約束事もあります。
RICOH THETA V,Z1,XはいずれもGoogle Playに対応していない機器ですので、このようなプラグインを[THETA PLUG-IN STORE](https://pluginstore.theta360.com/)に置くことはできません。

もしも、無理やり動作させ、地図の利用頻度があまりにも多いような場合、そのAPIキーが利用できなくなるかもしれません。Googleが悪質と判断した場合は、APIキー持ち主のGoogleアカウントがBANされる可能性も否めません。（よほど酷いときでしょうけれども……）

そういった制限が一切なく利用もできるのが[OpenStreetMap](https://openstreetmap.jp/#zoom=5&lat=38.06539&lon=139.04297&layers=000B)となります。
詳細はリンク先を参照してください。

国や地域によって、詳細な表示をしたときの情報量が少な目な地点があるものの（私の実家の某地域は、建物の情報がないケースもありましたが）十分に使える地図だと思います。

横浜エリアだとこんな感じです。

![00_Osmdroid表示例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265455/4b51db05-8731-df35-25e2-3d6429b91782.png)

スタジアムは客席までわかりますし、大さん橋や山下公園も徒歩ルートが赤い点線で描画されています。細かなところまでわかりますね。


## osmdroid 

AndoroidでOpenStreetMapを利用するためのライブラリがosmdroidです。<br>GitHubに公開されています。URLは以下となります。<br>https://github.com/osmdroid/osmdroid

上記のREADMEにも記載がありますが、Androidのスマートフォンやタブレットをお持ちの方は、以下アプリをインストールすると、osmdroidでできることが一通り動作確認できます。<br>https://play.google.com/store/apps/details?id=org.osmdroid

[GitHub内にWiki](https://github.com/osmdroid/osmdroid/wiki)も用意されており、こちらの説明にしたがって、THETA Plug-in SDKにosmdroidをとりこんで OpenStreetMapの地図をTHETAプラグインから利用しました。

詳しい説明は「ソースコード」の章で行いますね。


## THETA XとTHETA Z1 51GBの日本モデルにおけるプラグイン開発の制限


THETA Xで地図情報を取得するにはネットワークに接続する必要があります。（キャッシュされた地図はオフラインでも利用できますが、少なくとも１回は接続しないと大まかな地図すら表示できません）

[こちらで案内されている](https://topics.theta360.com/ja/news/2022-09-02/)通り、THETA XとTHETA Z1 51GBの日本モデルについても、一般開発者の方がプラグイン開発できるようになりました。
ただし、<b>一般開発者の方は、クライアントモードで動作する（=ネットワーク接続する）プラグインを開発することができません。</b>
（THETA XとTHETA Z1 51GBの日本モデルにおいて、一般開発者の方は、開発者モードにしているときにCLモードにできなくなります。普段利用をするときは開発者モードを解除し、プラグイン開発をするときは開発者モードにする、という都度都度の手間が生じますのでご注意を）

ネットワーク接続が必要なTHETAプラグインをTHETA XとTHETA Z1 51GBの日本モデルで開発するには、総務省において公布された「端末設備等規則及び電気通信主任技術者規則の一部を改正する省令」を守れる企業であるかの審査を通る必要があります。
申請フォームは[こちら](https://topics.theta360.com/ja/news/2022-09-02-2/)です。


THETA Xの日本モデルをお持ちの"一般開発者の方"は、今回紹介するサンプルコードをクライアントモードでは動作確認できないことになります。
（アクセスポイントモードや無線LANをオフにした状態で動かすことはできますが、、、地図を取得できません）

<b>ですが、一般開発者の方もご安心ください！</b>

今回ストアに置いた地図プラグインは、

- 他のプラグインから地図プラグインを呼び出す。
- 上記で呼び出した地図プラグインを終了すると、呼び出し元のプラグインに戻る。<br>（戻り方に少し制約がつきます）

ということが、THETAプラグインの約束事「[Plug-in Policy](https://github.com/ricohapi/theta-api-specs/blob/main/ricoh-theta-plugin/policy.md)」の中でも、特に以下事項

> 2 . User experience
> Plug-ins must not be enabled to operate in the background.
>
> 3 . Compatibility with hardware
> Developers must use the SDK provided by RICOH for RICOH THETA plug-ins.

を守った状態で行えます！

このようなプラグインの作り方、利用方法については[次回記事](https://qiita.com/KA-2/items/8ff4e3666423ca069744)で紹介することとして、今回は、OpenStreeMapをTHETAプラグインで利用する方法について解説しますね。
（GitHubのREADMEに記載があり、Google Mapの呼び出し方に倣っているので、それを読み解ける方はコードを書けるのですが、地図プラグイン呼び出しと戻る部分の動作確認をするときに、一旦開発者モードを解除しなければならない制約が生じると思います。この辺りについても次回記事で説明します。）


## ソースコード

[こちらのプロジェクト一式](https://github.com/theta-skunkworks/theta-plugin-current-location)を参照してください。<br>このソースコードは、次回で説明する内容（他のプラグインからこのプラグインを呼び出す部分）も含んでいます。

### 公開しているサンプルコードのファイル構成

公開しているサンプルコードのファイル構成は以下のとおりです。

```
theta-plugin-time-adjuster-via-gnss\app
└src\main
　├assets　　　　　　　 // ベースとしたプロジェクトのままです。
　├java\com\theta360
　│　　└pluginapplication
　│　　　├model　　 　　　// ベースとしたプロジェクトのままです。
　│　　　└network　 　　　// HttpConnector.javaは過去記事と同じメソッド追加をしています。が、今回はTHETAのWebAPI未使用なので不要ではあります。
　│　　　※"task"のフォルダは今回不要なので削除してあります。
　│　　
　└java\skunkworks　　　　// pakegeNameの都合から MainActivity.javaなどを置くフォルダを分けています。
　 　　└gnsstimeadjuster　// MainActivity.java　はプラグインの全体構造が記述されています。
　　　　　　　　　　　　　//　以下は新規追加したファイルです。ダイアログ毎に作成しています。
　　　　　　　　　　　　　//　OfflineWarningDaialog.java
　　　　　　　　　　　　　//　PositionInfoAddOffDaialog.java
```

以降は、THETA Plug-in SDKをベースにOpenStreetMapを取り込む最小限の方法について解説します。<br>公開しているソースコードと多少の差はあるものの、主用な部分に大きな差はないため、公開しているサンプルコードも読み解けるようになると思います。

### gradleを編集し osmdroidを取り込む

まず、2種類のbuild.gradleファイルを編集します。

1つはプロジェクトのルートフォルダにあるbuild.gradleです。
「mavenCentral()」の一行を追加しています。

```build.gradle
    repositories {
        google()
        jcenter()
        mavenCentral() // 追加
    }
```

もうひとつは appフォルダ配下のbuild.gradleです
dependenciesフィールドにosmdroidのimplementationを追加しています。
バージョンは作成時点の最新版です。

```app/build.gradle
dependencies {
    implementation 'org.osmdroid:osmdroid-android:6.1.13'

```

### AndroidManifestの編集

パーミッションを以下のようにしています。
下の2行が追加した権限です。位置情報取得とファイルアクセスの権限を追加しています。

```AndroidManifest.xml
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
```


### MapViewの追加

ここからは、GoogleMapを埋め込む時と作法が異なるので注意してください。
AndroidStudioでactivity_main.xmlを開いた後、「Code」をクリックして直接ファイルを編集します。

![01_AndroidStudio_Code.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265455/b390b063-52ec-97d4-b487-48699c2f9f46.png)

```activity_main.xml
      <org.osmdroid.views.MapView android:id="@+id/mapView"
                android:layout_width="fill_parent" 
                android:layout_height="fill_parent" />
```

このViewを追加したあと、「Design」をクリックするとあとからお好みのサイズに編集できます。
（MainActivityのコードも書かないと警告がでるので、一通り地図の埋め込み作業を終えてから整えたほうがよいと思います。THETA Plug-in SDKに元から記載があるTextViewの削除なども後から行ったほうがよいです。）

### MainAvtivityの編集

onCreateの「setContentView(R.layout.activity_main);」の前の行に以下の1行を追加します。

```MainAvtivity.java
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 注意　setContentView　より前に置く
        Configuration.getInstance().load(getApplicationContext(), PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));

        setContentView(R.layout.activity_main);

```

MainAvtivityにこれから利用する変数を追加します。

```MainAvtivity.java
    MapView mMapView;
    MyLocationNewOverlay mLocationOverlay;
    IMapController mapController;
```

onCreateの「setContentView(R.layout.activity_main);」の下に以下を追加します。

```MainAvtivity.java
        mMapView = (MapView) findViewById(R.id.mapView);
        //以下2行で、ライブラリの機能でタッチズームできるようになります。
        mMapView.setBuiltInZoomControls(true);
        mMapView.setMultiTouchControls(true);

        //地図の著作権表示です。プラグインを公開する場合表示するお約束です。
        mMapView.getOverlays().add(new CopyrightOverlay(this));

        mapController =  mMapView.getController();

```

さらにその下行に以下を記載します。

```MainAvtivity.java
        //測位前の初期位置を設定する
        mapController.setZoom( 15.0 ); //ズームの初期値です。
        GeoPoint centerPoint = new GeoPoint( 35.710058, 139.810718 );//東京スカイツリーの座標
        mapController.setCenter(centerPoint);

        //測位できたら、マークをオーバレイしてフォローする動作を有効にする
        mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(this),mMapView);
        mLocationOverlay.enableMyLocation();
        mLocationOverlay.enableFollowLocation();
        mMapView.getOverlays().add(mLocationOverlay);
```

上記コードは、公開しているサンプルコードですと、[InitialLocationAndZoomというメソッド](https://github.com/theta-skunkworks/theta-plugin-current-location/blob/main/app/src/main/java/skunkworks/currentlocation/MainActivity.java#L233-L244)に記述してある内容と等価です。この部分はonCreateではなくonResumeにあっても問題ありません。

その他参考情報として、今回"15.0"とダイレクトに数値指定しているズーム初期値の設定可能な範囲は、以下メソッドで取得可能です。

- 最大値：mMapView.getMaxZoomLevel()
- 最小値：mMapView.getMinZoomLevel()

あまり極端な値にすると、どこを表示しているのかわからなくなるので注意です。
その他の便利メソッドの使い方は、[osmdroidのWiki](https://github.com/osmdroid/osmdroid/wiki)を参考にしてください。

<b>ここまでの作業でビルドすると、THETAプラグインに地図を埋め込み表示できます！</b>
<b>ライブラリが良くできているため、測位を意識する必要もなく、とても簡単に地図を利用できますね！</b>

なお、公開しているコードのMainActivityは、

- 地図を動かしてフォローが外れてもシャッターボタン押下で再フォローする
- プラグインを終了したときのズームレベルや座標を次回起動時の初期表示に利用する
- 無線LANの状態を確認したり、測位が有効になっているか確認し、状態に応じた振る舞いをする
- 次回記事で説明する他プラグインから呼び出されたときの処理をする

というような、細かなケアも行っています。
とはいえ、これまで解説した地図表示の主要部分に大きな差はなく、ここまで解説した情報まであれば読み解けるはずです。


### 動作確認

以下の点にご注意ください。

- デバッグの時はセッティング画面からストレージと位置情報のパーミッションを与えてください。
- ネットワーク接続するプラグイン開発が可能な方でも、CLモード中は、ストアに公開されていないプラグインをメニューから起動することができません。セッティング画面から起動してデバッグしてください。<br>![03_プラグイン起動.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/265455/f0236131-169d-186f-a7ed-2ad133fa4a09.png)


- RICOH THETA Xにおけるセッティング画面の開き方は以下を参照してください。<br>[「RICOH THETA X におけるプラグイン開発」の記事の「Settings画面の開き方」](https://qiita.com/KA-2/items/35d7ef550d2aead50cb4#settings%E7%94%BB%E9%9D%A2%E3%81%AE%E9%96%8B%E3%81%8D%E6%96%B9)


### その他気づいたこと。

今回作成したプラグインは、ライブラリ任せでキャッシュされた地図をオフラインでも利用できますが、キャッシュのコントロールについてはあまり調べていません。

一方で、「明示的に、オフライン用の地図を事前ダウンロードする方法」については調べてみました。
しかし、現時点では利用禁止となっているようです。（禁止前のライブラリを使うと可能でしたが、古いライブラリのため、できなくなることもあるようです。なによりサーバーに負荷をかけてしまうため利用しない方が良いです。osmdroid側がサーバー負荷を低減した状態で本機能に再対応するのを待ちましょう。[このあたりのissues](https://github.com/osmdroid/osmdroid/issues/1297)で議論されているようでした。）



## おわりに

自身で作成したプラグインにOpenStreetMapを埋め込むことができました。
位置情報を得るための部分など、多くの面倒ごとをライブラリが担っており、とてもコーディングが楽であったかと思います。

しかし、先に説明したように、OpenStreetMapの地図をダウンロードするためにネットワーク接続が必要で、一般開発者の方は、日本モデルのTHETA X（やTHETA Z1 51GB）でネットワーク接続をするプラグインを作成することができません。

一般開発者の方も地図利用可能とするために、今回ストアに公開した地図プラグインは、他のプラグイン（一般の開発者が作成中のプラグインを含む）から地図プラグインを呼び出せ、地図プラグインを終了すると呼び出し元のプラグインに戻れるような仕掛けが入っています。（ただし、一般開発者は日本モデルでプラグイン連携の動作確認をするときに制約が生じます）

[次回](https://qiita.com/KA-2/items/8ff4e3666423ca069744)は上記の仕掛けの作り方、利用の仕方について解説しますね。


## RICOH THETAプラグインパートナープログラムについて

THETAプラグインをご存じない方は[こちら](https://github.com/ricohapi/theta-api-specs/blob/main/core/products/theta-plugin.md)をご覧ください。
パートナープログラムへの登録方法は[こちら](https://qiita.com/mShiiina/items/55d98f366e650ca42251)にもまとめてあります。
Qiitaの[RICOH THETAプラグイン開発者コミュニティ TOPページ「About」](https://qiita.com/organizations/theta-plugin)に便利な記事リンク集もあります。
興味を持たれた方は[Twitter](https://twitter.com/thetaplugin)のフォローと[THETAプラグイン開発者コミュニティ(Slack)](https://join.slack.com/t/thetadeveloper/shared_invite/enQtNzcxODI2MTk2ODg0LWY5YmU2ZTIxNGYwNzlmNWZjMWMyMWFlMzY3ZmQ3NGU3ZjZiZjI0Njk4MGIxMDU5NDJiMzQ4Y2FkNDEzYmFlNGU)への参加もよろしくおねがいします｡