GIS奮闘記

元GISエンジニアの技術紹介ブログ。主にPythonを使用。

スポンサーリンク

Python で緯度経度を地域メッシュコードに変換する方法

明けましておめでとうございます。

昨年は一昨年に続き変化の多い年でした。新しい分野に挑戦することになり四苦八苦していますが、頑張ろうと思います。

さて、本日は Python で緯度経度を地域メッシュコードに変換してみようと思います。以下のエントリーで地域メッシュコードを緯度経度に変換する方法を紹介していますが、今回についてその逆を紹介します。

www.gis-py.com

地域メッシュとは

前回エントリーに記載しましたので、こちらを参照してください。

www.gis-py.com

やり方

地域メッシュの考え方が以下のサイトで公開されています。これを参考にしてコードを作ってみようと思います。

https://www.stat.go.jp/data/mesh/pdf/gaiyo1.pdf

以下にサイトの画面ショットをのせておきます。

f:id:sanvarie:20210107211517p:plain

f:id:sanvarie:20210107211554p:plain

使用する緯度経度

こちらで取得したJR蒲田駅の緯度経度を使用したいと思います。なぜ蒲田かは秘密です。

緯度:35.562479
経度:139.716073

f:id:sanvarie:20210107211845p:plain

蒲田駅の地域メッシュコードをESRIジャパンさんが公開しているデータで確認してみます。

1次メッシュ:5339
2次メッシュ:533925
3次メッシュ:53392577

ということがわかりました。

f:id:sanvarie:20210107212512p:plain

実行環境

Windows 10
Python 3.6.6

サンプルコード

1~3次メッシュまで対応させたサンプルコードを作成しました。

# -*- coding: utf-8 -*-
def latlon2mesh(lat, lon):
    #1次メッシュ上2けた
    quotient_lat, remainder_lat = divmod(lat * 60, 40)
    first2digits = str(quotient_lat)[0:2]

    #1次メッシュ下2けた
    last2digits = str(lon - 100)[0:2]
    remainder_lon = lon - int(last2digits) - 100

    #1次メッシュ
    first_mesh = first2digits + last2digits

    #2次メッシュ上1けた
    first1digits, remainder_lat = divmod(remainder_lat, 5)

    #2次メッシュ下1けた
    last1digits, remainder_lon = divmod(remainder_lon * 60, 7.5)

    #2次メッシュ
    second_mesh = first_mesh + str(first1digits)[0:1] + str(last1digits)[0:1]

    #3次メッシュ上1けた
    first1digits, remainder_lat = divmod(remainder_lat * 60, 30)

    #3次メッシュ下1けた
    last1digits, remainder_lon = divmod(remainder_lon * 60, 45)

    #3次メッシュ
    third_mesh = second_mesh + str(first1digits)[0:1] + str(last1digits)[0:1]

    print("1次メッシュ:" + first_mesh)
    print("2次メッシュ:" + second_mesh)
    print("3次メッシュ:" + third_mesh)

if __name__ == '__main__':
    latlon2mesh(35.7007777, 139.71475)

結果を確認すると想定通りの地域メッシュコードを取得することができました。

1次メッシュ:5339
2次メッシュ:533925
3次メッシュ:53392577

f:id:sanvarie:20210107213306p:plain

さいごに

「緯度経度を地域メッシュコードに変換」と聞くとなんとなく難解そうな気がしますが、コードにするととても短くシンプルなプロセスということがわかるかと思います。地域メッシュコードは GIS の世界ではよく使用するものなので、今まで聞いたことなかったという方はぜひ色々調べてみてください。本年もよろしくお願いします。

Python で交通事故統計データを可視化してみよう!

さて、本日は交通事故統計データの可視化をしてみようと思います。本エントリーですが、以下二つのエントリーで行った作業を前提として進めていきます。興味がありましたらぜひ以下エントリーも読んでみてください。

www.gis-py.com

www.gis-py.com

交通事故統計データとは

警察庁が公開しているオープンデータです。その名の通り交通事故の統計データですね。

www.npa.go.jp

交通事故統計データ概要

2019年の交通事故統計データが公開されています。こちらに記載されていますが、以下三つのデータが CSV 形式で存在します。

  • 本票

    • 交通事故1件につき1レコード
    • 記録事項は、交通事故の内容に関する事項及び交通事故に関与した者(当事者A、当事者B)に関する事項
  • 補充票

    • 本票以外の交通事故に関与した者1人について1レコード
    • 記録事項は、本票に記録されない交通事故に関与した者のうち、死亡若しくは負傷した者又は車両等の運転者で死傷がなく死亡事故に関与した者に関する事項
  • 高速票

    • 高速自動車国道及び道路交通法第110条第1項により国家公安委員会が指定する自動車専用道路における交通事故1件について1レコード
    • 記録事項は、交通事故の発生地点、道路構造等に関する事項

要するに本票がメインのレコードで補充票、高速票がそれに紐づくレコードといった感じでしょうか。今回は「本票」「高速票」のデータを使用して可視化を行いたいと思います。補充票に関しては可視化はしませんが、一応データの格納だけはしようと思います。

手順

  1. 本票、補充票、高速票を SQLite に格納する
  2. 本票のデータを加工
  3. 加工したデータからシェープファイルに変換するデータを取得
  4. PyShp を使ってポイントのシェープファイルを作成
  5. 作成したシェープファイルを ArcGIS Online で可視化する

PyShp と ArcGIS Online に関しては以下のエントリーで紹介しているので、興味のある方がいましたらぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

