CordovaアプリでGoogleMapからロケーション情報を共有する


[散歩ルート計画アプリを作った](https://qiita.com/poruruba/items/90d25395670f1bf5a5fe) の作成時に習得したテクニックです。

GoogleMapでスポットをクリックすると以下のような詳細ダイアログが表示されます。
そのダイアログの右上に共有ボタンがあり、これを選択すると他のアプリにこの情報が共有されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/c381f679-4658-15d1-5eff-b97f210c87fe.png)

この共有機能をCordovaアプリで実現してみます。

# 共有内容
 
共有ボタンをクリックすると、以下のような共有先アプリの選択ダイアログが表示されます。
今回はこの選択先アプリに含まれるようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ceb1f015-ced4-7d13-a028-cdb8c564b351.png)

この共有機能は一般的な機能で、いろんなアプリで使えます。以下は、乗換案内アプリでの共有です。
この経路を共有・登録を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/63f66bd0-31d9-6250-c91d-a362cc49034f.png)

その他 を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ffeaa661-2c58-521f-cf28-d06dbef4c5d3.png)
 
こんな感じで共有先アプリが選択できます。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/abb29c91-d64c-9fad-5756-c2221ef886f0.png)


# 共有を受け取る

共有を受け取るには、以下のプラグインを使います。

https://github.com/napolitano/cordova-plugin-intent

以下のように、GitHubから直接インストールしてください。

```
> cordova plugin add https://github.com/napolitano/cordova-plugin-intent
```

次にconfig.xmlに以下を追記します。

```xml
    <platform name="android">
      <config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
          <intent-filter>
              <action android:name="android.intent.action.SEND" />
              <category android:name="android.intent.category.DEFAULT" />
              <data android:mimeType="text/plain" />
          </intent-filter>
      </config-file>
      <preference name="AndroidLaunchMode" value="singleTask"/>
    </platform>
```

GoogleMap等からの共有でアプリが立ち上がる時に別インスタンスが立ち上がるのを避けるため、singleTaskの指定もしておいた方がよいです。

一緒に、androidのnamespaceも追記します。

```xml
<widget id="jp.or.myhome.sample.PlanningClient" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
```

あとは、Javascriptで以下を実行しておきます。

```js
        window.plugins.intent.setNewIntentHandler(this.onNewIntent);
```
受け取りたい情報は、``` intent.extras["android.intent.extra.TEXT"]``` にあります。

```js
        onNewIntent: async function (intent) {
            console.log("onNewIntent:", intent);
            if (!intent.extras || !intent.extras["android.intent.extra.TEXT"])
                return;
```

以下の条件に合致するとき、GoogleMapからの共有であることがわかるようです。

```js
if (intent.extras["androidx.core.app.EXTRA_CALLING_PACKAGE"] == "com.google.android.apps.maps")
```

```intent.extras["android.intent.extra.TITLE"]``` というのも付けてくれるようです。
何を渡してくれるかは共有元のアプリケーションに依存します。


# GoogleMapが渡してくれる情報と解析

GoogleMapから共有される情報は以下のようなURLです。

```https://maps.app.goo.gl/WiWYny4XvgKU6nKH6```

これだけではよくわからないのですが、さらに処理を進めます。
このURLにアクセスすると別のURLにリダイレクトされます。そのURLに少し情報が含まれてきます。

```https://www.google.co.jp/maps/place/鬼怒川ライン下り/@36.8256636,139.7118355,16.92z/data=!4m6!3m5!1s0x601f9f37204fa7cb:0x45a454a53ed1f670!8m2!3d36.8257502!4d139.7155352!16s/g/1ttpg_bn?coh=219816&entry=tts&g_ep=EgoyMDI0MDgxMi4wKgBIAVAD```

緯度経度とスポット名称があるようです。
この緯度経度は少し荒いため、正確な位置情報から少しずれてしまうようです。
そこで、この緯度経度の近辺100m四方で、スポット名称を検索してみます。
検索には、GeoCoderを使います。

