kimagre inrash

感想を書きます

住所から地図上にプロットしよう(python+geopandas)

time 2021/09/27

ふととあるゲームの設置店舗を地図上にプロットしたくなりました。
地図描画には geopandas が良さそうです。

geopandas を使うまで

ここが一番厄介。
pip install だとすんなりいかないので conda install からやる必要があります。

Anaconda をインストール。
https://www.anaconda.com/products/individual

Anaconda Prompt を実行。

$ conda install geopandas

でインストールできるという触れ込みですが・・

$ Collecting package metadata (current_repodata.json): done
$ Solving environment: failed with initial frozen solve. Retrying with flexible solve.

こうなってインストールが完了しない。
専用環境を用意して切り替えてインストールすればいける。

$ conda create -n geo_env
$ conda activate geo_env
$ conda config --env --add channels conda-forge
$ conda config --env --set channel_priority strict
$ conda install python=3 geopandas

この状態で geosample.py が実行できるハズ。(専用環境は geo_env)

$ python geosample.py

VisualStudioCodeで起動したい場合はちょっとだけ厄介。
まずAnaconda Navigaterを起動。

Applications on を geo_env にする。

次に Visual Studio Code の Launch で起動。

左下の「Python 3.9.5 64-bit」をクリックして
「Python 3.9.7 64-bit (‘geo_env’:conda )」を選択。(バージョンは人によって異なる)

これで使用するpythonを切り替えることができ、実行すれば起動できる。

地図上にマーカーをプロット

設置店舗一覧から住所を取得、その住所を元に経度・緯度を求める。
pyファイルを分けているのは「住所から経度緯度を求めるサイトのAPI制限」があるため、一度ファイルに書き出すようにしている。

$ python wonderOsaka.py
# -*- coding: utf-8 -*-
import re
import time
import traceback

import chromedriver_binary
import requests
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.chrome.options import Options


def coordinate(address):
    # 検索の頻度を、10秒に1回程度に抑えてください。
    time.sleep(10)

    payload = {'q': address}
    html = requests.get('http://www.geocoding.jp/api/', params=payload)
    soup = BeautifulSoup(html.content, "html.parser")
    if soup.find('error'):
        raise ValueError(f"Invalid address submitted. {address}")
    latitude = soup.find('lat').string
    longitude = soup.find('lng').string
    return [latitude, longitude]

if __name__ == "__main__":

    options = Options()
    # ヘッドレスモードで実行する場合
    options.add_argument("--headless")
    driver = webdriver.Chrome(options=options)

    # 取得先URLにアクセス
    url = 'https://wonder.sega.jp/store/#!/area:5,pref:4'
    driver.get(url)
    
    # コンテンツが描画されるまで待機
    time.sleep(5)

    # ファイル
    f = open('wonderOsaka.txt','w', encoding='UTF-8') 

    # 店舗情報取得
    trs = driver.find_elements_by_class_name("Store")
    index = 1
    length = len(trs)
    for tr in trs:
        print(str(index) + "/" + str(length))

        storeName = tr.find_elements_by_css_selector(".TextLink.Store-name")
        storeAddr = tr.find_elements_by_css_selector(".Store-addr")

        if len(storeName) > 0 and len(storeAddr) > 0:
            latlons = coordinate(storeAddr[0].text)
            lat = latlons[0]
            lon = latlons[1]

            f.write(storeName[0].text + "\t" + storeAddr[0].text+ "\t" + lat + "\t" + lon + "\n")

        index = index + 1

    f.close()

    # プラウザを閉じる
    driver.quit()

ファイルを読み込み、プロットして完成。
japan.geojson は https://github.com/dataofjapan/land から取得。

$ python geocoding.py
# -*- coding: utf-8 -*-
import csv

import geopandas as gpd
import matplotlib.pyplot as plt

if __name__ == "__main__":

    # リストを読み込む
    # csvファイル
    csv_file = open("wonderOsaka.txt", "r", encoding="UTF-8", errors="", newline="" )
    # リスト形式
    fcsv = csv.reader(csv_file, delimiter="\t", doublequote=True, lineterminator="\r\n", quotechar='"', skipinitialspace=True)
    list_of_rows = list(fcsv)

    # 地図データ読み込み
    df = gpd.read_file('japan.geojson')

    with plt.style.context("ggplot"):
        # 表示範囲を大阪府のみにする
        df[df['nam_ja'] == '大阪府'].plot(figsize=(8,8), edgecolor='#444', facecolor='white', linewidth = 0.5)

        index = 1
        length = len(list_of_rows)
        for row in list_of_rows:
            print(str(index) + "/" + str(length) )
            index = index + 1

            # 地図にプロット
            #lon = 135.499465
            #lat = 34.726033
            lon = float(row[3])
            lat = float(row[2])
            plt.scatter(lon, lat, marker='o', s=20, color="green", alpha=0.8, linewidths = 2)

        # 保存
        plt.savefig("map-300dpi.png", format="png", dpi=300, bbox_inches='tight', pad_inches=0)

        # 表示
        plt.show()


マップが保存され、表示される。

前後記事

Spotifyのマイライブラリをエクスポートしよう(Python)
とあるデータを取り出してPDF化(Python+Selenium)