PythonでGoogle Maps APIを利用して位置情報と航空写真を取得する

# はじめに
本記事では、GoogleMapsAPIを利用し、指定した場所の緯度経度の情報や、その場所の航空写真を取得する方法について紹介します。

# 目次
- 環境
- 事前準備
- モジュールとパラメータの設定
- データの作成
- 位置情報の取得
- 航空写真の取得
- 参考情報
- おまけ

# 環境
本記事における実装環境は以下となっています。

- Windows 10 Home
- Python 3.6.8
- Anaconda conda-4.6.2

# 事前準備
以下のサイトより、GoogleのAPIキーを取得しておきます。
Google Maps Platform：https://developers.google.com/maps/web/
詳しい取得方法については参考情報の記事をご参照下さい。

それから、以下のコマンドをコマンドプロンプトに打ち込んで、GoogleMapsAPIのモジュールをインストールしておきましょう。

```
pip install pygeocoder
pip install googlemaps
```

# モジュールとパラメータの設定
上記の準備が終わったら，モジュールのインポートをします。
今回使用するモジュールは以下の通りです。

```Python:get_location.py
# モジュールのインポート
import pandas as pd
import urllib
import urllib.error
import urllib.request

# Google API モジュール
from pygeocoder import Geocoder
import googlemaps
```

また、ここでパラメータの設定もしておきます。
本記事では、パラメータとして以下の4つを設定しています。

- APIキー
- 取得した緯度経度の情報や航空写真を出力するアウトプットフォルダ
- 取得する航空写真の画素数
- 取得する航空写真の縮尺度合い

```Python:get_location.py
# パラメータの設定
googleapikey = '取得したGoogleのAPIキー'
output_path = '出力先のフォルダのパス'
pixel = '640x480'
scale = '18'
```

# データの作成
ここで緯度経度や航空写真を取得したい場所や地名のリストを作成します。
実際には、場所や地名のデータセットがあると思いますので、その場合はデータをインポートし、リスト型に変換しておいて下さい。
本記事では、以下の5つをリストに追加しています。

- 国会議事堂
- วัดพระแก้ว（タイの寺院であるワットプラケオ）
- New York City
- Государственный Эрмитаж（ロシアにあるエルミタージュ美術館）
- مكة المكرمة（メッカの正式名称であるマッカ・アル＝ムカッラマ）

本記事では、色々な言語に対応していることを確認するために、日本語や英語の他に、タイ語、ロシア語、アラビア語で書かれた場所や地名も入れています。
※場所や地名のチョイスは筆者の趣向が大いに反映されています。

```Python:get_location.py
# リストの初期化
location = []

# リストに場所や地名を追加する
location = ["国会議事堂", "วัดพระแก้ว", "New York City", "Государственный Эрмитаж", "مكة المكرمة"]

# リストの表示
location
```

以下のようにリストが表示されていれば問題ありません。

```
['国会議事堂',
 'วัดพระแก้ว',
 'New York City',
 'Государственный Эрмитаж',
 'مكة المكرمة']
```

# 位置情報の取得
それでは早速位置情報を取得していきましょう。
手始めに以下のコードでどのような情報が取得できるか見てみましょう。

```Python:get_location.py
# geocodeで取得できる情報の一覧の例（国会議事堂の場合）
gmaps = googlemaps.Client(key=googleapikey)
address = u'国会議事堂'
result = gmaps.geocode(address)
result
```

すると、以下のようにJSONで結果が返ってくると思います。

```:JSON
[{'address_components': [{'long_name': '1',
    'short_name': '1',
    'types': ['premise']},
   {'long_name': '7',
    'short_name': '7',
    'types': ['political', 'sublocality', 'sublocality_level_4']},
   {'long_name': '1 Chome',
    'short_name': '1 Chome',
    'types': ['political', 'sublocality', 'sublocality_level_3']},
   {'long_name': 'Nagatacho',
    'short_name': 'Nagatacho',
    'types': ['political', 'sublocality', 'sublocality_level_2']},
   {'long_name': 'Chiyoda',
    'short_name': 'Chiyoda',
    'types': ['locality', 'political']},
   {'long_name': 'Tokyo',
    'short_name': 'Tokyo',
    'types': ['administrative_area_level_1', 'political']},
   {'long_name': 'Japan',
    'short_name': 'JP',
    'types': ['country', 'political']},
   {'long_name': '100-0014',
    'short_name': '100-0014',
    'types': ['postal_code']}],
  'formatted_address': '1 Chome-7-1 Nagatacho, Chiyoda, Tokyo 100-0014, Japan',
  'geometry': {'location': {'lat': 35.675888, 'lng': 139.744858},
   'location_type': 'ROOFTOP',
   'viewport': {'northeast': {'lat': 35.6772369802915,
     'lng': 139.7462069802915},
    'southwest': {'lat': 35.6745390197085, 'lng': 139.7435090197085}}},
  'place_id': 'ChIJibDhsomLGGARkIZSXvrUx0g',
  'plus_code': {'compound_code': 'MPGV+9W Tokyo, Japan',
   'global_code': '8Q7XMPGV+9W'},
  'types': ['establishment', 'point_of_interest']}]
```

