【Python】GeoPyを使って住所一覧からまとめて緯度経度を出す

# 初めに
住所から緯度経度を計算してくれるライブラリーはいくつもあります。

今回の処理を考えた時、第1候補としてGoogle Maps Platform APIがありました。
このAPIを使えば正確な位置情報が得られますが、無料枠が月に28500リクエストであり、それ以上は有料となってしまいます。

有料はちょっと、、、

GeoPyは○○丁目までの住所にしか対応していないですが、**無料**で使用できる！
なので、多少のずれは目を瞑ります😴

GeoPyを使用して緯度経度を出していこうと思います！！

## 作るもの
出力するファイルの名前とExcelファイルのパスを入れたらExcelファイルを返してくれる関数を作成していきます。

イメージ
```mermaid
%%{init:{'theme':'base'}}%%
graph TB
    Name[出力するファイルの名前] & Path[Excelファイルまでのパス]-->function[今回作成する関数]
    function-->file[緯度経度の列が追加されているExcelファイル]
```
## 環境
GoogleColaboratoryで実行
- Python(3.8.10)
## ライブラリー
- re
- tqdm
- pandas
- geopy
- openpyxl （xlsxファイルを作成する時に使用）
↑GoogleColaboratoryには初めから入っていないライブラリーなのでpipでインストールする必要があります。
```:GoogleColaboratoryの場合
!pip install openpyxl
```
# GeoPyとは
いくつかあるジオコーディング用のPythonライブラリーです。
世界中の住所、都市、国、ランドマークの座標を簡単に見つけ出すことができる便利ツール！！
## インストール
インストールはpipで簡単に行うことができます。
```
pip install geopy
```
```:GoogleColaboratoryの場合
!pip install geopy
```
## 基本的な使い方
```python
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent="test") #名前はなんでもOK
location = geolocator.geocode("(む)") # 検索

print(location.address)       #住所
print(location.latitude)      #緯度
print(location.longitude)     #経度
```
```:出力結果
Mauritius / Maurice
-20.2759451
57.5703566
```
２行目の`user_agent`は設定しないといけないですが名前は何を入れても大丈夫です。

検索するには住所（丁目まで）を入れるか、ランドマーク（東京都庁など）を入れることで`location`の中に結果が格納されます。

面白いことに(む)や(め)を入れてあげると不思議なことに国名が出てきます。
（む）と調べるとモーリシャスという東アフリカの国、（め）と調べるとメキシコが出てきます。

# 処理の流れ
大雑把な関数内の処理の流れを書いておきます。
1. ExcelをDataFrame形式で読み込む

2. 読み込んだDataFrameから住所の列を取り出し、1行ごとに住所から緯度経度へ変換する

3. 緯度経度を元のDataFrameに結合しExcelファイルとして書き出す

## 問題点
GeoPyを使用する際に１つ処理しなければならない問題があります。

住所を緯度経度へ変換する際に、住所に入っている数字は漢数字にしておかないと検索できませんでした。
また、21丁目は二一丁目ではなく二十一丁目としなければいけません。

なので、単純に数字を漢字に置き換えるだけではなく、2桁の場合は「十」を数字の間に追加する処理が必要になります。

例）1 → 一、2 → 二、11 → 十一、 23 → 二十三
## 日本の住所は何丁目まである？
余談ですが、日本で最も大きい丁目は**北海道の帯広市の西19条南42丁目**だそうです。
気になる方は下の記事を読んでみてください！！

https://ontheroad.toyotires.jp/tidbits/6557/#:~:text=%E7%8F%BE%E5%9C%A8%E3%80%81%E5%9B%BD%E5%86%85%E3%81%A7%E6%9C%80%E3%82%82%E5%A4%A7%E3%81%8D%E3%81%84,%E3%82%82%E5%AD%98%E5%9C%A8%E3%81%99%E3%82%8B%E3%82%88%E3%81%86%E3%81%A7%E3%81%99%E3%80%82