実行環境

Windows10 64bit
Python3.6.6
ArcGIS Online

手順1(本票、補充票、高速票を SQLite に格納) サンプルコード

まずは データを SQLite に格納します。前回エントリーと同じ要領ですね。

# -*- coding: utf-8 -*-
import sqlite3
from os.path import join, split
from glob import glob
import pandas as pd

# 本票、補充票、高速票格納先
csv_path = r"D:\blog\data\csv"

# SQLite 格納先
db_path = r"D:\blog\data\sqlite\test.db"

def insert_csv():
    # CSV ファイルのディレクトリ取得
    csv_files = glob(join(csv_path, "*.csv"))

    for csv in csv_files:

        table_name = split(csv)[1] # テーブル名作成

        table_name = table_name[0:-4] # テーブル名から拡張子を削除

        df = pd.read_csv(csv, dtype=object, encoding="SHIFT-JIS") # CSV 読込

        with sqlite3.connect(db_path) as conn:
            df.to_sql(table_name, con=conn) # SQLiteにCSVをインポート

if __name__ == '__main__':
    insert_csv()

前回エントリーで作成したDBに「hojuhyo_2019」「honhyo_2019」「kosokuhyo_2019」というテーブルが追加されました。

f:id:sanvarie:20201230143715p:plain

f:id:sanvarie:20201230144550p:plain

手順2(手順「1」で格納した本票のデータを加工) サンプルコード

オープンデータにありがちですが、公開されているデータがあまりきれいな状態ではなく、少しデータの整形をしたいと思います。ここのパートはややこしく、説明がわかりづらいかもしれませんがご容赦ください。以下3つのステップを踏んでデータ加工を行います。

  1. 「honhyo_2019」テーブルの「死者数」、「負傷者数」カラムに入っている不要な0を削除
  2. 一意になるキーを作成
  3. 「honhyo_2019」テーブルの「路線コード」カラムを名称に変換
1.「honhyo_2019」テーブルの「死者数」、「負傷者数」カラムに入っている不要な0を削除

負傷者が一人だったら、001のような値が入っているのですが、このような形でデータが格納されているのは一体なぜなのでしょうか。ちょっとよくわからないですね。。。

 update honhyo_2019
    set 死者数 = case
                    when substr(死者数,1,2) = '00' then
                        substr(死者数,3,1)
                    when substr(死者数,1,1) = '0' and substr(死者数,2,1) <> '0' then
                        substr(死者数,2,2)
                end,
        負傷者数 = case
                      when substr(負傷者数,1,2) = '00' then
                          substr(負傷者数,3,1)
                      when substr(負傷者数,1,1) = '0' and substr(負傷者数,2,1) <> '0' then
                          substr(負傷者数,2,2)
                  end

加工前

f:id:sanvarie:20201230153528p:plain

加工後

f:id:sanvarie:20201230153647p:plain

2.一意になるキーを作成

本票番号というカラムがキーになると思ったのですが、色々調べてると違うことがわかりました。。。「honhyo_2019」「kosokuhyo_2019」テーブルともに「都道府県コード」、「警察署等コード」、「本票番号」カラムを結合させてキーを作りたいと思います。

事故番号」というカラムを作成します。

alter table honhyo_2019 add column 事故番号[text];
alter table kosokuhyo_2019 add column 事故番号[text];

以下で当該カラムに「都道府県コード」、「警察署等コード」、「本票番号」カラムの結合した文字列を格納します。

update honhyo_2019 
   set 事故番号 = 都道府県コード || 警察署等コード || 本票番号

update kosokuhyo_2019
   set 事故番号 = 都道府県コード || 警察署等コード || 本票番号

これをキーとして使用したいと思います。

3.「honhyo_2019」テーブルの「路線コード」カラムを名称に変換

最初は「本票_路線コード」テーブルと結合して路線名を取得しようと思ったのですが、ちょっと困ったことに当該テーブルのレコードが以下のようになっていて結合ができないので、カラム追加をしてそこに路線名を格納しようと思います。

f:id:sanvarie:20201230172219p:plain

まずは「路線名」カラム追加します。

alter table honhyo_2019 add column 路線名[text];

次に追加したカラムに路線名を格納します。

update honhyo_2019
   set 路線名 = case 
                  when substr(路線コード,1,4) between '0001' and '0999' then '一般国道(国道番号)'
                  when substr(路線コード,1,4) between '1000' and '1499' then '主要地方道-都道府県道'
                  when substr(路線コード,1,4) between '1500' and '1999' then '主要地方道-市道'
                  when substr(路線コード,1,4) between '2000' and '2999' then '一般都道府県道'
                  when substr(路線コード,1,4) between '3000' and '3999' then '一般市町村道'
                  when substr(路線コード,1,4) between '4000' and '4999' then '高速自動車国道'
                  when substr(路線コード,1,4) between '5000' and '5499' then '自動車専用道-指定'
                  when substr(路線コード,1,4) between '5500' and '5999' then '自動車専用道-その他'
                  when substr(路線コード,1,4) between '6000' and '6999' then '道路運送法上の道路'
                  when substr(路線コード,1,4) between '7000' and '7999' then '農(免)道'
                  when substr(路線コード,1,4) between '8000' and '8499' then '林道'
                  when substr(路線コード,1,4) between '8500' and '8999' then '港湾道'
                  when substr(路線コード,1,4) between '9000' and '9499' then '私道'
                  when substr(路線コード,1,4) = '9500' then 'その他'
                  when substr(路線コード,1,4) = '9900' then '一般の交通の用に供するその他の道路'
             end

