無償ジオコーディングの比較

# はじめに

仕事でジオコーディングをすることが多々あるため、プロバイダー毎のジオコーディングを整理したいと思い、本記事を執筆します。
ジオコーディングとは、住所から緯度経度情報に変換する技術のことです。逆に、緯度経度情報から住所に変換することは、リバースジオコーディングや逆ジオコーディングといいます。
私がよくやるのは、ジオコーディングの方で、住所情報をQGIS等のGISソフト上に載せたいときなどに、使います。
順序としては下記の通りです。
1. 当該情報（住所を含んだ不動産情報等）をインターネット上か取得します（スクレイピング）（データ形式はCSV）
2. 取得した情報（住所）に対して、ジオコーディングを行い、緯度経度情報に変換します（データ形式はCSV）
3. QGISの「CSVテキストレイヤの追加」を行い、QGIS上に表示します。

しかし、ジオコーディングは色々なプロバイダーが提供しているので、どのプロバイダーを使えばいいのかよくわかりません。
そこで、本記事ではプロバイダー別に、ジオコーディングを比較していきたいと思います。

#比較するジオコーディングのプロバイダ
ジオコーディングの関数は色々ありますが、今回は[本サイト](https://geocoder.readthedocs.io/)で紹介されているモジュールを使います。Pythonで使用可能です。

本モジュールで使用可能なプロバイダを下記にまとめました。

| #       | プロバイダ名       |       APIkeyが必要か不要か |    備考    |
|:-----------------:|:-----------------|:------------------|:------------------|
|1| ArcGIS             |              不要 |                |
|2| Baidu           |            必要|       住所情報ではなく、IPアドレスを元にジオコーディングする。リクエスト数は100万回/日       |
|3| Bing             |              必要 |        |
|4| CanadaPost               |                必要|       |
|5| FreeGeoIP.net             |             不要|       リクエスト数は1万回/1時間|
|6| Gaode          |           必要|     リクエスト数は2000回/日|
|7| GeocodeFarm          |           不要|      リクエスト数は250回/日|
|8| Geocoder.ca          |           不要|     カナダと米国のジオコーダー|
|9| GeoNames          |           必要|       |
|10| GeoOttawa          |           不要|     |
|11| Google|           必要|   |
|12| HERE|           必要| |
|13| IP Info.io|           不要 |住所情報ではなく、IPアドレスを元にジオコーディングする|
|14| LocationIQ|           必要 |OpenStreetMapと完全互換性|
|15| Mapbox|           不要|      |
|16| MapQuest|           必要|   |
|17| MaxMind|           不要|   住所情報ではなく、IPアドレスを元にジオコーディングする|
|18| Opencage|           必要|       |
|19| OpenStreetMap|           不要 |       |
|20| Tamu|           必要 |      |
|21| TomTom|           必要|     |
|22| What3Words|          不要  | 緯度経度情報ではなく、3mX3m四方のグローバルグリッドを指す一意の3ワードを返す|
|23| Yahoo|          不要  |      YOLPとは違うサービスらしい。Yahoo Place Finderというサービス |
|24| Yandex|          不要 |  ロシア最大のインターネット企業が提供するサービス|
|25| TGOS|         必要   |   台湾の公式地図サービス|

APIkeyが必要なものはいちいち登録するのも面倒なため、不要のサービスを比較してみます。
また、IPアドレスからのジオコーディングは使わないため、こちらも除外します。
それと、What3Wordsも除外します。

不要のサービスは以下の通りです。
また、YahooとYandexは使えなくなっていたので、除外します。

| #       | プロバイダ名       |       APIkeyが必要か不要か |    備考    |
|:-----------------:|:-----------------|:------------------|:------------------|
|1| ArcGIS             |              不要 |                |
|2| GeocodeFarm          |           不要|      リクエスト数は250回/日|
|3| Geocoder.ca          |           不要|     カナダと米国のジオコーダー|
|4| GeoOttawa          |           不要|     |
|5| Mapbox|           不要|      |
|6| OpenStreetMap|           不要 |       |


9のサービスを対象にどの程度の制度でジオコーディングができるのか実施します。

#住所の細かさ
住所を細かくしたとき、どの程度の細かさまで対応できるのか試してみたいと思います。
対象とする住所は、下記の6つにします
1. 日本
2. 東京都
3. 東京都足立区
4. 東京都足立区北千住
5. 東京都足立区北千住3
6. 東京都足立区北千住3-92

#プログラム
TypeErrorとかそのあたりは例外処理でとりあえず飛ばしています。
多分、サービス停止シていたりするものも有るかと思いますが、そのあたりは後で調査するとしてまずは緯度経度を返してくれるかどうかだけをみます。


```python:main.py
import geocoder
import pandas as pd
import numpy as np

value = ["日本","東京都","東京都足立区","東京都足立区北千住","東京都足立区北千住","東京都足立区北千住3","東京都足立区北千住3-92"]
provider = ["ArcGIS","GeocodeFarm","Geocoder.ca","GeoOttawa","Mapbox","OpenStreetMap"]

cols = ['住所情報','プロバイダ','緯度','経度']
df = pd.DataFrame(index=[],columns=cols)

for i in range(0,len(value)):
    print("■" + value[i] + "の場合")
    
    for j , name in enumerate(provider):
        print("-" + name + "の場合")
        
        #ArcGISの場合
        if(j == 0):
            try:
                latlon = geocoder.arcgis(value[i])
                latlon_lat = latlon.json["lat"]
                latlon_lng = latlon.json["lng"]

                print(latlon)
                print(latlon.json["lng"])
                print(latlon.json["lat"])
            
        
            except ValueError:
                print("ValueErrorが発生")
                latlon_lat = "ValueError"
                latlon_lng = "ValueError"
 
            except TypeError:
                print("TypeErrorが発生")
                latlon_lat = "TypeError"
                latlon_lng = "TypeError"
                
            except KeyError:
                print("KeyError")
                latlon_lat = "KeyError"
                latlon_lng = "KeyError"
                
            
            record = pd.Series([value[i],
                                   name,
                                   latlon_lat,
                                   latlon_lng],
                                   index=df.columns)
            df = df.append(record,ignore_index=True)
          

        #GeocodeFram場合
        if(j == 1):
            try:
                latlon = geocoder.geocodefarm(value[i])
                latlon_lat = latlon.json["lat"]
                latlon_lng = latlon.json["lng"]

                print(latlon)
                print(latlon.json["lng"])
                print(latlon.json["lat"])
            
            except ValueError:
                print("ValueErrorが発生")
                latlon_lat = "ValueError"
                latlon_lng = "ValueError"
 
            except TypeError:
                print("TypeErrorが発生")
                latlon_lat = "TypeError"
                latlon_lng = "TypeError"
                
            except KeyError:
                print("KeyError")
                latlon_lat = "KeyError"
                latlon_lng = "KeyError"
            
            record = pd.Series([value[i],
                                   name,
                                   latlon_lat,
                                   latlon_lng],
                                   index=df.columns)
            df = df.append(record,ignore_index=True)
      
        #Geocoder.ca場合
        if(j == 2):
            try:
                latlon = geocoder.geolytica(value[i])
                #print(latlon.json)
                latlon_lat = latlon.json["lat"]
                latlon_lng = latlon.json["lng"]

                print(latlon)
                print(latlon.json["lng"])
                print(latlon.json["lat"])
            
            except ValueError:
                print("ValueErrorが発生")
                latlon_lat = "ValueError"
                latlon_lng = "ValueError"
 
            except TypeError:
                print("TypeErrorが発生")
                latlon_lat = "TypeError"
                latlon_lng = "TypeError"
                
            except KeyError:
                print("KeyError")
                latlon_lat = "KeyError"
                latlon_lng = "KeyError"
            
            record = pd.Series([value[i],
                                   name,
                                   latlon_lat,
                                   latlon_lng],
                                   index=df.columns)
            df = df.append(record,ignore_index=True)


            
            
        #GeoOttawa場合
        if(j == 3):
            try:
                latlon = geocoder.ottawa(value[i])
                #print(latlon.json)
                latlon_lat = latlon.json["lat"]
                latlon_lng = latlon.json["lng"]

                print(latlon)
                print(latlon.json["lng"])
                print(latlon.json["lat"])
            
            except ValueError:
                print("ValueErrorが発生")
                latlon_lat = "ValueError"
                latlon_lng = "ValueError"
 
            except TypeError:
                print("TypeErrorが発生")
                latlon_lat = "TypeError"
                latlon_lng = "TypeError"
                
            except KeyError:
                print("KeyError")
                latlon_lat = "KeyError"
                latlon_lng = "KeyError"
            
            record = pd.Series([value[i],
                                   name,
                                   latlon_lat,
                                   latlon_lng],
                                   index=df.columns)
            df = df.append(record,ignore_index=True)

            
            
        
        #GMapbox場合
        if(j == 4):
            try:
                latlon = geocoder.mapbox(value[i])
                #print(latlon.json)
                latlon_lat = latlon.json["lat"]
                latlon_lng = latlon.json["lng"]

                print(latlon)
                print(latlon.json["lng"])
                print(latlon.json["lat"])
            
            except ValueError:
                print("ValueErrorが発生")
                latlon_lat = "ValueError"
                latlon_lng = "ValueError"
 
            except TypeError:
                print("TypeErrorが発生")
                latlon_lat = "TypeError"
                latlon_lng = "TypeError"
                
            except KeyError:
                print("KeyError")
                latlon_lat = "KeyError"
                latlon_lng = "KeyError"
            
            record = pd.Series([value[i],
                                   name,
                                   latlon_lat,
                                   latlon_lng],
                                   index=df.columns)
            df = df.append(record,ignore_index=True)

            
            
            
        #OpenStreetMaps場合
        if(j == 5):
            try:
                latlon = geocoder.osm(value[i])
                #print(latlon.json)
                latlon_lat = latlon.json["lat"]
                latlon_lng = latlon.json["lng"]

                print(latlon)
                print(latlon.json["lng"])
                print(latlon.json["lat"])
            
            except ValueError:
                print("ValueErrorが発生")
                latlon_lat = "ValueError"
                latlon_lng = "ValueError"
 
            except TypeError:
                print("TypeErrorが発生")
                latlon_lat = "TypeError"
                latlon_lng = "TypeError"
                
            except KeyError:
                print("KeyError")
                latlon_lat = "KeyError"
                latlon_lng = "KeyError"
            
            record = pd.Series([value[i],
                                   name,
                                   latlon_lat,
                                   latlon_lng],
                                   index=df.columns)
            df = df.append(record,ignore_index=True)
print("finish!")
```

#出力結果
出力結果は以下の通りです。

|    | 住所情報               | プロバイダ    | 緯度               | 経度               |
|----|------------------------|---------------|--------------------|--------------------|
| 0  | 日本                   | ArcGIS        | 36.655226998000046 | 139.27149500000007 |
| 1  | 日本                   | GeocodeFarm   | TypeError          | TypeError          |
| 2  | 日本                   | Geocoder.ca   | KeyError           | KeyError           |
| 3  | 日本                   | GeoOttawa     | TypeError          | TypeError          |
| 4  | 日本                   | Mapbox        | ValueError         | ValueError         |
| 5  | 日本                   | OpenStreetMap | 36.5748441         | 139.2394179        |
| 6  | 東京都                 | ArcGIS        | 35.68945633200008  | 139.69171608500005 |
| 7  | 東京都                 | GeocodeFarm   | TypeError          | TypeError          |
| 8  | 東京都                 | Geocoder.ca   | KeyError           | KeyError           |
| 9  | 東京都                 | GeoOttawa     | TypeError          | TypeError          |
| 10 | 東京都                 | Mapbox        | ValueError         | ValueError         |
| 11 | 東京都                 | OpenStreetMap | 35.6828387         | 139.7594549        |
| 12 | 東京都足立区           | ArcGIS        | 35.77481105600003  | 139.80453694100004 |
| 13 | 東京都足立区           | GeocodeFarm   | TypeError          | TypeError          |
| 14 | 東京都足立区           | Geocoder.ca   | KeyError           | KeyError           |
| 15 | 東京都足立区           | GeoOttawa     | TypeError          | TypeError          |
| 16 | 東京都足立区           | Mapbox        | ValueError         | ValueError         |
| 17 | 東京都足立区           | OpenStreetMap | 35.783703          | 139.795319         |
| 18 | 東京都足立区北千住     | ArcGIS        | 35.77481105600003  | 139.80453694100004 |
| 19 | 東京都足立区北千住     | GeocodeFarm   | TypeError          | TypeError          |
| 20 | 東京都足立区北千住     | Geocoder.ca   | KeyError           | KeyError           |
| 21 | 東京都足立区北千住     | GeoOttawa     | TypeError          | TypeError          |
| 22 | 東京都足立区北千住     | Mapbox        | ValueError         | ValueError         |
| 23 | 東京都足立区北千住     | OpenStreetMap | 35.7496486         | 139.8046045        |
| 24 | 東京都足立区北千住     | ArcGIS        | 35.77481105600003  | 139.80453694100004 |
| 25 | 東京都足立区北千住     | GeocodeFarm   | TypeError          | TypeError          |
| 26 | 東京都足立区北千住     | Geocoder.ca   | KeyError           | KeyError           |
| 27 | 東京都足立区北千住     | GeoOttawa     | TypeError          | TypeError          |
| 28 | 東京都足立区北千住     | Mapbox        | ValueError         | ValueError         |
| 29 | 東京都足立区北千住     | OpenStreetMap | 35.7496486         | 139.8046045        |
| 30 | 東京都足立区北千住3    | ArcGIS        | 35.77481105600003  | 139.80453694100004 |
| 31 | 東京都足立区北千住3    | GeocodeFarm   | TypeError          | TypeError          |
| 32 | 東京都足立区北千住3    | Geocoder.ca   | KeyError           | KeyError           |
| 33 | 東京都足立区北千住3    | GeoOttawa     | TypeError          | TypeError          |
| 34 | 東京都足立区北千住3    | Mapbox        | ValueError         | ValueError         |
| 35 | 東京都足立区北千住3    | OpenStreetMap | TypeError          | TypeError          |
| 36 | 東京都足立区北千住3-92 | ArcGIS        | 35.77481105600003  | 139.80453694100004 |
| 37 | 東京都足立区北千住3-92 | GeocodeFarm   | 48.9505233764802   | 2.29213690757375   |
| 38 | 東京都足立区北千住3-92 | Geocoder.ca   | KeyError           | KeyError           |
| 39 | 東京都足立区北千住3-92 | GeoOttawa     | TypeError          | TypeError          |
| 40 | 東京都足立区北千住3-92 | Mapbox        | ValueError         | ValueError         |
| 41 | 東京都足立区北千住3-92 | OpenStreetMap | TypeError          | TypeError          |

ほぼほぼエラーですね。。。
整理したのは下記です。

|    | 住所情報               | プロバイダ    | 緯度        | 経度        |
|----|------------------------|---------------|-------------|-------------|
| 0  | 日本                   | ArcGIS        | 36.655227   | 139.271495  |
| 5  | 日本                   | OpenStreetMap | 36.5748441  | 139.2394179 |
| 6  | 東京都                 | ArcGIS        | 35.68945633 | 139.6917161 |
| 11 | 東京都                 | OpenStreetMap | 35.6828387  | 139.7594549 |
| 12 | 東京都足立区           | ArcGIS        | 35.77481106 | 139.8045369 |
| 17 | 東京都足立区           | OpenStreetMap | 35.783703   | 139.795319  |
| 18 | 東京都足立区北千住     | ArcGIS        | 35.77481106 | 139.8045369 |
| 23 | 東京都足立区北千住     | OpenStreetMap | 35.7496486  | 139.8046045 |
| 24 | 東京都足立区北千住     | ArcGIS        | 35.77481106 | 139.8045369 |
| 29 | 東京都足立区北千住     | OpenStreetMap | 35.7496486  | 139.8046045 |
| 30 | 東京都足立区北千住3    | ArcGIS        | 35.77481106 | 139.8045369 |
| 36 | 東京都足立区北千住3-92 | ArcGIS        | 35.77481106 | 139.8045369 |

ArcGISとOpenStreetMapのみ、変換に使うことができました。
これらをQGISで表示させてみます。

#QGISでの表示

※赤丸はOpenStreetMap、青丸はArcGISです

住所情報が日本の箇所に関しては、下記のようになりました。(赤枠)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284911/c8891d5e-b674-e3c9-2a0f-4da64ea5f146.png)
群馬県になりました。だいたい日本の中心でしょうか。

日本以外に関しては、下記のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284911/7507d3e0-e169-89ad-7e33-422f15dd4a90.png)

東京都に関しては、OpenStreetMapは東京駅を指しています。ArcGISは都庁あたりを指しています。
東京の県庁所在地は新宿区なので、ArcGISもある意味正しいのではないでしょうか。

東京都足立区は下記のようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284911/c61f5580-6de7-c9c8-f6f8-63f4d0d79a0b.png)

OpenStreetMapは、特に何もランドマークはなかったのですが、ArcGISは足立区役所を指していました。


# おわりに
色々なジオコーディングサービスがありますが、とりあえずArcGISか、OpenStreetMapを使えば問題なさそうです。ただし、ポインティングする場所が微妙に異なるため、目視確認等もして判断していけばいいかなと思いました。