PythonとWebAPIによる店舗出店状況など地理情報の分析の自動化 Automation of a research on geographical information such as store network

# 何か / What
店舗の出店状況などの分析をPythonとWeb API (Google geocode, 地理院）を用いて自動化しました。
自動化した箇所は以下の3点です。
①店舗の住所の取得
②住所から経度、緯度の取得
③経度、緯度から距離の取得

これにより、例えば以下のことができます。
例１）自店舗の冗長性の調査。ある店舗から見て、半径3km以内にある店舗を列挙。
例２）競合店舗との距離の調査。ある自店舗から見て、半径3km以内にある他店舗の列挙。

より簡潔には、こちらのレポジトリをご確認ください。
https://github.com/taiga518/automation-geo

Automated a research on geographical information such as store network using python and Web API.
Automation includes the following three parts.
1. Collecting address information
2. Converting address information into latitude and longitude
3. Converting longitude and latitude information into distance

You can also check my git repository:
https://github.com/taiga518/automation-geo

# 何が問題だったか / What was the issue
愚直な店舗網の分析では上記の①～③を手作業で行う必要がありました。
①ホームページから一店舗ずつ住所をコピペ。この際に間違いや見落としが起こりえます。
②③緯度経度自体が欲しいことはあまりないと思いますが、距離を知りたい場合は、手作業でgooglemapsなどに住所を入力して地点間の距離を測る必要がありました。これは時間がかかります。例えば100店舗の2店舗間の距離をすべて出すとなると5000通り以上(!)を調べないといけないので、現実的ではありません。

The most simple way to tackle with these task might be doing them manually.
However, it would take unrealistically long time if number of stores you would like to check is high (eg. to check distance of all the pair of two stores from in total 100 stores, you need to check distance for more than 5000 times)


# 何をしたか / What I did

上記の通り、3つのプログラムで問題を解決しました。
As I mentioned earlier, I solved this issue with three programs.

①店舗の住所の取得 / Collecting address information

このプログラムはホームページの店舗リストからWebスクレイピングにより、住所を自動で取得するプログラムの簡潔な例です。

This program is a simple example of web scraping to get address information of shops automatically.

- Github : https://github.com/taiga518/automation-geo/blob/master/get_address(example).py


<details><summary>コード(Click to open)</summary><div>

``` get_address(example).py
"""" 
Just an example of simple web scraping.
Webスクレイピングによる店舗住所取得の例です。
"""

import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np


"""
Rondom company with many shops is set as an example.
店舗の多い会社の例として、ほけんの窓口を挙げます。他の会社でも基本的には同じです。
Homepage here : https://www.hokennomadoguchi.com/shop-list/
"""

urlName = "https://www.hokennomadoguchi.com/shop-list/"
url = requests.get(urlName)
soup = BeautifulSoup(url.content, "html.parser")


shop_name_list = []
address_list = []
# select finds class and id. selectでクラスやidを探します。
for elem in soup.select(".shop-link"):
    # find finds first tag appears. findでは最初に表れる対応するタグを探します。
    # get finds attributes. getは属性（アトリビュート）を取得します。
    shop_url = "https://www.hokennomadoguchi.com" + elem.find("a").get("href")
    # contents breakdowns tags. contentsはタグをブレークダウンして、タグの部分やアトリビュートが選択できるようにします。
    shop_name = elem.find("a").select(".shop_name")[0].contents[0]
    print("--- "+ shop_name +" ---")
    url = requests.get(shop_url)
    soup_shop = BeautifulSoup(url.content, "html.parser")
    address = soup_shop.select(".item2")[0].contents[2].replace(" ","").replace("\u3000","").replace("\r","").replace("\n","")
    print(address)

    shop_name_list.append(shop_name)
    address_list.append(address)
    
    
df_madoguchi = pd.DataFrame([shop_name_list,address_list]).T
df_madoguchi.columns=["shop_name", "address"]

df_madoguchi.to_csv("madoguchi_address.csv", encoding='utf_8_sig')
```

</div></details>



②住所から経度、緯度の取得 / Converting address information into latitude and longitude
このプログラムはgoogle geocode APIを用いて、住所（文字列）から緯度経度を自動で取得するものです。
This program is to get longitude and latitude from address(string) using google geocode API.

- Github : https://github.com/taiga518/automation-geo/blob/master/get_lnglat_geocode.py

<details><summary>コード(Click to open)</summary><div>


```get_lnglat_geocode.py 
import json
import pandas as pd
import requests
import time

API_key = "XXXXX"
"""    
APIキーはご自身で取得する必要があるのでご注意ください。
個人的な使用頻度では料金を請求されないと思いますが、料金表はよく確認してください。
API key needs to be set to use google geocoding API. Follow the guidance here :
https://developers.google.com/maps/documentation/geocoding/overview
"""


def start_end_decolator(input_function):
    """Decolator to print start and end"""
    def return_function(*args, **kwargs):
        print("\n--------------start--------------")
        result = input_function(*args, **kwargs)
        print("\n---------------end---------------")
        return result
    return return_function

def progress_decolator(input_function):
    """Decolator to print * to show progress"""
    def return_function(*args, **kwargs):
        print("*", end="")
        result = input_function(*args, **kwargs)
        return result
    return return_function

@progress_decolator
def get_location(address):
    """ 
    Googleのgeocodingを使って、住所から経度緯度を取得します。
    
    Get latitude and longitude using google geocoding API.
    API key needs to be set to use google geocodin API. Follow the guidance here : 
    https://developers.google.com/maps/documentation/geocoding/overview
    Check billing here: https://console.cloud.google.com/google/maps-apis/overview
    
    Input : address as text
        eg) "東京都港区芝公園４丁目２−8"
        
    Output : tupple of address(text), latitude(float), longitude(float)
        eg) ('4-chōme-2-8 Shibakōen, Minato City, Tōkyō-to 105-0011, Japan', 35.6585769, 139.7454506)
    """
    url = "https://maps.googleapis.com/maps/api/geocode/json?address=+"+address+"&key="+API_key
    result = requests.get(url)
    result_json = json.loads(result.text)
    formatted_address = result_json["results"][0]["formatted_address"]
    lat, lng = result_json["results"][0]["geometry"]["location"].values()
    return (formatted_address, lat, lng)


@start_end_decolator    
def add_location_info(input_df):
    """
    複数の住所のリストから、上記のget_location関数を使って経度緯度のリストを取得します。
    
    Get lists of location information using get_location function.
    
    Input : dataframe with address information named address
    Output : dataframe with formatted_address, latitute, longitude columns
    """    
    
    formatted_address_list = []
    lat_list = []
    lng_list = []

    for i_row in range(len(input_df)):
        formatted_address, lat, lng = get_location(input_df.loc[i_row,"address"])
        formatted_address_list.append(formatted_address)
        lat_list.append(lat)
        lng_list.append(lng)
    
    output_df = input_df
    output_df["formatted_address"] = formatted_address_list
    output_df["latitude"] = lat_list
    output_df["longitude"] = lng_list
    return output_df



### main here

df = pd.read_csv("PATH.csv")
df = df[["name","address"]]
df_loc = add_location_info(df)
df_loc.to_csv("output.csv", encoding='utf_8_sig')


```
</div></details>




③経度、緯度から距離の取得 / Converting longitude and latitude information into distance
このプログラムは緯度経度で表された2地点の距離を自動で計算するものです。
This program is to calculate distance between two points described with longitude and latitude.

- Github : https://github.com/taiga518/automation-geo/blob/master/get_distance.py

<details><summary>コード(Click to open)</summary><div>


```get_distance.py 
import json
import pandas as pd
import requests
import time


def progress_decolator(input_function):
    """Decolator to print * to show progress"""
    def return_function(*args, **kwargs):
        print("*", end="")
        result = input_function(*args, **kwargs)
        return result
    return return_function


def get_distance_API(lat1, lng1, lat2, lng2):
    """ Get distance between two points described with latitute and longitude.
    Details of the API can be found here: https://vldb.gsi.go.jp/sokuchi/surveycalc/api_help.html
    Validate the result using this web app : https://vldb.gsi.go.jp/sokuchi/surveycalc/surveycalc/bl2stf.html
    Input : latitute and longitude of two points (float)
        eg) 35.6585769, 139.7454506, 35.710256, 139.8107946
    Output : distance of input two points with kilo meter unit (float)
        eg) 8.237
    """
    url = "http://vldb.gsi.go.jp/sokuchi/surveycalc/surveycalc/bl2st_calc.pl?latitude1={}&longitude1={}&latitude2={}&longitude2={}&ellipsoid=bessel&outputType=json".format(lat1,lng1,lat2,lng2)
    i_count = 0
    while i_count <= 10:
        result = requests.get(url)
        status_code = result.status_code
        if status_code == 200:
            break
        i_count += 1    
        time.sleep(2)
        print("retry : {}".format(i_count+1),end="")
        
    result_json = json.loads(result.text)
    distance = "0" + result_json["OutputData"]["geoLength"] 
    if distance == "0":
        print("error here")
        print(url)
        print(result)
        print(result_json)
    return round(float(distance)/1000, 3)

def get_distance_locally(lat_a, lon_a,lat_b, lon_b):
    """
    Credit : https://qiita.com/damyarou/items/9cb633e844c78307134a
    """
    ra=6378.140  # equatorial radius (km)
    rb=6356.755  # polar radius (km)
    F=(ra-rb)/ra # flattening of the earth
    rad_lat_a=np.radians(lat_a)
    rad_lon_a=np.radians(lon_a)
    rad_lat_b=np.radians(lat_b)
    rad_lon_b=np.radians(lon_b)
    pa=np.arctan(rb/ra*np.tan(rad_lat_a))
    pb=np.arctan(rb/ra*np.tan(rad_lat_b))
    xx=np.arccos(np.sin(pa)*np.sin(pb)+np.cos(pa)*np.cos(pb)*np.cos(rad_lon_a-rad_lon_b))
    c1=(np.sin(xx)-xx)*(np.sin(pa)+np.sin(pb))**2/np.cos(xx/2)**2
    c2=(np.sin(xx)+xx)*(np.sin(pa)-np.sin(pb))**2/np.sin(xx/2)**2
    dr=F/8*(c1-c2)
    rho=ra*(xx+dr)
    return rho

@progress_decolator
def get_distance(lat1, lng1, lat2, lng2, method=0):
    if method == 0:
        return_distance = get_distance_API(lat1, lng1, lat2, lng2)
    else: 
        return_distance = get_distance_locally(lat1, lng1, lat2, lng2)
    return return_distance 


def create_matrix(n_row, n_col):
    """Create matrix filled with nan in decided size
    
    Input : n_row(int), n_col(int)
    Output : dataframe
    """
    
    matrix = pd.DataFrame(index=range(n_row), columns=range(n_col))
    return matrix



# main here

df1 = pd.read_csv("PATH1.csv")
df2 = pd.read_csv("PATH2.csv")

matrix = create_matrix(len(df1), len(df2))

for i in range(len(df1)):
    for j in range(len(df2)):
        distance = get_distance(df1.loc[i,"latitude"], 
                                df1.loc[i,"longitude"], 
                                df2.loc[j, "latitude"], 
                                df2.loc[j, "longitude"],
                               method = 0)
        if distance == 0:
            # if distance equal 0, that is most probably wrong. check what is the problem.
            # distanceが0の場合、問題が発生していることが多いです。そのため確認を行ってください。
            print(df1[i])
            print(df2[j])
        matrix.iloc[i,j] = distance
        
matrix.to_csv("output.csv", encoding='utf_8_sig')

# if you want to decolate output with headings, run the followings
# 以下はヘッダーの追加です。任意で実行してください。

col_expanded = pd.concat([df1[["name","address"]],matrix], axis = "columns")
df_head = pd.DataFrame([[""]*2,[""]*2],columns=["name","address"])
df_head = pd.concat([df_head , df2[["name","address"]]], ignore_index=True).T.reset_index(drop=True)
df_head.columns = col_expanded.columns
df_head.index = ["name", "address"]
df_expanded = pd.concat([df_head, col_expanded])
df_expanded.to_csv("output_with_header.csv", encoding='utf_8_sig')


```
</div></details>

# 終わりに / Closing
転職情報あれば教えてください。
Hire me.
https://www.linkedin.com/in/taigakubota/