これでデータの加工は終了です。

手順3(手順「2」で加工したデータをもとにシェープファイルに変換するデータを取得) サンプルコード

「honhyo_2019」「kosokuhyo_2019」テーブルから必要なカラムを選定し、コードを名称に変換します。

以下のSQLで取得するデータをシェープファイルに変換します(「手順4」参照)。カラム数がかなり多いので、必要最低限のカラムのみを取得しています。

select a.事故番号   as 事故番号,
       c.都道府県名 as 都道府県,
       e.区分       as 事故内容,
       a.死者数     as 死者数,
       a.負傷者数   as 負傷者数,
       a.路線名     as 路線名,
       a.発生日時  年 || '/' || a.発生日時  月 || '/' || a.発生日時  月 || ' ' || a.発生日時  時 || ':' ||a.発生日時  分 as 発生日時, 
       f.区分       as 天候,
       g.区分       as 高速道路事故発生場所,
       h.区分       as 道路構造,
       a.地点 緯度(北緯) as 緯度,
       a.地点 経度(東経) as 経度
  from honhyo_2019 a
    left outer join kosokuhyo_2019 b
      on a.事故番号 = b.事故番号
    left outer join 本票_補充票_高速票_都道府県コード c
      on a.都道府県コード = c.コード
    left outer join 本票_事故内容 e
      on a.事故内容 = e.コード
    left outer join 本票_天候 f
      on a.天候 = f.コード
    left outer join 高速票_道路区分 g
      on b.道路区分 = g.コード
    left outer join 高速票_道路構造 h
      on b.道路構造 = h.コード

こんな感じのデータを取得することができました。

f:id:sanvarie:20201230175502p:plain

手順4(PyShp を使ってポイントのシェープファイルを作成) サンプルコード

「手順3」で使用している SQL を使用してポイントのシェープファイルを作成を作成します。PyShp に関しては以下のエントリーを参考にしてみてください。

www.gis-py.com

# -*- coding: utf-8 -*-
import math
from decimal import Decimal, ROUND_HALF_UP
from os.path import join
import sqlite3
import shapefile
import pandas as pd

# シェープファイル作成先
shape_path = r"D:\blog\data\shape"

# SQLite 格納先
db_path = r"D:\blog\data\sqlite\test.db"

# epsg
epsg = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]'

def dms2deg(dms):
    """緯度経度を度分秒から度へ変換します"""

    h = dms[0]
    m = dms[1]
    s = dms[2]
    deg = Decimal(str(int(h) + (int(m) / 60) + (float(s) / 3600))).quantize(Decimal('0.0001'), rounding=ROUND_HALF_UP)
    return deg

def get_data():
    """SQLiteからデータを取得します"""

    conn = sqlite3.connect(db_path)
    sql = """select a.事故番号  as 事故番号,
                   c.都道府県名 as 都道府県,
                 e.区分       as 事故内容,
                 a.死者数     as 死者数,
                 a.負傷者数   as 負傷者数,
                 a.路線名     as 路線名,
                 a.発生日時  年 || '/' || a.発生日時  月 || '/' || a.発生日時  月 || ' ' || a.発生日時  時 || ':' ||a.発生日時  分 as 発生日時,
                 f.区分       as 天候,
                   g.区分       as 高速道路事故発生場所,
                   h.区分       as 道路構造,
                 a.地点 緯度(北緯) as 緯度,
                 a.地点 経度(東経) as 経度
              from honhyo_2019 a
                left outer join kosokuhyo_2019 b
                on a.事故番号 = b.事故番号
              left outer join 本票_補充票_高速票_都道府県コード c
                on a.都道府県コード = c.コード
                left outer join 本票_事故内容 e
                on a.事故内容 = e.コード
              left outer join 本票_天候 f
                on a.天候 = f.コード
              left outer join 高速票_道路区分 g
                on b.道路区分 = g.コード
              left outer join 高速票_道路構造 h
                on b.道路構造 = h.コード
            """

    return pd.read_sql_query(sql=sql, con=conn)

def create_shape():
    """シェープファイルを作成します"""

    # SQLite からデータを取得
    df = get_data()

    # データフレームのカラム取得
    column_list = df.columns.values

    with shapefile.Writer(join(shape_path, "traffic_data.shp")) as w:
        # シェープファイルのフィールドを作成
        for column in column_list:
            if column == "緯度":
               w.field(column, "F", 18, 8)
            elif column == "経度":
                w.field(column, "F", 18, 8)
            else:
                w.field(column, "C", 100)

        # シェープファイル作成処理
        for _, row in df.iterrows():

            lat = 0
            lon = 0
            attributes = []

            for column in column_list:

                if column == "緯度":
                    lat = row[column]
                    lat = dms2deg((lat[0:2], lat[2:4], lat[4:6] + "." + lat[6:]))
                    attributes.append(lat)

                elif column == "経度":
                    lon = row[column]
                    lon = dms2deg((lon[0:3], lon[3:5], lon[5:7] + "." + lon[7:]))
                    attributes.append(lon)

                else:
                    if row[column] == None:
                        attributes.append("")
                    else:
                        attributes.append(row[column])


            # ポイントを作成
            w.point(float(lon), float(lat))
            w.record(attributes[0],attributes[1],attributes[2],attributes[3],
                     attributes[4],attributes[5],attributes[6],attributes[7],
                     attributes[8],attributes[9],attributes[10],attributes[11])

    # プロジェクションファイル作成
    with open("%s.prj" % join(shape_path, "traffic_data"), "w") as prj:
        prj.write(epsg)