GeoCoderで検索し、スポットを見つけるとPlace IDが返ってきますので、Place APIを使ってPlaceIDから詳細なスポット情報を取得します。


# 実装編

## リダイレクトURLの取得

リダイレクトURLを取得するには、クライアント側のJavascriptではできず、サーバ側のNode.jsの力を借りることとしました。redirect: "manual" がポイントです。

```js
	if( event.path == '/planning-parse-redirect') {
		var response = await fetch( body.url, {redirect: "manual" });
		if( response.status == 301 || response.status == 302 ){
			var url = new URL(response.headers.get('location'), response.url);
			return new Response({ location: url.toString(), status: response.status });
		}else{
			throw new Error("not redirect");
		}
	}else
```
## 緯度経路・スポット名の抽出

以下のようなURLから、緯度経路・スポット名を抽出します。

```https://www.google.co.jp/maps/place/鬼怒川ライン下り/@36.8256636,139.7118355,16.92z/data=!4m6!3m5!1s0x601f9f37204fa7cb:0x45a454a53ed1f670!8m2!3d36.8257502!4d139.7155352!16s/g/1ttpg_bn?coh=219816&entry=tts&g_ep=EgoyMDI0MDgxMi4wKgBIAVAD```

```js
function extractPlaceData(url) {
    const namePattern = /\/maps\/place\/([^\/]+)\//;
    const coordinatePattern = /@([-+\d.]+),([-+\d.]+)/;

    const nameMatch = url.match(namePattern);
    const name = nameMatch ? decodeURIComponent(nameMatch[1].replace(/\+/g, ' ')) : null;

    const coordinateMatch = url.match(coordinatePattern);
    const latitude = coordinateMatch ? parseFloat(coordinateMatch[1]) : null;
    const longitude = coordinateMatch ? parseFloat(coordinateMatch[2]) : null;

    return {
        name: name,
        lat: latitude,
        lng: longitude,
    };
}
```
## PlaceIDの取得

指定した緯度経路の周辺100m四方の範囲を以下のように生成します。

```js
function createBounds(center, radius) {
    const latPerMeter = 1 / 110574;
    const lngPerMeter = 1 / (111320 * Math.cos(center.lat() * Math.PI / 180));

    const latOffset = radius * latPerMeter;
    const lngOffset = radius * lngPerMeter;

    const northEast = new google.maps.LatLng(center.lat() + latOffset, center.lng() + lngOffset);
    const southWest = new google.maps.LatLng(center.lat() - latOffset, center.lng() - lngOffset);

    return new google.maps.LatLngBounds(southWest, northEast);
}
```

あとは、以下のようにして呼び出せばよいです。

```js
    return new Promise((resolve, reject) => {
        geocoder.geocode({ address: result.name, bounds: bounds }, (results, status) => {
            if (status === "OK") {
                places.getDetails({ placeId: results[0].place_id, fields: ["name", "geometry", "place_id", "website"] }, (place, status) => {
                    if (status == "OK")
                        return resolve(place);
                    else
                        return reject(status);
                });
            } else {
                return reject(status);
            }
        });
    });
}
```
geocoder.geocodeの部分です。
geocoderはあらかじめ以下のようにして生成しておきます。
```geocoder = new google.maps.Geocoder();```

これで、PlaceIDが返ってきます。
ここから、Place APIを使ってスポットの詳細な情報を取得します。
places.getDetailsの部分です。
placesは以下のようにあらかじめ生成しておきます。
```places = new google.maps.places.PlacesService(map);```

それにより、正確な緯度経度やスポット名称等が得られます。

PlaceAPIを使うためには、APIキーを払い出すときに、Place APIも有効なキーである必要があります。
また、ライブラリの宣言時に、librariesとしてplaceを含める必要があります。

```html
<script src="https://maps.googleapis.com/maps/api/js?key=【APIキー】&libraries=places&callback=initMap" async defer></script>
```

(参考)
・https://developers.google.com/maps/documentation/javascript/reference/geocoder?hl=ja
・https://developers.google.com/maps/documentation/javascript/reference/places-service?hl=ja

以上