Google Maps API でGeocodingしようとしたらundefinedになる

#はじめに
Google Maps API でGeocodingしようと思ったら、色々とハマったのでメモ。
Geocodingの結果がundefinedで、ずいぶん長く格闘した記事になります。

基本的なGoogle Maps API の使い方は[こちら](http://qiita.com/Arashi/items/2aa756c942e0de323a97)へ。

#そもそもGeocodingとは

ジオコーディングというのは、住所や地名、駅名などの情報を、緯度・経度の座標値に変換する技術です。
逆も然りで、緯度・経度から住所や地名を取得することも可能です。
それは、逆ジオコーディング(Reverse geocoding)と呼ばれます。

それでは本題へといきましょう。

#基本的なGeocodingの書き方
まずは、[公式ドキュメント](https://developers.google.com/maps/documentation/javascript/examples/geocoding-simple)を参考に書いてみます。

```html:geocode.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>
        html, body {
            height: 100%;
            margin: 0;
            padding: 0;
        }
        #map {
            height: 500px;
            width: 50%;
        }
        #floating-panel {
            position: absolute;
            top: 10px;
            left: 35%;
            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="東京">
    <input id="submit" type="button" value="Geocode">
</div>
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js"></script>
<script>
    function initMap() 
    {
        var myLatLng = {lat: 35.658581, lng: 139.745433};
        var map = new google.maps.Map(document.getElementById('map'), {
            zoom: 15,
            center: myLatLng
        });
        var marker = new google.maps.Marker({
            position: myLatLng,
            map: map
        });
        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 === google.maps.GeocoderStatus.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);
            }
        });
    }
    initMap();
</script>
</body>
</html>
```

![test3.png](https://qiita-image-store.s3.amazonaws.com/0/50208/6ea87a62-7e66-4b1d-9e75-2ce5d237d10d.png)

はい。こんな感じです。
何をしているのかを簡単に説明していきますね。

##マップの表示
まず、マップの表示部分である、**`initMap()`**から。
基本的なマップの表示から、マーカー表示、イベントの設定をしてあげてます。

```js
    function initMap() 
    {
        var myLatLng = {lat: 35.658581, lng: 139.745433};
        var map = new google.maps.Map(document.getElementById('map'), {
            zoom: 15,
            center: myLatLng
        });
        var marker = new google.maps.Marker({
            position: myLatLng,
            map: map
        });
        var geocoder = new google.maps.Geocoder();

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

下記は、緯度・経度を指定して、地図を表示させています。マーカーも合わせて表示しています。

```js:
        var myLatLng = {lat: 35.658581, lng: 139.745433};
        var map = new google.maps.Map(document.getElementById('map'), {
            zoom: 15,
            center: myLatLng
        });
        var marker = new google.maps.Marker({
            position: myLatLng,
            map: map
        });
```

次のコードがgecodeに関わる部分です。
まず、geocodeを使えるように **`new google.maps.Geocoder()`** します。

```js
        var geocoder = new google.maps.Geocoder();
```

次に、**`id="submit"`** をクリックしたときに発生するイベントを設定します。
先ほど生成した **`map`** と **`geocoder`** を **`geocodeAddress()`**　に渡してあげます。　 
 
```js
        document.getElementById('submit').addEventListener('click', function() {
            geocodeAddress(geocoder, map);
        });
```

これでイベントの設定が完了しました。
次に、 **`geocodeAddress()`** の処理を見ていきましょう。

##Geocodeの処理

**`geocodeAddress()`**では、実際にGeocodeを使用して、緯度・経度の取得をしています。

```js
    function geocodeAddress(geocoder, resultsMap) 
    {
        var address = document.getElementById('address').value;
        geocoder.geocode({'address': address}, function(results, status) {
            if (status === google.maps.GeocoderStatus.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);
            }
        });
    }
```

まずは上から。
**`id="address"`** の値を **`address`**にセットします

```js
        var address = document.getElementById('address').value;
```

geocodeのオプションに、先ほど取得した値を**`geocoder.geocode({'address': address}`** にセットします。
それで、第二引数に関数が設定されているのですが、**`function(results, status)`** の **`results`** に緯度・経度などの情報、**`status`** に緯度・経度取得に成功したかどうかの判定結果が入ってきます。
それを元に判定ロジックを書き、成功したら現在表示しているマップ・マーカーの位置を変更、失敗したらアラートを表示、という流れになります。

```js
        geocoder.geocode({'address': address}, function(results, status) {
            if (status === google.maps.GeocoderStatus.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);
            }
        });
```

ここまでは簡単だと思います。
上記のコードであれば、何ら問題はなかったのです。

しかし、私がハマったのはこの次。

#Geocodingの結果が**`undefined`**になる
Geocodingの結果を元に処理を書こうと思ったら、Geocodingの結果が**`undefined`**になる現象に陥ったわけです。

私がやろうとしたことは次の通り。

- Geocoding処理を行う
- Geocodingの結果をreturnさせる
- それを元に別の関数で処理させる

例えば、下記の様なコードを書いたとします。

```js
    initMap() 
    {
        // マップ表示処理部分は先程と同じ

        // submitをクリックしたら、Geocoding処理が走り、次の処理へ移行
        document.getElementById('submit').addEventListener('click', function() {
            var geoCodeResults = geocodeAddress();
            testResults(geoCodeResults);
            alert("処理終了");
        });
    }
    function geocodeAddress()
    {
        var geocoder = new google.maps.Geocoder();
        var address = document.getElementById('address').value;
        geocoder.geocode({'address': address}, function(results, status) {
            if (status === google.maps.GeocoderStatus.OK) {
                return {"lat":results[0].geometry.location.lat(), "lng":results[0].geometry.location.lng()};
            } else {
                alert('Geocode was not successful for the following reason: ' + status);
            }
        });
    }
    function testResults(geoCodeResults)
    {
        // ジオコーディング結果を用いた処理
        console.log(geoCodeResults);
    }
    initMap();
```

**`initMap()`**の部分に関しては先程とほぼ同じです。

あとは、**`submit`**をクリックしたらGeocodeの処理結果を**`return`**して、**`testResults()`**に処理結果を渡して実行、**`alert`**を表示して処理終了という流れです。

ここではひとまず、処理結果を**`console.log(geoCodeResults);`**で見ることにしています。

期待した挙動は、**`geoCodeResults`**にGeocodeによって取得した緯度・経度が入っていること。
しかし、残念ながら**`undefined`**が返って来ていたのです。

##Geocodeは非同期処理である
そうなのです。
Geocodeは非同期処理だったことに気づきました。
そのため、緯度・経度の値が返ってくる前に次の処理が走り、処理結果が**`undefined`**になっていたのです。

なので、コールバックにして処理をさせてあげないといけないわけです。
こんな感じに↓


```js
    initMap() 
    {
        // マップ表示処理部分は先程と同じ

        // submitをクリックしたら、Geocoding処理が走り、次の処理へ移行
        document.getElementById('submit').addEventListener('click', function() {
            geocodeAddress(testResults);
            alert("処理終了");
        });
    }

    function geocodeAddress(callback)
    {
        var geocoder = new google.maps.Geocoder();
        var address = document.getElementById('address').value;
        geocoder.geocode({'address': address}, function(results, status) {
            if (status === google.maps.GeocoderStatus.OK) {
                callback({"lat":results[0].geometry.location.lat(), "lng":results[0].geometry.location.lng()});
            } else {
                alert('Geocode was not successful for the following reason: ' + status);
            }
        });
    }
    function testResults(geoCodeResults)
    {
        // ジオコーディング結果を用いた処理
        console.log(geoCodeResults);
    }
    initMap();
```

これで、期待した挙動になりました。
コールバックにすれば、勝手に次の処理に行かずに処理結果が**`undefined`**という問題児にならなくて済むわけですね。

##$.ajax編

下記の記述でもいいみたいです。
しかし、Chrome、firefoxでは問題なかったのですが、IEのバージョンによっては期待した挙動にはならなかったのであまり推奨はしません。
書き方が悪いのかな？

```js
    initMap() 
    {
        // マップ表示処理部分は先程と同じ

        // submitをクリックしたら、Geocoding処理が走り、次の処理へ移行
        document.getElementById('submit').addEventListener('click', function () {
            geocodeAddress();
            alert("処理終了");
        });
    }

    function geocodeAddress()
    {
        var address = document.getElementById('address').value;
        $.ajax({
            url: 'http://maps.googleapis.com/maps/api/geocode/json',
            data: {
                address: address
            }
        }).done(function(geoCodeResults, status){
            testResults(geoCodeResults);
        }).fail(function(data){
            alert('error');
        });

    }
    function testResults(geoCodeResults) {
        // ジオコーディング結果を用いた処理
        console.log(geoCodeResults);
    }
    initMap();
```

上記で処理を流した場合、取得した緯度・経度がある場所は
 **`geoCodeResults.results[0].geometry.location`** になります。
他にも色々と返却されている値があるので、見てみると楽しいかもしれません。

#逆ジオコーディング
今までやってきたものはジオコーディングの処理でしたが、逆ジオコーディングも同じ要領でやれば問題ありません。

**`{'address': address}`**の部分を**`{'location': location}`**にすればOKです。
ただ、locationの値は **`{lat: latの値(数値), lng: lngの値(数値)}`** でなければ上手く動作しないのでお気をつけ下さい。

例として書いておきますね。

```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>
        html, body {
            height: 100%;
            margin: 0;
            padding: 0;
        }

        #map {
            height: 500px;
            width: 50%;
        }

        #floating-panel {
            position: absolute;
            top: 10px;
            left: 30%;
            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="latlng" type="textbox" value="37.09024, -95.712891">
    <input id="submit" type="button" value="Reverse Geocode">
</div>
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js"></script>
<script>
    function initMap()
    {
        var myLatLng = {lat: 35.658581, lng: 139.745433};
        var map = new google.maps.Map(document.getElementById('map'), {
            zoom: 15,
            center: myLatLng
        });
        var marker = new google.maps.Marker({
            position: myLatLng,
            map: map
        });

        document.getElementById('submit').addEventListener('click', function () {
            geocodeLatLng(testResults);
            alert("処理終了");
        });
    }

    function geocodeLatLng(callback) {
        var geocoder = new google.maps.Geocoder();
        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 === google.maps.GeocoderStatus.OK) {
                if (results[1]) {
                    callback(results);
                } else {
                    alert('No results found');
                }
            } else {
                alert('Geocoder failed due to: ' + status);
            }
        });
    }

    function testResults(geoCodeResults)
    {
        // ジオコーディング結果を用いた処理
        console.log(geoCodeResults);
    }
    initMap();
</script>
</body>
</html>

```

![スクリーンショット 2016-01-11 13.41.36.png](https://qiita-image-store.s3.amazonaws.com/0/50208/6c4141b9-3ce9-06e5-0b8f-2cca0f640d19.png)


返却値は結構数があるので、欲しい値を上手く取得してください。
ー　追記　ー
[Google Maps API のReverse Geocodingで都道府県名を取得](http://qiita.com/Arashi/items/c8d0d679559bcb768bde)にて、都道府県名だけを取得するJSを書きました。



#終わりに
さらーと流しましたが、やはり書いてみないとわからないことだらけですね。。。
これに行き着くまで結構な時間を要しました(´・ω・`)
実際、大したことはしていないのですが、期待した挙動にならないと「なんでやねん！！！」となりますからね。