if __name__ == '__main__':
    create_shape()

このようにシェープファイルが作成されていることが確認できました。やはり件数が多いだけあって容量が大きいですね。

f:id:sanvarie:20201230202558p:plain

手順5(ArcGIS Online で可視化)

シェープファイル を ArcGIS Online にアップロードしてみます。

ArcGIS Online の使い方に関してはESRIジャパンさんが主催した「ArcGIS 開発者のための最新アプリ開発塾 2020」の「Web GIS 基礎 ~ArcGIS Online を使ってみよう!~」を参考にしてみてください。

community.esri.com

路線ごとに色分けした結果を表示してみました。正しくデータがプロットされていることがわかります。

f:id:sanvarie:20201231092538p:plain

f:id:sanvarie:20201231092620p:plain

天候ごとにも色分けしてみました。

f:id:sanvarie:20201231092449p:plain

事故ごとの負傷者数で色分けなんかもできます。

f:id:sanvarie:20201231123750p:plain

データをより細かく分析するにはどうしたらいいのか?

以下のようなことがしたい場合は ArcGIS Pro などのデスクトップアプリケーションを導入することをお勧めします。

  1. 事故多発地帯の分析
  2. 同じ地点に短期間限定で事故が多発している箇所の分析
  3. 天候ごとに事故が発生しやすい箇所の分析
  4. etc

さいごに

オープンデータにありがちですが、データが素直に取り込めばいいという状態になっておらず色々苦労しました。交通事故統計データだけでなく、日本のオープンデータすべてが利用者にとってもう少し優しい形になってほしいですね。

手順が多いエントリーになってしまいましたが、何とか交通事故統計データの可視化までもっていくことができました。ただ、GIS的にはここからが本番でデータ分析してこそ、そのデータに価値が生まれるかと思います。もし ArcGIS Pro でこのデータを使った分析方法を知りたいという方がいらっしゃいましたら本エントリーにコメントを残していただければと思います。

本エントリーが今年最後です。今年もありがとうございました。来年もよろしくお願いします。それではよいお年をお迎えください。

Python で CSV ファイルを SQLite にインポートする方法

さて、本日は CSV ファイルを SQLite にインポートしてみようと思います。前回のエントリーで「交通事故統計情報のオープンデータ」のコード表を CSV に変換してみました。本エントリーではその CSV を SQLite にインポートしてみようと思います。

PDF を CSV に変換する方法について興味のある方はぜひ前回のエントリーを読んでみてください。

www.gis-py.com

また、以下のエントリーで SQLite に関して書いていますので、こちらも興味がありましたらぜひ読んでみてください。

www.gis-py.com

対象データ

前回のエントリーで出力した CSV です。 これらをファイルごとにインポートしていこうと思います。

f:id:sanvarie:20201229171900p:plain

f:id:sanvarie:20201229172351p:plain

やり方

  1. Pandas で データフレームとして CSV を読み込む
  2. 「1」のデータフレームをSQLiteにインポート

実行環境

Windows10 64bit
Python3.6.6

サンプルコード

CSV ファイルを SQLite にインポートするサンプルです。すごく短いコードで済みましたね。

# -*- coding: utf-8 -*-
import sqlite3
from os.path import join, split
from glob import glob
import pandas as pd

csv_path = r"D:\blog\data\pdf2csv\csv"
db_path = r"D:\blog\data\sqlite\test.db"

def insert_csv():
    # CSV ファイルのディレクトリ取得
    csv_files = glob(join(csv_path, "*.csv"))

    for csv in csv_files:

        table_name = split(csv)[1] # テーブル名作成

        table_name = table_name[0:-4] # テーブル名から拡張子を削除

        df = pd.read_csv(csv, dtype=object) # CSV 読込

        with sqlite3.connect(db_path) as conn:
            df.to_sql(table_name, con=conn) # SQLiteにCSVをインポート

if __name__ == '__main__':
    insert_csv()

結果を確認するとこのように CSV がSQLiteにインポートされたことがわかります。

f:id:sanvarie:20201229185546p:plain

f:id:sanvarie:20201229183843p:plain

さいごに

CSV ファイルの SQLite へのインポートは思ったよりも簡単にできることがわかりました(Pandas は本当に便利なライブラリですね)。これでようやく「交通事故統計情報のオープンデータ」の可視化にとりかかれそうです。ちょっとまだすべてのデータを確認したわけではないので何とも言えないのですが、結構大変そうですね。ただ、今年の年末年始はコロナでたっぷり時間がありそうですし楽しみながら取り掛かってみようと思います。次回のエントリーもぜひ読んでみてください。

Python で PDF を CSV に変換してみよう!

さて、本日は Python で PDF を CSV に変換してみようと思います。実は「交通事故統計情報のオープンデータ」を可視化してみようと思ったのですが、そこで使用しようと思ったデータの一部がPDFで公開されており(データ自体はCSVだったのですが、コード表がPDFで公開されていました)、まず最初にPDF → CSV 変換について書いてみようと思いました。

変換対象

「交通事故統計情報のオープンデータ」のコード表ですね。できれば別のフォーマットで公開していただければ手間が省けたのですが・・・

以下のように色々なパターンのページがあることがわかりました。

①コード表説明とコード表

f:id:sanvarie:20201229173050p:plain

②コード表のみ(コード表が複数ページにまたがっている)

f:id:sanvarie:20201229170736p:plain

③コード表説明とコード表 とコード表の補足(画面ショット右端の表)

