ジオコーディング備忘録

# 前提
・住所入力から緯度経度を算出してDBへ登録したい
・後修正をマップGUIからできるようにしたい

**自分用備忘録でありGoogle Maps APIガイドをもとに書いていますので悪しからず。**

# Google Maps API
### ジオコーディング サービス
**概要**
ジオコーディングとは、住所を地理的座標に変換するプロセス。
リバース ジオコーディングとは、地理的位置を人間が判別可能な住所に変換するプロセス。
> Google Maps JavaScript API に含まれるジオコーダのクラスを使用すると、ユーザーの入力に応じて動的にジオコーディングやリバース ジオコーディングができます。既知の住所に対して静的にジオコーディングを行いたい場合は、[ジオコーディング ウェブサービス](https://developers.google.com/maps/documentation/geocoding/index?hl=ja)のドキュメントをご覧ください。

[参照](https://developers.google.com/maps/documentation/javascript/geocoding?hl=ja)

# はじめに
まず Google API Console で Google Maps JavaScript API をセットアップしたプロジェクトで、Google Maps Geocoding API が有効になっていることを確認する。

有効な API のリストを表示するには:
1. [Google API Console](https://console.developers.google.com/project/_/apiui/apis/enabled?hl=ja&pli=1)へ移動。
2. **[Select a project]**を選択し、Google Maps JavaScript API を設定している同じプロジェクトを選択して、**[Open]**をクリックする。
3. **[Dashboard]**のAPIリストで、Google Maps Geocoding APIを探す。
4. リストに API が表示されていれば、準備は完了。

# ジオコーディング リクエスト
ジオコーディング サービスにアクセスすると、Google Maps API は外部サーバーに対して呼び出しを行うので、処理が非同期となる。ゆえに、リクエストの完了時に実行されるコールバック メソッドを渡して、結果を処理させる必要がある。
※ジオコーダは複数の結果を返すこともある。

コード内で Google Maps API ジオコーディング サービスにアクセスするには、google.maps.Geocoder オブジェクトを使用。Geocoder.geocode() メソッドはジオコーディング サービスへのリクエストを開始し、入力したデータを含む GeocodeRequest オブジェクト リテラルと、レスポンスを受信したときに実行するコールバック メソッドを渡す。

```js
{
 address: string,
 location: LatLng,
 placeId: string,
 bounds: LatLngBounds,
 componentRestrictions: GeocoderComponentRestrictions,
 region: string
}
```

**必須パラメータ**

- `address` — ジオコードしたい住所。
- `location` — 最も近い、判読可能な住所を取得したい場所の LatLng（または LatLngLiteral）。ジオコーダはリバースジオコーディングを行う。
- `location` — 最も近い、判読可能な住所を取得したい場所のプレイスID。(プレイスIDは、Google の他の API で使用できるユニークな識別子, `placeId`を渡すと、ジオコーダはリバースジオコーディングを行う)

**省略可能パラメータ**

- `bounds` — `LatLngBounds`内で、ジオコーディングの結果に極端なバイアスをかける。
- `componentRestrictions` — 指定した領域に結果を制限するのに使用する。
- `region` — [IANA](https://ja.wikipedia.org/wiki/IETF%E8%A8%80%E8%AA%9E%E3%82%BF%E3%82%B0)の`region` サブタグとして指定される地域コード。(多くの場合、これらのタグはよく使用される ccTLD（トップレベル ドメイン）の2文字の値に直接マッピングされる。regionパラメータは、ジオコーダから返される結果に影響を与えるが、完全に制限するわけではない。)

# ジオコーディングのレスポンス
ジオコーディングサービスには、ジオコーダの結果を取得する際に実行されるコールバックメソッドが必要。このコールバックは`results`と`status`コードをこの順序で保持する2つのパラメータを渡す必要がある。

### ジオコーディングの結果
`GeocoderResults`オブジェクトは、**1件のジオコーディングの結果を表す**。(1件のジオコードリクエストで結果として複数のオブジェクトが返される場合もある。)

```js
results[]: {
 types[]: string,
 formatted_address: string,
 address_components[]: {
   short_name: string,
   long_name: string,
   postcode_localities[]: string,
   types[]: string
 },
 partial_match: boolean,
 place_id: string,
 postcode_localities[]: string,
 geometry: {
   location: LatLng,
   location_type: GeocoderLocationType
   viewport: LatLngBounds,
   bounds: LatLngBounds
 }
}

```
**[確認用URL](https://developers.google.com/maps/documentation/javascript/geocoding?hl=ja#GeocodingResults)**

### ステータスコード
`status`コードは以下のいずれかの値を返す。

- `OK`: エラーが発生しなかったことを示す。住所の解析が成功し、少なくとも1件のジオコードが返された。
- `ZERO_RESULTS`: ジオコーディングは成功したものの結果が返されなかったことを示す。
- `OVER_QUERY_LIMIT`: クエリ数が割り当て量を超えていることを示す。
- `REQUEST_DENIED`: リクエスト拒否。
- `INVALID_REQUEST`: 通常、照会条件（address、components、latlngのいずれか）がない。
- `UNKNOWN_ERROR`: サーバー エラーでリクエストが処理できなかった。(**再度リクエストすると、成功する可能性がある**。)

下記は住所をジオコードして、返された緯度と経度が示す場所にマーカーを配置する。(ハンドラを匿名の関数リテラルとして渡していることに注意。)

```html
<script type="text/javascript">
  var geocoder;
  var map;
  function initialize() {
    geocoder = new google.maps.Geocoder();
    var latlng = new google.maps.LatLng(-34.397, 150.644);
    var mapOptions = {
      zoom: 8,
      center: latlng
    }
    map = new google.maps.Map(document.getElementById('map'), mapOptions);
  }

  function codeAddress() {
    var address = document.getElementById('address').value;
    geocoder.geocode( { 'address': address}, function(results, status) {
      if (status == 'OK') {
        map.setCenter(results[0].geometry.location);
        var marker = new google.maps.Marker({
            map: map,
            position: results[0].geometry.location
        });
      } else {
        alert('Geocode was not successful for the following reason: ' + status);
      }
    });
  }
</script>

<body onload="initialize()">
 <div id="map" style="width: 320px; height: 480px;"></div>
  <div>
    <input id="address" type="textbox" value="Sydney, NSW">
    <input type="button" value="Encode" onclick="codeAddress()">
  </div>
</body>
```

## 実際に試す

```sample.html
<!DOCTYPE html>
<html>
  <head>
    <title>Geocoding service</title>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <style>
      /* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
      #map {
        height: 100%;
      }
      /* Optional: Makes the sample page fill the window. */
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
      #floating-panel {
        position: absolute;
        top: 10px;
        left: 25%;
        z-index: 5;
        background-color: #fff;
        padding: 5px;
        border: 1px solid #999;
        text-align: center;
        font-family: 'Roboto','sans-serif';
        line-height: 30px;
        padding-left: 10px;
      }
    </style>
  </head>
  <body>
    <div id="floating-panel">
      <input id="address" type="textbox" value="Sydney, NSW">
      <input id="submit" type="button" value="Geocode">
    </div>
    <div id="map"></div>
    <script>
      function initMap() {
        var map = new google.maps.Map(document.getElementById('map'), {
          zoom: 8,
          center: {lat: -34.397, lng: 150.644}
        });
        var geocoder = new google.maps.Geocoder();

        document.getElementById('submit').addEventListener('click', function() {
          geocodeAddress(geocoder, map);
        });
      }

      function geocodeAddress(geocoder, resultsMap) {
        var address = document.getElementById('address').value;
        geocoder.geocode({'address': address}, function(results, status) {
          if (status === 'OK') {
            resultsMap.setCenter(results[0].geometry.location);
            var marker = new google.maps.Marker({
              map: resultsMap,
              position: results[0].geometry.location
            });
          } else {
            alert('Geocode was not successful for the following reason: ' + status);
          }
        });
      }
    </script>
    <script async defer
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
    </script>
  </body>
</html>
```

# リバース ジオコーディング（住所検索）
一般的にジオコーディングとは、人間が読み取れる住所をマップ上の地点に変換すること、逆ジオコーディングはその反対で、地図上の地点を人間が読み取れる住所に変換するプロセス。

### 位置によるリバース ジオコーディング
以下の例では、緯度と経度の値をジオコーディングし、その場所に地図の中心を合わせ、フォーマットされた住所が入った情報ウィンドウを表示する。(2番目の結果（この例では地域名）も返されますが、1番目の結果よりも明確ではない。)

```javascript
function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 8,
    center: {lat: 40.731, lng: -73.997}
  });
  var geocoder = new google.maps.Geocoder;
  var infowindow = new google.maps.InfoWindow;

  document.getElementById('submit').addEventListener('click', function() {
    geocodeLatLng(geocoder, map, infowindow);
  });
}

function geocodeLatLng(geocoder, map, infowindow) {
  var input = document.getElementById('latlng').value;
  var latlngStr = input.split(',', 2);
  var latlng = {lat: parseFloat(latlngStr[0]), lng: parseFloat(latlngStr[1])};
  geocoder.geocode({'location': latlng}, function(results, status) {
    if (status === 'OK') {
      if (results[1]) {
        map.setZoom(11);
        var marker = new google.maps.Marker({
          position: latlng,
          map: map
        });
        infowindow.setContent(results[1].formatted_address);
        infowindow.open(map, marker);
      } else {
        window.alert('No results found');
      }
    } else {
      window.alert('Geocoder failed due to: ' + status);
    }
  });
}
```

## 実際に試す

```sample.html
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="initial-scale=1.0, user-scalable=no">
    <meta charset="utf-8">
    <title>Reverse Geocoding</title>
    <style>
      /* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
      #map {
        height: 100%;
      }
      /* Optional: Makes the sample page fill the window. */
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
      #floating-panel {
        position: absolute;
        top: 10px;
        left: 25%;
        z-index: 5;
        background-color: #fff;
        padding: 5px;
        border: 1px solid #999;
        text-align: center;
        font-family: 'Roboto','sans-serif';
        line-height: 30px;
        padding-left: 10px;
      }
      #floating-panel {
        position: absolute;
        top: 5px;
        left: 50%;
        margin-left: -180px;
        width: 350px;
        z-index: 5;
        background-color: #fff;
        padding: 5px;
        border: 1px solid #999;
      }
      #latlng {
        width: 225px;
      }
    </style>
  </head>
  <body>
    <div id="floating-panel">
      <input id="latlng" type="text" value="40.714224,-73.961452">
      <input id="submit" type="button" value="Reverse Geocode">
    </div>
    <div id="map"></div>
    <script>
      function initMap() {
        var map = new google.maps.Map(document.getElementById('map'), {
          zoom: 8,
          center: {lat: 40.731, lng: -73.997}
        });
        var geocoder = new google.maps.Geocoder;
        var infowindow = new google.maps.InfoWindow;

        document.getElementById('submit').addEventListener('click', function() {
          geocodeLatLng(geocoder, map, infowindow);
        });
      }

      function geocodeLatLng(geocoder, map, infowindow) {
        var input = document.getElementById('latlng').value;
        var latlngStr = input.split(',', 2);
        var latlng = {lat: parseFloat(latlngStr[0]), lng: parseFloat(latlngStr[1])};
        geocoder.geocode({'location': latlng}, function(results, status) {
          if (status === 'OK') {
            if (results[1]) {
              map.setZoom(11);
              var marker = new google.maps.Marker({
                position: latlng,
                map: map
              });
              infowindow.setContent(results[1].formatted_address);
              infowindow.open(map, marker);
            } else {
              window.alert('No results found');
            }
          } else {
            window.alert('Geocoder failed due to: ' + status);
          }
        });
      }
    </script>
    <script async defer
    src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap">
    </script>
  </body>
</html>
```
※（results[1] を選択して）2 番目の結果を示している。リバース ジオコーダーは複数の結果を返すこともあるため注意。


# 更新履歴
February 27th, 2018. // First post.