この中から以下の箇所より緯度、経度の情報を取得していきます。
` 'geometry': {'location': {'lat': 35.675888, 'lng': 139.744858}`
緯度、経度の情報の抽出は、以下のコードで行うことが可能です。

```Python:get_location.py
# 上記の取得情報一覧より緯度・経度の情報のみを抽出
lat = result[0]["geometry"]["location"]["lat"]
lon = result[0]["geometry"]["location"]["lng"]
print (lat,lon)
```

以下のような結果が得られます。

```
35.675888 139.744858
```

上記で実施してきたことを踏まえて、以下のような関数を作成し、各場所や地名に対し、それに応じた緯度、経度の情報を取得していきます。

```Python:get_location.py
def geocode_address(loc):
    # リストの初期化
    loc_dict = []
    
    # locationを変数にセット
    rows = loc
    
    # geocodeにより緯度・経度の情報をループ処理で取得
    for row in rows[0:]:
        gmaps = googlemaps.Client(key=googleapikey)
        geocode_result = gmaps.geocode(row)

        # loc
        loc = row
        # lat
        lat = geocode_result[0]["geometry"]["location"]["lat"]
        # lng
        lng = geocode_result[0]["geometry"]["location"]["lng"]
        
        # results
        loc_dict.append({'loc': loc, 'lat': lat, 'lng': lng})
    
    # リスト型のloc_dictをデータフレームに変換
    df = pd.DataFrame(data=loc_dict)
    
    # 重複の削除
    df = df.drop_duplicates('loc')
    
    # データの出力
    df.to_csv(output_path + '\\loc.csv')
```

上記の関数を使用して得られた結果は、パラメータで設定したアウトプットフォルダに、CSV形式でloc.csvというファイル名で出力されます。

```Python:get_location.py
# locationの緯度と経度の情報を取得する
geocode_address(location)
```

loc.csvを開き、以下のように結果を保存できていることが確認できれば、問題ありません。
※ファイルを開くと、英語以外の文字については、文字化けしている場合がありますが、再度Pythonに取り込んでみると、文字化けは解消されるので、心配しないで下さい。

|   |lat      |lng       |loc|
|:-:|--------:|---------:|:--------|
|0  |35.675888|139.744858|国会議事堂|
|1  |13.751644|100.492704|วัดพระแก้ว|
|2  |40.712775|-74.005973|New York City|
|3  |59.939832|30.314560 |Государственный Эрмитаж|
|4  |21.389082|39.857912 |مكة المكرمة|

# 航空写真の取得
上記で各場所や地名に対し、緯度と経度の情報を取得できました。
次は、その情報を基に、各場所や地名の航空写真を取得していきます。
まず、上記で作成したloc.csvをインポートします。

```Python:get_location.py
# 上記で出力したloc.csvをインポート
loc = pd.read_csv(output_path + '\\loc.csv', index_col = 'Unnamed: 0')
```

以下のような関数を作成し、各場所や地名の航空写真を、緯度、経度の情報を基に取得していきます。

```Python:get_location.py
# defによる関数オブジェクトの作成
# 画像をダウンロードする
def download_image(loc):
    
    # データフレームから行列に変換
    lats = loc['lat'].values.tolist()
    lngs = loc['lng'].values.tolist()
    locs = loc['loc'].values.tolist()
    
    # htmlの設定
    html1 = 'https://maps.googleapis.com/maps/api/staticmap?center='
    
    # maptypeで取得する地図の種類を設定
    html2 = '&maptype=hybrid'

    # sizeでピクセル数を設定
    html3 = '&size='
    
    # sensorはGPSの情報を使用する場合にtrueとするので今回はfalseで設定
    html4 = '&sensor=false'

    # zoomで地図の縮尺を設定
    html5 = '&zoom='
    
    # マーカーの位置の設定（マーカーを表示させてくなければ無でも大丈夫）
    html6 = '&markers='
    
    # key="googleから取得したキーコード"となるように設定
    html7 = '&key='
    
    # 緯度経度の情報に該当する航空写真をループ処理で取得
    for lat, lng, loc in zip(lats, lngs, locs):
        
        # 緯度経度の情報をセット
        axis = str(lat) + "," + str(lng)
    
        # url
        url = html1 + axis + html2 + html3 + pixel + html4 + html5 + scale + html6 + axis + html7 + googleapikey
        
        # pngファイルのパスを作成
        dst_path = output_path + '\\' + str(loc) + ".png"
        
        # 画像を取得しローカルに書き込み
        try:
            data = urllib.request.urlopen(url).read()
            with open(dst_path, mode="wb") as f:
                f.write(data)
                
        except urllib.error.URLError as e:
            print(e)
```

上記の関数内で設定しているmaptypeには以下の4種類があり、必要に応じて使い分けると良いかと思います。

- roadmap: 道路や建物などが表示されるGoogle Mapにおけるデフォルトのロードマップで、maptypeを指定しない場合はこのmaptypeで出力される
- satellite: Google Earthで公開されている衛星画像
- hybrid: roadmapとsateliteを複合した地図
- terrain: roadmapから建物などの情報を除いた地形情報のみの地図