f:id:sanvarie:20201229170810p:plain

④コード表説明のみ

f:id:sanvarie:20201229170928p:plain

これらの中から以下をCSV に変換してみようと思います。
- ①のコード表(コード表説明は省く)
- ②のコード表
- ③のコード表(コード表説明は省く)

使用するライブラリ

tabula-py というライブラリを使用します(Python 3.5+必須)。このライブラリを使用することで PDFの表を pandas の データフレームに変換することができます。

※本ライブラリを使用するには Java 8+ が必要なので、こちらもインストールします。

実行環境

Windows10 64bit
Python3.6.6

サンプルコード

「交通事故統計情報のオープンデータ」の各コード表を CSV に変換するサンプルです。

# -*- coding: utf-8 -*-
import os
import pandas as pd
import tabula

file_in = r"D:\blog\data\pdf2csv\codebook_2019.pdf"
file_out = r"D:\blog\data\pdf2csv\csv"

def check_columns(df, previous_df):
    """前ページと現ページのデータフレーム比較"""
    difference1 = set(df.keys()) - set(previous_df.keys())
    difference2 = set(previous_df.keys()) - set(df.keys())

    if (len(difference1) == 0 and len(difference2) == 0):
        return True
    else:
        return False

def pds2csv():
    """PDF → CSV 変換処理"""
    df_list = tabula.read_pdf(file_in, lattice=True, pages = 'all')

    concat_flg = 0
    previous_df = ""
    master_name = ""
    for df in df_list:

        # 各コード区分の概要説明の箇所は抽出対象外とする
        if (df.columns[0] == "項目名"):

            # 数ページにまたがっているコード表の出力
            if (concat_flg == 1):
                previous_df.to_csv(os.path.join(file_out, master_name + ".csv"), index=False)
                concat_flg = 0

            # 出力ファイル名作成
            master_name = df.columns[1]
            master_name = df[master_name][0].replace('、', '_') + "_" + master_name

        # 25ページ目の一部表は不要
        elif (df.columns[0] == "コード(下1桁)"):
            pass
        # 数ページにまたがっているコード表の結合
        elif (check_columns(df, previous_df)):
            df = pd.concat([previous_df, df])
            concat_flg = 1
        # CSV 出力
        else:
            df.to_csv(os.path.join(file_out, master_name + ".csv"), index=False)
            concat_flg = 0

        previous_df = df

if __name__ == '__main__':
    pds2csv()

結果を確認するとこのようにCSV が作成されたことがわかりました。

f:id:sanvarie:20201229171900p:plain

中身も問題なさそうですね。全部のファイルは調べていないのですが、とりあえず変換はできたっぽいです(もし何か不備がありましたら本エントリーにコメントをいただけると助かります)。

f:id:sanvarie:20201229172351p:plain

さいごに

PDF に記載されている内容を CSV に変換したいというケースはけっこうあると思います(特にオープンデータまわりを触ることが多い方)。そんな時に tabula-py はとても便利な存在かと思います。興味のある方はぜひ使ってみてください。PDF から CSV の変換が終わったのでさっそく「交通事故統計情報のオープンデータ」の可視化にとりかかりたいところですが、その前に今回変換した CSV を DB に格納するなど何かしらの処理が必要な気がしています。なので次回は その辺について書いてみようと思います。

Amazon Polly と Python で音声合成をしてみよう!

さて、本日は Amazon Polly と Python を使って音声合成(テキストなどによって入力した言葉を読み上げさせること)をしてみようと思います。ここ数年どんどん存在感を増してきている AWS ですが、本当に色々なサービスがあり便利ですね。

Amazon Polly とは?

Amazon Polly はテキストをリアルなスピーチに変換するクラウドサービスです。毎月 500万文字まで無料らしいので、非常に良心的ですね。

必要なライブラリ

Python で AWS まわりの操作をするには、boto3 というライブラリが必要です。pip install boto3 でインストールしてください。

読み上げるテキスト

コードにべた書きですが、今回は「GIS奮闘記」という文章を音声合成してみようと思います。

実行環境

Windows10 64bit
Python3.6.6

サンプルコード

# -*- coding: utf-8 -*-
import os
from tempfile import gettempdir
from contextlib import closing
from boto3 import client

polly = client("polly",
               region_name="us-east-1",
               aws_access_key_id="****",
               aws_secret_access_key="****")

response = polly.synthesize_speech(Text = "GIS奮闘記",
                                   OutputFormat = "mp3",
                                   VoiceId = "Mizuki")

if "AudioStream" in response:
    with closing(response["AudioStream"]) as stream:
        output = os.path.join(gettempdir(), "speech.mp3")

        with open(output, "wb") as file:
            file.write(stream.read())

os.startfile(output)

作成された音声ファイルが以下になります。

https://drive.google.com/file/d/1ZgowBcCQPDJw9U1C0lLZ9ZVfdQ1zk6H_/view?usp=sharing

ばっちり「GIS奮闘記」ち読み上げられていますね!

さいごに

この技術を使用すれば Web サイトや スマホアプリの中で色々なテキストの読み上げをすることができますね。こんなサービスを用意してくれている AWS は本当にありがたい存在です。Python からの使用もとても簡単なので興味のある方はぜひ Amazon Polly を使ってみてください。本日は以上です。

Python と Googleマップのタイムライン機能を使って自分の移動履歴を可視化してみよう!

さて、本日は Googleマップのタイムライン機能を使って自分の移動履歴を可視化してみようと思います。自分の移動履歴が見れるなんて面白そうですね!

