便利ページ：plus codeのエンコード・デコード

GoogleMapで見かけるようになったPluscodeをエンコードして緯度経度を算出したり、その逆をしてみます。

「[便利ページ：Javascriptでちょっとした便利な機能を作ってみた](https://qiita.com/poruruba/items/6c67c7eef34ed302feb3 )」のシリーズものです。

Pluscodeは、位置情報(緯度経度)を短縮した文字にエンコードしたコードをいいます。
GoogleMapでは以下の部分です。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ce00e112-e4dd-7b70-ae6b-25698cea12ec.png)

2種類のエンコード方式があります。

* Full：少々長いコードですが、緯度経度の値を表します。
例：8Q7XFJ3V+25

* Short：少々短いコードですが、狭い範囲しか位置を特定できません。したがって、どこの場所の狭い範囲なのかおおよその位置を別途町名等で補います。こちらがGoogleMapではよく使われます。
例：FJ3V+25 横浜市、神奈川県

上記の例は同じ場所を表しています。
Pluscodeから緯度経度への算出には以下のライブラリを使わせていただきました。

google/open-location-code

https://github.com/google/open-location-code

Shortの場合は、補った町名等の情報から、おおよその位置を特定する必要があり、その方法は実装依存です。今回はその特定に「国土地理院のAPI」を使わせていただきました。

国土地理院のAPIは、「 https://msearch.gsi.go.jp/address-search/AddressSearch 」で示されるWebAPIのことです。
ちなみに、Google Map APIを使う方法もありますが、今回は使っていません。

ソースコードもろもろは、以下に上げておきました。

poruruba/utilities

https://github.com/poruruba/utilities

すぐに使いたい場合は以下から参照し、ユーティリティタブからGeocodingを選択してください。（すべてJavascriptのみで動作します）

https://poruruba.github.io/utilities/

# pluscode(Short)から緯度経度への変換

Pluscode(Short)から緯度経度への変換は以下の手順で行います。

①	Pluscode(short)からCodeFragmentとlocalityの分離
②	Localityからおおよその緯度経度の変換
③	CodeFragmentとおおよその緯度経度からPluscode(Full)への変換
④	Pluscode(Full)から正確な緯度経度への変換

## ①Pluscode(short)からCodeFragmentとlocalityの分離

Pluscode(short)は以下のようなフォーマットです。

FJ3V+25 横浜市、神奈川県

前半部分と後半部分に分ける必要があります。
前半をCodeFragment、後半をlocalityと呼ぶことにします。
適当ですがこんな感じ。

```js:/js/comp/comp_geocode.js
        const pluscode = this.input_pluscode.trim(); 
        const index = pluscode.indexOf(' ');
        const codeFragment = pluscode.slice(0, index);
        const locality = pluscode.slice(index + 1).trim();
```
CodeFragmentがFJ3V+25、localityが横浜市、神奈川県です。

## ②Localityからおおよその緯度経度の変換

国土地理院のAPIを使って変換します。
呼び方はこんな感じです。localityのところは、ちゃんとURLエンコードしましょう。

```
https://msearch.gsi.go.jp/address-search/AddressSearch?q=[locality]
```

```
例：
https://msearch.gsi.go.jp/address-search/AddressSearch?q=横浜市、神奈川県
結果：
[
	{
		"geometry": {
			"coordinates": [
				139.634216,
				35.450336
			],
			"type": "Point"
		},
		"type": "Feature",
		"properties": {
			"addressCode": "",
			"title": "神奈川県横浜市"
		}
	}
]
```

おおよその緯度：35.450336
おおよその経度：139.634216

ですね。

## ③CodeFragmentとおおよその緯度経度からPluscode(Full)への変換

open-location-codeを使います。

```js:/js/comp/comp_geocode.js
          pluscode(Full) = OpenLocationCode.recoverNearest(codeFragment, 緯度, 経度);
```

```
例
pluscode(Full) = OpenLocationCode.recoverNearest(“FJ3V+25”, 35.450336, 139.634216);
結果：8Q7XFJ3V+25
```

## ④Pluscode(Full)から正確な緯度経度への変換

Pluscode(Full)から、正確な緯度経度を算出します。
open-location-codeを使います。

```js:/js/comp/comp_geocode.js
codeArea = OpenLocationCode.decode(fullCode);
```

```
例：
codeArea = OpenLocationCode.decode(“8Q7XFJ3V+25”)
結果：codeAreaはこんな感じです。
{
  Codelength: 10,
  latitudeCenter: 35.4525625,
  longitudeCenter: 139.64293750000007,
  latitudeHi: 35.452625,
  latitudeLo: 35.4525
  longitudeHi: 139.6430000000001,
  longitudeLo: 139.64287500000006
}
```

正確な緯度：latitudeCenter = 35.4525625
正確な経度：longitudeCenter = 139.64293750000007

です。

# 緯度経度からpluscode(Full)への算出

今度は逆に、緯度経度からpluscode(Full)を算出します。
open-location-codeを使います。

```js:/js/comp/comp_geocode.js
const pluscode = OpenLocationCode.encode(緯度, 経度);
```

```
例：
OpenLocationCode.encode(35.4525625, 139.64293750000007);
結果：8Q7XFJ3V+25
```

# (参考) Google Map APIの場合

GoogleAPIキーの払い出しが必要です。
以下の権限が必要だと思います。
* Geocoding API

(参考)
https://developers.google.com/maps/documentation/geocoding/?hl=ja

```js
const GOOGLE_API_KEY = "【GoogleAPIキー】";
const GOOGLE_API_GEO_URL = "https://maps.googleapis.com/maps/api/geocode/json";

async function pluscode2location(pluscode){
	var params = {
		language: "ja",
		address: pluscode,
		key: GOOGLE_API_KEY
	};
	var result = await do_get(GOOGLE_API_GEO_URL, params);
	console.log(result);

	var comp = result.results[0].address_components;
	var pref = comp[comp.length - 2].long_name;
	var city = "";
	for( var i = comp.length - 3 ; i >= 0 ; i-- ){
		if( comp[i].types[0] == 'plus_code')
			continue;
		city += comp[i].long_name;
	}
	var address = { pref: pref, city: city };

	var location = result.results[0].geometry.location;
	var latlng = { lat: location.lat, lng: location.lng };

	return { latlng: latlng, address: address };
}

async function latlng2location(lat, lng){
	var params = {
		latlng: String(lat) + "," + String(lng),
		result_type: "locality",
		language: "ja",
		key: GOOGLE_API_KEY
	};
	var result = await do_get(GOOGLE_API_GEO_URL, params);
	console.log(result);

	var comp = result.results[0].address_components;
	var pref = comp[comp.length - 2].long_name;
	var city = "";
	for( var i = comp.length - 3 ; i >= 0 ; i-- )
		city += comp[i].long_name;
	var address = { pref: pref, city: city };

	var location = result.results[0].geometry.location;
	var latlng = { lat: location.lat, lng: location.lng };	

	return { latlng: latlng, address: address };
}

function do_get(url, qs) {
  var params = new URLSearchParams(qs);

  var params_str = params.toString();
  var postfix = (params_str == "") ? "" : ((url.indexOf('?') >= 0) ? ('&' + params_str) : ('?' + params_str));
	console.log(url + postfix);
  return fetch(url + postfix, {
      method: 'GET',
    })
    .then((response) => {
      if (!response.ok)
        throw new Error('status is not 200');
      return response.json();
    });
}
```

以上