# コード全体
```python
import re
import tqdm
import pandas as pd

# 緯度経度を出してくれるライブラリー
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent="test")

# 数字を漢数字へ変換するための変換テーブル
table = str.maketrans({
        '1' : '一',
        '2' : '二',
        '3' : '三',
        '4' : '四',
        '5' : '五',
        '6' : '六',
        '7' : '七',
        '8' : '八',
        '9' : '九',
        '１' : '一',
        '２' : '二',
        '３' : '三',
        '４' : '四',
        '５' : '五',
        '６' : '六',
        '７' : '七',
        '８' : '八',
        '９' : '九',
    })

def address_to_lonlat(name, path):

    lon = [] #　経度
    lat = [] # 緯度

    # エクセルをデータフレームで読み込む
    df = pd.read_excel(path, index_col=0)
    address_list = df['address']

    # 二桁の数字の場合数字の間に'十'を入れる
    str_list = [re.sub(r'([0-9０-９])([0-9０-９])', r'\1十\2', i) if re.compile('[0-9０-９][0-9０-９]').search(i) else i for i in address_list]

    # tableを作成して全ての数字を漢数字へ変換
    result = [s.translate(table).replace('*区','') for s in str_list]
    # result = [s.translate(table) for s in str_list]

    # ループを使用して住所が日本の場合lon,latへ追加
    for ad in tqdm.tqdm(result):
        try:
            location = geolocator.geocode(ad)
            if '日本' in location.address:
                lon.append(location.longitude)
                lat.append(location.latitude)
            else:
                lon.append(' ')
                lat.append(' ')
        except:
            lon.append(' ')
            lat.append(' ')

    # 緯度経度を元のdfへ結合
    linking_df = pd.DataFrame({'経度':lat, '緯度':lon})
    excel_df = pd.concat([df, linking_df], axis=1)

    pd.io.formats.excel.ExcelFormatter.header_style = None
    excel_df.to_excel(f'{name}(緯度経度変換済み).xlsx', sheet_name='test')
```
# 解説
関数の中身を切り取って説明していきます！！
データの読み込み方法などはデータに合わせて変更してください。
## ライブラリのインポート
```python
import re
import tqdm
import pandas as pd

# 緯度経度を出してくれるライブラリー
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent="test")
```
必要なライブラリーのインポートとGeoPyの設定を行っておきます。
## 住所の数字を漢数字へ変換
```python:関数外
table = str.maketrans({
        '1' : '一',
        '2' : '二',
        '3' : '三',
        '4' : '四',
        '5' : '五',
        '6' : '六',
        '7' : '七',
        '8' : '八',
        '9' : '九',
        '１' : '一',
        '２' : '二',
        '３' : '三',
        '４' : '四',
        '５' : '五',
        '６' : '六',
        '７' : '七',
        '８' : '八',
        '９' : '九',
    })
```
```python:関数内
lon = [] #　経度
lat = [] # 緯度

# エクセルをデータフレームで読み込む
df = pd.read_excel(path, index_col=0)
address_list = df['address']

# 二桁の数字の場合数字の間に'十'を入れる
str_list = [re.sub(r'([0-9０-９])([0-9０-９])', r'\1十\2', i) if re.compile('[0-9０-９][0-9０-９]').search(i) else i for i in address_list]

# tableを作成して全ての数字を漢数字へ変換
result = [s.translate(table).replace('*区','') for s in str_list]
# result = [s.translate(table) for s in str_list]
```
関数外で`table`という変換するための辞書を作成。
関数内で`lon`,`lat`のリストを用意。

初めに、`df`へDataFrame形式でファイルを読み込み、住所の列のみを`test_address`へ格納。

次に、`test_address`をfor文で回し、結果を`str_list`へ格納する。
正規表現を使用し,もし二桁の数字だったら数字の間に「十」を入れる処理を行っている。

最後に一番下の行で、str_listの中身を数字から漢数字へ変換、ついでに「*区」といういらないものが住所についていたのでreplaceで取り除いている。
特に必要がなかったら１つ下コメントアウトを外しそちらを使ってください。

for文はなるべく内包表記を使用しています。
[Pythonのリスト内包表記の速度](https://qiita.com/intermezzo-fr/items/43f90e07e4cebe63aeb6)という記事によるとfor文より内包表記の方が早いらしい。
## 住所から緯度経度へ変換
```python:関数内
# ループを使用して住所が日本の場合lon,latへ追加
for ad in tqdm.tqdm(result):
    try:
        location = geolocator.geocode(ad)
        if '日本' in location.address:
            lon.append(location.longitude)
            lat.append(location.latitude)
        else:
            lon.append(' ')
            lat.append(' ')
    except:
        lon.append(' ')
        lat.append(' ')
```
数字を漢数字へ変換し終わったリスト(`result`)をfor文で回す。

GeoPyは検索に何も引っ掛からなかった場合は何も返してくれないので、try,exceptを使用して検索に成功し、日本の住所だった場合`lon`,`lat`へ追加、それ以外は空白を追加する。

## Excelファイルへ書き込む
```python:関数内
# 緯度経度を元のdfへ結合
linking_df = pd.DataFrame({'経度':lat, '緯度':lon})
excel_df = pd.concat([df, linking_df], axis=1)

pd.io.formats.excel.ExcelFormatter.header_style = None
excel_df.to_excel(f'{name}(緯度経度変換済み).xlsx', sheet_name='test')
```
出力するファイルの中身は、入力されたファイルの中身に緯度経度の列を追加したものになる。

なので、`lat`と`lon`のDataFrameを作成し、初めに読み込んだDataFrameと行方向に結合させる。

結合させたものをto_excelを使用してExcelファイルとして出力させる。

pd.io.formats.excel.ExcelFormatter.header_style = Noneを設定しないと出力されたExcelファイル内の初めの列と行に枠線がついてしまう。
つけたい場合はコメントアウトしておいてください。
# 終わりに
作成したプログラムを動かしてみると、1分で約100件の変換を行ってくれました。

初学者なので情報に誤りがあるかもしれません。
ご容赦ください🙇

より高速に動かせる方法などがありましたらコメントで教えもらえるとありがたいです:slight_smile:

最後まで読んでいただきありがとうございました。

# 参考サイト一覧

https://geopy.readthedocs.io/en/stable/

https://qiita.com/daifuku10/items/0cd4a409417d3a7b7297

https://note.nkmk.me/python-str-replace-translate-re-sub/

https://python-work.com/excel-to-excel/#pandasExcel