タイムライン機能とは?

Googleマップの機能で自分がいる「場所」と「時間」を記録してくれるものです。

どのように記録したデータを利用するのか?

ここから JSON としてタイムライン機能が記録したデータをダウンロードすることができます。ただし、タイムライン機能をオンにしておく必要があるので、使用してみたいという方は先に設定を確認した方がいいかと思います。

appllio.com

どのように可視化するか

以下のエントリーと同じく PyShp と ArcGIS Pro を使用します。

www.gis-py.com

手順

  1. タイムライン機能が記録したデータをダウンロード(KML と JSON をダウンロードできますが、今回は JSON をダウンロードします)
  2. PyShp を使ってJSON を Shape ファイルに変換
  3. ArcGIS Proで可視化

1. JSON をダウンロード

1.https://takeout.google.com/settings/takeout/custom/location_history に行き、「次のステップ」を押します。

f:id:sanvarie:20201010074328p:plain

2.「エクスポートを作成」を押します。

f:id:sanvarie:20201010074407p:plain

3.「ダウンロード」を押します。

f:id:sanvarie:20201010074542p:plain

4.ロケーション履歴.json がダウンロードされます(以下のイメージでは緯度経度をマスキングしています) f:id:sanvarie:20201011212111p:plain

2. PyShp を使って json を Shape ファイルに変換

json をダウンロードしたら次はそれを Shape ファイルに変換します。

実行環境

Windows10 64bit
Python3.6.6
ArcGIS Pro2.6

サンプルコード

少し冗長になってしまいましたが、ロケーション履歴.json を Shape ファイルに変換するサンプルコードです。ポイントとラインの Shape ファイルに変換しています。

# -*- coding: utf-8 -*-
import json
from datetime import datetime
import shapefile

input_file = r"D:\python\data\GoogleMaps\Takeout\ロケーション履歴\ロケーション履歴.json"
output_file_point = r'D:\python\data\googlemaps\timeline_point.shp'
output_file_line = r'D:\python\data\googlemaps\timeline_line.shp'
output_projection_file_point = r'D:\python\data\googlemaps\timeline_point'
output_projection_file_line = r'D:\python\data\googlemaps\timeline_line'

def convert_time(timestamp):
    """ unix 時間を変換"""

    t = datetime.fromtimestamp(float(timestamp)/1000)
    return (t.year, t.month, t.day, t.hour, t.minute, t.second)

def create_projection_file(file):
    """ プロジェクションファイル作成"""

    with open("%s.prj" % file, "w") as prj:
        epsg = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]'
        prj.write(epsg)

def create_line():
    """ライン作成"""

    with shapefile.Writer(output_file_line) as w:

        # とりあえず属性は以下の一つとする
        w.field("年月日", "C")

        # json 読込
        json_open = open(input_file, 'r')
        json_load = json.load(json_open)

        verticles = []
        date = ""
        previous_date = ""

        for j in json_load["locations"]:

            timestamp = convert_time(j["timestampMs"])
            lat = float(j["latitudeE7"])/10000000
            lon = float(j["longitudeE7"])/10000000

            # 年月日ごとにラインを作成
            date = str(timestamp[0]) + "/" + str(timestamp[1]) + "/" + str(timestamp[2])

            if (len(verticles) == 0):
                verticles.append([lon, lat])
            elif (previous_date != "" and date != previous_date):

                if(len(verticles) > 1):
                    w.line([verticles])
                    w.record(previous_date)
                    # いったんクリア
                    verticles = []
                    verticles.append([lon, lat])
                elif(len(verticles) == 1):
                    verticles.append([lon, lat])
            else:
                verticles.append([lon, lat])

            previous_date = date

        # 最後ラインを作成
        if(len(verticles) > 0):
            w.line([verticles])
            w.record(previous_date)

        # プロジェクションファイル作成
        create_projection_file(output_projection_file_line)

def create_point():
    """ポイント作成"""

    with shapefile.Writer(output_file_point) as w:

        # とりあえず属性は以下の四つとする
        w.field("年月日", "C")
        w.field("時間", "C")
        w.field("緯度", "F", 12, 6)
        w.field("経度", "F", 12, 6)

        # json 読込
        json_open = open(input_file, 'r')
        json_load = json.load(json_open)
        for j in json_load["locations"]:

            timestamp = convert_time(j["timestampMs"])
            lat = float(j["latitudeE7"])/10000000
            lon = float(j["longitudeE7"])/10000000

            w.point(lon, lat)
            w.record(str(timestamp[0]) + "/" + str(timestamp[1]) + "/" + str(timestamp[2]),
                     str(timestamp[3]) + ":" + str(timestamp[4]) + ":" + str(timestamp[5]),
                     lat,
                     lon)

        # プロジェクションファイル作成
        create_projection_file(output_projection_file_point)

if __name__ == '__main__':
    create_point()
    create_line()

このように Shape ファイル(ポイントとライン)に変換することができました。

f:id:sanvarie:20201011224728p:plain

3. ArcGIS Pro で可視化

まずはポイントを可視化してみました。年月日と時間ごとにレコードを作成しました。田端駅周辺にいたことがわかります。ただ、これだと、どういう移動経路なのかがわかりません。

f:id:sanvarie:20201012060246p:plain

そこで、ラインを可視化してみます。以下の画像では2020/10/8は強調されたラインが移動経路になっています。

f:id:sanvarie:20201012060336p:plain

2020/10/10は北区→豊島区→大田区に移動しているのがわかります。