本記事ではmaptypeをhybridに設定しています。

markersは地図上に表示させる赤いマーカーの設定なので、表示する必要が無ければ、関数内のurlの設定のところで、以下の箇所を削除すれば、マーカーは表示されなくなります。
`html6 + axis +`

上記の関数を使用すると、各場所や地名の名前の航空写真のファイルが、パラメータで設定したアウトプットフォルダ内に保存されていきます。

```Python:get_location.py
# 緯度経度の情報より画像を取得する
download_image(loc)
```

以下のような国会議事堂の写真を取得できました。
![国会議事堂.png](https://qiita-image-store.s3.amazonaws.com/0/107043/d526763d-f5e0-6054-c5ea-02e05bdb7710.png)

因みに、パラメータで設定したScaleの値を小さくすることで、より広範囲の航空写真を取得することもできます。
例えば、Scaleを15に設定して、国会議事堂の航空写真を取得すると、以下のような写真を取得できます。
![国会議事堂.png](https://qiita-image-store.s3.amazonaws.com/0/107043/9171d685-76c1-59e6-f1c9-c0ee57ea3390.png)

# おまけ
おまけとして、筆者が以前これらの航空写真をどのように使っていたのかについて、ご紹介します。
私は購買傾向の予測のための特徴量の1つとして、これらの位置情報や航空写真を使っていました。
例えば、北海道と沖縄では、購買する商品の傾向にも差があるでしょうし、田舎かどうか、もしくは海や湖といった水辺が近いか否かによっても、購買傾向に差が出てくるはずです。
それらを考慮するために、緯度、経度の情報や、航空写真から色の平均値を計算したものを、特徴量に加えて、モデルの構築をしていました。

因みに、各場所や地名の航空写真の色の平均値は、以下の関数で取得することが可能です。
※パラメータとして用いているoutput_pathは、最初に設定したものと同じです。
※予め上記で作成したloc.csvファイルは別の場所に移しておくと良いです。

```Python:get_location.py
# モジュールのインポート
import numpy as np
import cv2

def get_rgb_avg(image):
    # リストの初期化
    rgb_dict = []
    
    # ループで画像のファイルパスより色の平均値を算出
    for i in imgs[0:]:
        # ファイルパスの設定
        path = output_path +'\\' + i
        # ファイルを開く
        img = cv2.imread(path)
        
        # バイナリ型に変換
        with open(path, 'rb') as f:
            binary = f.read()
        arr = np.asarray(bytearray(binary), dtype=np.uint8)
        img = cv2.imdecode(arr, -1) 
        
        # RGBによる色の平均値の算出
        rgb_avg = np.mean(img, axis=(0,1))
        
        # loc
        loc = i.replace('.png', '')
        # red
        red = rgb_avg[0]
        # green
        green = rgb_avg[1]
        # blue
        blue = rgb_avg[2]
        
        # results
        rgb_dict.append({'loc': loc, 'red_avg': red, 'green_avg': green, 'blue_avg': blue})
    
     # リスト型のloc_dictをデータフレームに変換
    df = pd.DataFrame(data=rgb_dict)
    # 重複の削除
    df = df.drop_duplicates('loc')
    # データの出力
    df.to_csv(output_path + '\\rgb.csv')
```

以下でフォルダ内の画像のファイル名を取得した上で、関数を利用して、色の平均値を計算し、結果をrgb.csvに出力します。

```Python:get_location.py
# 画像のファイル名を取得
imgs = os.listdir(output_path)

# 画像の色の平均値の取得および結果の出力
get_rgb_avg(imgs)
```

上手くいけば、以下のような結果が得られているはずです。

|   |blue_avg  |green_avg |red_avg   |loc|
|:-:|---------:|---------:|---------:|:--------|
|0  |75.537354 |73.117559 |70.947559 |国会議事堂|
|1  |110.995820|112.009831|113.706563|วัดพระแก้ว|
|2  |82.438949 |87.910039 |96.007301 |New York City|
|3  |112.611553|123.225700|123.313779|Государственный Эрмитаж|
|4  |82.890130 |78.179072 |75.678112 |مكة المكرمة|

# 参考情報
APIキー取得については、色々記事が出ているので、そのうちの1つをご紹介致します。
・Google Maps API を使ってみた: https://qiita.com/Haruka-Ogawa/items/997401a2edcd20e61037

Google Maps APIは使用量によっては有料となる場合があります。
以下のサイトでは使用する際に必要な設定などを紹介してくれているので、こちらも目を通しておいた方が良いでしょう。
・Google Maps APIが新しくなる！Google Maps Platformの料金体系と必要な設定変更: https://www.marie-web.design/blog/google-maps-platform/

# 最後に
今回が初めての投稿とあって、色々と苦労しましたが、大変勉強になりました。
会社で上記のことができるよって教えた時に、「折角ならQiitaにでも投稿すればいいじゃん」と焚きつけてくれた友人には感謝しています(笑)
これからも時間がある時に、こういった記事を細々書いていきたいなと思います。