f:id:sanvarie:20201012060432p:plain

さいごに

Googleマップのタイムライン機能を使えばこんなに面白いことが簡単にできてしまうことが分かったかと思います。微妙に場所がずれているのが残念なのと(まぁ仕方ないとは思いますが)、どのタイミングで自分がいた地点が JSON に記録されるのか知れたらもっと楽しそうですね。また、今回も PyShp を使ってみたのですが、サクッと Shape ファイルを作るにはもってこいのライブラリなので、ぜひ使ってみてください。今回は以上です。

ArcObjects からの移行について考えてみる

ArcObjects を使用して GIS アプリケーションを開発している方はたくさんいるかと思います。しかし、Esri は ArcGIS Online などの Web GIS 製品に力を入れており、なおかつ、ArcMap から ArcGIS Pro への移行に伴い、近い将来、ArcObjects のサポートが終了すると言われています。本日はその ArcObjects からの移行について考えてみようと思います。かなり深いテーマであり、本エントリーですべてを網羅できるほど単純な話ではないのは承知なのですが、本件について頭を悩ませている方の参考になれば幸いです。

対象読者

  1. ArcObjects を使用している開発者の方
  2. ArcObjects の代替製品を探している方

ArcMap、 ArcGIS Pro とは

Esri が販売している GIS デスクトップアプリケーションです。それぞれの違いについて以下のエントリーに書いていますので、興味のある方はぜひ読んでみてください。

www.gis-py.com

ArcObjects とは

簡単に言うと、GIS アプリケーションを開発するためのライブラリでしょうか。

blog.esrij.com

Esri の方向性

Esri はプラットフォームとしての ArcGIS の使用を推奨しており、ArcObjects を使用してアプリをスクラッチで開発するというのは時代に合っていないと考えていると個人的には感じています。少し説明しづらいのですが、プラットフォームとしての ArcGIS とは以下に詳細が記載されています。ただ、これを読んでもなかなかピンとこないかと思います。簡単に言うと「Web GIS を利用してデータを共有」「C/S アプリの開発はもうやめて Web GIS を使用」「できるだけアプリの開発はせずに、ArcGIS の既製品を利用」していこうといった感じだと思います。ビジネスのやり方としてはすごく時代に合っていると思います。

www.esrij.com

ArcObjects の代わりとなる製品

単刀直入に言うと代替となる製品はありませんし、現時点でそれを開発をしているという情報もありません。あえて言うなら以下が候補になりうるかと思いますが、今まで ArcObjects で作っていたものをすべて作り直さなければいけません。

ArcGIS Pro SDK

以下エントリーで紹介しているのですが、ArcGIS Pro SDK を使えば ArcGIS Pro の UI をカスタマイズしたり、アドイン機能を開発することができます。

www.gis-py.com

ArcGIS Pro SDK のメリット

  1. ArcGIS Pro をベースにしてアドインを作るだけなので、基本機能は ArcGIS Pro のものを利用することができ、開発工数を減らすことができる。

思いつくのはこのくらいでしょうか。

ArcGIS Pro SDK のデメリット

  1. 高価な ArcGIS Pro を必ず買わなければいけないので費用がかさむ。
  2. ArcGIS Pro ありきなので、ArcGIS Pro が起動していることが前提。そのため、ArcObjects だったらできていたような処理を実現することが難しくなる(タスクでの夜間処理など)
  3. ArcGIS Pro の上にのっかっているアドイン開発なので、ArcGIS Pro を完全に制御することはできない(自由度が低い)。
  4. ArcGIS Pro での何気ない操作が ArcGIS Pro SDK 上でイベントとして走ってしまうことがある(制御が難しい)。

ArcObjects の代替と考えるのは難しいかと思います。

ArcGIS Runtime SDK

こちらも以下エントリーで紹介しているのですが、Windows 及び iOS、Android プラットフォーム上で動作するネイティブ GIS アプリケーションの開発キットです。

www.gis-py.com

ArcGIS Runtime SDK のメリット

  1. クロスプラットフォームでデスクトップアプリとしても開発できるし、スマホアプリとしても開発できる。

メリットとしてはこの辺なのでしょうか。ArcGIS Pro SDK とは異なり、アプリの制御は自分の好きなようにできます。

ArcGIS Runtime SDK のデメリット

  1. Web クライアント的な立ち位置なので、ArcObjects ほど何でもできるというわけではない。特に高頻度で様々なデータを編集するようなアプリの開発には向いていない。
  2. 機能とは関係ないですが、ライセンス形態が複雑すぎて理解することが困難。

こちらも ArcObjects の代替と考えるのは難しいかと思います。

番外:ArcPy

ArcObjects で夜間バッチなどを走らせている場合、上記二つの SDK ではなく、ArcPy を使ってください、と米国Esriもたしか言っていた気がします。

どうやって ArcObjects の代わりとなるアプリを開発すればいいのか

仕様が満たせるのであれば、ArcGIS Pro SDK か ArcGIS Runtime SDK のどちらか適した方で開発すればいいかと思います。ただ、それが難しい場合(ArcGIS Pro SDK も ArcGIS Runtime SDK も単独では ArcObjects の代わりとして使えない場合)、業務や用途に合わせてこれら二つの SDK を使って複数のアプリケーションを開発する必要があるかと思います。ただ、個人的にはこれはあまり現実的ではないかと思います。まず、コストの問題です。ただでさえ ArcGIS Pro は高いのにさらに ArcGIS Runtime SDK 分のコストをかけるというのは難しいかと思います。また、二つの SDK を習得する学習コストや複数のアプリを保守する必要がでてくるので、そこも大きな負担になってくるかと思います。

なぜ ArcObjects の代替となる製品がないのか?

上述したように、Esri はプラットフォームとしての ArcGIS の使用を推奨しており、ArcObjects でアプリを開発するという手法は時代に合わなくなってきていると考えているからだと思います。基本的には Web GIS を中心にできるだけ開発をせずに ArcGIS の既製品を使いましょうというスタンスですね。

ArcGIS の既製品とは何か?

ArcGIS にはデスクトップ製品や開発用の SDK 以外にも様々な製品があります。例えば以下のような製品は ArcGIS のライセンスさえ持っていれば無料で使用することができたはずです。ArcGIS Online や ArcGIS Enterprise などの Web GIS を中心にこういった ArcGIS 製品をできるだけ使用して、開発をできるだけ少なくしていきましょうという方向性になっていますね。

www.esrij.com

doc.arcgis.com

www.esrij.com

Esri の方針は日本の市場に合っているのか?

個人的には合っているとは思いません。Web GIS はまだまだ日本で浸透しているとはいえず、色々な会社さんでスタンドアロン、C/S アプリの開発が行われているのが現状です。日本ではシステムを導入する際、どうしても開発することが前提になってしまうことが多いので、これがプラットフォームとしての ArcGIS の普及が遅れている大きな要因の一つかと思います。また、日本だとシステムを刷新する際も業務フローは意地でも変えず(システムに合わせて業務を変えることはしない)、システムを業務に合わせる傾向にあると思います。そのため、ArcObjects が使われていたのだと思うのですが、プラットフォームとしての ArcGIS を使うためにはシステムに合わせて業務を変える必要が出てくると思います。こういった日本のシステム導入の傾向には Esri の方針は明らかにマッチしていないのではと感じます。

どうして Web GIS がなかなか浸透していないのか?

多くの会社さんが Web GIS に移行したいと考えているかもしれませんが、様々な問題がありそれを実行することができません。

  1. セキュリティ上の理由で ArcGIS Online は使用できない。
  2. ArcGIS Enterprise は非常に高価なので導入できない。

※ArcGIS Online と ArcGIS Enterprise が Web GIS にあたります。

典型的なものとしては上記でしょうか。特に小さな自治体さんなんかに当てはまると思いますが、こうなると Web GIS は使うことができませんね(プラットフォームとしての ArcGIS の使用は難しい)。ですので、こういった自治体さんは ArcObjects で開発したようなスタンドアロンの GIS ソフト を導入せざるを得ません。そのため、開発会社さんもそういった需要に答える必要があり、Web GIS の導入が進まないと考えられます。

ただ、プラットフォームとしての ArcGIS が悪いわけではないかと思います。むしろ、要件にはまればすごく便利だと思います。それが日本での需要にマッチしているかどうかはまた別の話だと思いますが。

ArcGIS ユーザーが他の GIS エンジンに逃げる?

私の知る限りですが、すでにそういう動きをしている会社さんはあります。特に小さな自治体さんなんかを顧客に持つ開発会社さんには「プラットフォームとしての ArcGIS 」はメリットよりデメリットの方が大きいかと思います。 ArcGIS Pro SDK や ArcGIS Runtime SDK では仕様を満たすことが難しいし、コスト的に採用できない。また、これらの SDK で開発するにしても今まで ArcObjects で作ったソフトを作り直ししなければならないのだったらこのタイミングで別の GIS エンジンに乗り換えるというのは自然な流れかと思います。

結局どうすればいいのか?

ArcObjects から移行するにあたって、色々なデメリットがあっても ArcGIS を使い続けたいという方は多いかと思います。そういった方向けに個人的に思う ArcObjects の代替パターンを記載します。

ArcObjects 代替パターン

仕様 ArcGIS Pro SDK ArcGIS Runtime SDK
編集機能がある場合(ポイント、ライン、ポリゴンすべて) ×
編集機能がある場合(アノテーション) ×
編集機能がある場合(ポイントのみ)
参照機能のみの場合※1
高度な印刷機能がある場合※2 ×
ラスターを編集(幾何補正など)する場合 ×
モバイルアプリの場合 ×
アプリを完全に制御したい場合 ×
図面作成をしたい場合 ×
C/S にしたい場合 ×

※1. 仕様次第ですが、高度な分析機能がなければ ArcGIS Runtime SDK で十分かと思います。
※2. テンプレートを使用したり、図面の印刷をする場合など

仕様やコストを考慮しながら決めるものなので、上記の表が絶対というわけではありませんが、参考程度に見ていただければと思います。また、ArcObjects で高度なアプリケーション(様々な編集、分析機能を有しているアプリケーション)を開発している場合、どちらの SDK も不十分となってしまうかもしれません。その場合は、以下二つの選択肢しかないかと思います。

  1. 別の GIS エンジンを使用
  2. アプリの仕様を変更する

まとめ

取り留めの無い話になってしまったかもしれませんが、いかがでしたでしょうか。本件は非常に複雑で、本エントリーですべてを書き切ることは難しいですし、有識者の方からしたら考慮が足りない部分もあるかもしれません。しかし、ArcObjects からの移行に関しては ESRIジャパンさんから具体的なアナウンスがないですし、困っている方がたくさんいらっしゃるかと思います。本エントリーだけでは不十分かと思いますが、そういった方への参考になれば幸いです。もし、何か気になること、知りたいことがありましたらコメントを残していただければと思います。