GIS奮闘記

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

スポンサーリンク

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 アプリの開発が行われているのが現状です。

どうして 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ジャパンさんから具体的なアナウンスがないですし、困っている方がたくさんいらっしゃるかと思います。本エントリーだけでは不十分かと思いますが、そういった方への参考になれば幸いです。もし、何か気になること、知りたいことがありましたらコメントを残していただければと思います。

PyShp を使って CSV を Shape に変換する方法

さて、本日は久しぶりに GIS に関するエントリーを書いてみようと思います。PyShp を使って CSV を Shape に変換してみます。

PyShp とは

Shape ファイルを読み書きするためのライブラリです。

使用するデータ

ESRIジャパンさんが主催した「ArcGIS 開発者のための最新アプリ開発塾 2020」で使用された 各店舗売上.csvを使用します。

community.esri.com

CSV の中身はこんな感じになっています。

f:id:sanvarie:20200930145007p:plain

使用するライブラリ

PyShp と Pandas を使用するのでインストールをお願いします。

実行環境

Windows 10 64bit
Python 3.6.10
Pandas 1.1.2
PyShp 2.1.2
ArcGIS Pro 2.6(作成した Shape ファイルの確認用に使用)

サンプルコード

PyShp を使って CSV を Shape に変換するサンプルです

# -*- coding: utf-8 -*-
import shapefile
import pandas as pd

# 読み込むCSV
input_file = r"D:\python\data\csv\各店舗売上.csv"

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

# CSV のカラムを取得
column_list = df.columns.values

def create_shape():

    with shapefile.Writer(r'D:\python\data\pyshp\各店舗売上.shp') as w:

        # Shape のフィールドを作成
        for column in column_list:
            if column == "緯度":
               w.field(column, "F", 12, 6)
            elif column == "経度":
                w.field(column, "F", 12, 6)
            else:
                w.field(column, "C", 50)

        for _, row in df.iterrows():

            lat = 0
            lon = 0
            attributes = []

            for column in column_list:

                if column == "緯度":
                    lat = row[column]
                    attributes.append(lat)

                elif column == "経度":
                    lon = row[column]
                    attributes.append(lon)

                else:
                    attributes.append(row[column])

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

    # プロジェクションファイル作成
    with open("%s.prj" % r'D:\python\data\pyshp\各店舗売上', "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)

if __name__ == '__main__':
    create_shape()

結果をみるとばっちり Shape ファイルが作成されたことがわかります。属性もしっかり付与されてますね。

f:id:sanvarie:20200930200901p:plain

PyShp のいいところ

GDAL や Fiona などで同じことをやろうとすると以下エントリーのように日本語でひっかかるのですが、PyShp の場合はその問題は起きないのが素晴らしいですね。

www.gis-py.com

PyShp のいまいちなところ

属性を作成する際に、w.record(attributes[0],attributes[1],attributes[2],attributes[3],attributes[4],attributes[5],attributes[6],attributes[7]) のようにしなければならないのがいまいちかなと思います(他のやり方があるのかもしれませんが・・・)。この仕様だと汎用的には使えないですね。

最後に

ArcGIS のような商用 GIS を使用すれば Shape ファイルの作成なんて簡単にできてしまいますが、高価な製品なので簡単には購入できません。Shape ファイルが欲しいけど、フリーのライブラリで何とかしたいという方はぜひ PyShp を使ってみてください。本日は以上です。

Python で Tkinter を使って GUI アプリを作ってみよう!

さて、今日は Python で Tkinter を使って GUI アプリを作ってみようと思います。意外と知られていないのですが、Python でも GUI アプリを作ることができます。様々な GUI 系のライブラリがあるのですが、今日は Tkinter というライブラリを使ってみようと思います。

Tkinter とは

Tkinter は Python からGUIを構築・操作するための標準ライブラリ(ウィジェット・ツールキット)です。

作るもの

ジオコーディングをする簡単な GUI アプリです。仕様の詳細は以下を参照してください。

  • 場所を入力
  • 実行ボタンを押下
  • ジオコーディングした結果(緯度経度)を出力

ジオコーディングは geocoder を使おうと思います。geocoder は以下のエントリーでも紹介しているので、興味があったら読んでみてください。

www.gis-py.com

環境

  • Windows 10
  • Python 3.6.6

サンプルコード

ジオコーディングをする GUI アプリのサンプルです。

# -*- coding: utf-8 -*-
from tkinter import*
import geocoder

root = Tk()
root.geometry("300x150")
root.title("ジオコーディングツール")

# フレーム
frame = Frame(root, width = 300, height=150, relief=SUNKEN)
frame.grid()

# ラベル作成
label_location = Label(frame, text="場所", font=('Meiryo' ,12, 'bold'), padx=5, pady=5)
label_location.grid(row=0, column=0, sticky=E)

label_lat = Label(frame, text="緯度", font=('Meiryo' ,12, 'bold'), padx=5, pady=5)
label_lat.grid(row=1, column=0, sticky=E)

label_lon = Label(frame, text="経度", font=('Meiryo' ,12, 'bold'), padx=5, pady=5)
label_lon.grid(row=2, column=0, sticky=E)

# テキストボックス作成
textbox_location = StringVar()
textbox_location_entry = Entry(frame, textvariable=textbox_location, width=30)
textbox_location_entry.grid(row=0, column=1)

textbox_lat = StringVar()
textbox_lat_entry = Entry(frame, textvariable=textbox_lat, width=30)
textbox_lat_entry.configure(state='readonly')
textbox_lat_entry.grid(row=1, column=1)

textbox_lon = StringVar()
textbox_lon_entry = Entry(frame, textvariable=textbox_lon, width=30)
textbox_lon_entry.configure(state='readonly')
textbox_lon_entry.grid(row=2, column=1)

def exexute_geocoding():
    """ジオコーディングを実行するメソッド"""

    # 一度テキストボックスを編集可能にする
    textbox_lat_entry.configure(state='normal')
    textbox_lon_entry.configure(state='normal')

    # ジオコーディング実行
    location = geocoder.osm(textbox_location.get())
    textbox_lat_entry.insert(END, location.latlng[0])
    textbox_lon_entry.insert(END, location.latlng[1])

    # テキストボックスを編集不可にする
    textbox_lat_entry.configure(state='readonly')
    textbox_lon_entry.configure(state='readonly')

def exexute_clear():
    # 一度テキストボックスを編集可能にする
    textbox_lat_entry.configure(state='normal')
    textbox_lon_entry.configure(state='normal')

    textbox_lat_entry.delete(0, END)
    textbox_lon_entry.delete(0, END)
    textbox_location_entry.delete(0, END)

    # テキストボックスを編集不可にする
    textbox_lat_entry.configure(state='readonly')
    textbox_lon_entry.configure(state='readonly')

# ボタン作成
btn_execute = Button(frame, text='実行', command = exexute_geocoding)
btn_execute.grid(row=3, column=1, sticky=E)

btn_clear = Button(frame, text='クリア', command = exexute_clear)
btn_clear.grid(row=3, column=2, sticky=W)

root.mainloop()

こんな感じの見た目になりました。普段 Visual Studio を使っているので、アイテムの配置とかは少し面倒な印象ですね。

f:id:sanvarie:20200919162620p:plain

このように場所を入力します。

f:id:sanvarie:20200919162740p:plain

実行ボタンを押すと、このように緯度経度が出力されます。そして、クリアボタンを押すと項目がクリアされます。

f:id:sanvarie:20200919162812p:plain

まとめ

いかがでしたでしょうか?Python でもこのように GUI アプリを簡単に作成することができます。スクリプトとして実行するだけでも問題ない場合がほとんどかと思いますが、画面で入力できた方が便利な場合もあるかと思います。もし興味がある方はぜひ Tkinter を使ってみてください。本日は以上です。

Python で PostgreSQL を操作してみよう!

さて、本日は Python を使った PostgreSQL の操作について紹介しようと思います。

関連記事

Python で SQL Server、SQLite を操作する方法についても書いていますので、興味のある方はぜひ読んでみてください。

www.gis-py.com

www.gis-py.com

使用するライブラリ

psycopg2 というライブラリを使用します。

実行環境

Windows10 64bit
Python 3.6.6
PostgreSQL 12.4

使用するデータ

以下のようなテーブルを用意しました。

f:id:sanvarie:20200901144003p:plain

データは以下のようなものを用意しました。

f:id:sanvarie:20200901144032p:plain

今回試すこと

psycopg2 を使用して以下を実行してみます

ログイン
検索
更新
挿入
削除

ログイン

接続文字列を作成して psycopg2.connect を実行すればログインができます。

# -*- coding: utf-8 -*-
import psycopg2

def connect():
    con = psycopg2.connect("host=" + "localhost" +
                           " port=" + "5432" +
                           " dbname=" + "DB名" +
                           " user=" + "ユーザー" +
                           " password=" + "パスワード")

    return con
if __name__ == '__main__':
    con = connect()

検索

テーブルを検索してカラムの値を print します。

def select_execute(con, sql):
    with con.cursor() as cur:
        cur.execute(sql)
        rows = cur.fetchall()

    return rows

if __name__ == '__main__':
    con = connect()

    sql =  'select * from test'

    res = select_execute(con, sql)
    for r in res:
        print(r[0])
        print(r[1])

このような結果になりました。

f:id:sanvarie:20200901143407p:plain

更新

name カラムの中身を「A」から「B」となるようにデータを更新します。

def updatet_execute(con, slq):
    with con.cursor() as cur:
        cur.execute(sql)

    con.commit()

if __name__ == '__main__':
    con = connect()

    sql =  """update test set name = 'B'"""

    # データ更新
    updatet_execute(con, sql)

想定通りの結果になりました。

f:id:sanvarie:20200901150408p:plain

挿入

1レコード挿入する処理を書いています。

def insert_execute(con, slq):
    with con.cursor() as cur:
        cur.execute(sql, (2,'Z'))

    con.commit()

if __name__ == '__main__':
    con = connect()

    sql =  """insert
                into test(id,
                          name)
                      values(%s,
                             %s)"""

    # データ登録
    insert_execute(con, sql)

ちゃんと挿入した分のレコードが DB 側に反映されていました。

f:id:sanvarie:20200901145114p:plain

削除

上記処理で挿入したレコードを削除しています。

def delete_execute(con, slq):
    with con.cursor() as cur:
        cur.execute(sql)

    con.commit()

if __name__ == '__main__':
    con = connect()

    sql =  """delete
                from test
               where id = 2"""

    # データ削除
    delete_execute(con, sql)

想定通りレコードが削除されました。

f:id:sanvarie:20200901145412p:plain

まとめ

psycopg2 を使った一通りの処理を紹介したのですが、簡単に Python で PostgreSQL を操作できることがわかるかと思います。システム開発にDB はつきものなので、今後も色々 DB の扱いについて書いてみようと思います。本日は以上です。

Python で ini ファイルを読み込んでみよう!

色々忙しくてしばらく更新が止まっていましたが、今日は久しぶりに書いてみようと思います。本日は Python で ini ファイルを読み込む方法について紹介しようと思います。

ini ファイルとは

一言でいうと設定ファイルですね。プログラムで使用する値を ini ファイルに記載します。

使用するライブラリ

configparser という Python 標準ライブラリを使用します。

使用する ini ファイル

以下のような ini ファイルを用意しました。「workspace」と「data」に対してそれぞれ設定値を記載しています。

f:id:sanvarie:20200901103243p:plain

実行環境

Windows10 64bit
Python 3.6.6

サンプル

ini ファイルを読み込んで print で出力するサンプルです。

# -*- coding: utf-8 -*-
import os
from pathlib import Path
import configparser

dic = {}

def read_ini():
    config = configparser.ConfigParser()
    path = os.path.join(Path(__file__).parent, "example.ini")
    config.read(path)
    config.optionxform = str

    for section in config.sections(): # ini ファイルの値をディクショナリに格納
        for key in config.options(section):
            dic[key] = config.get(section, key)

def get_value():
    print(dic["workspace"])
    print(dic["data"])

if __name__ == '__main__':
    read_ini()
    get_value()

configparser を使用して ini ファイルの値をディクショナリに格納して、その値を print しています。結果は想定通り以下のようになりました。

f:id:sanvarie:20200901104502p:plain

まとめ

何かプログラムを作る際、ini ファイルなどを使用することはよくあるかと思います。プログラム内にパスをべた書きするよりも今回のように設定ファイルに記載しておくことをお勧めします。すごく簡単にできるのでぜひ使ってみてください!

TypeScript を使って Web マップにカスタムウィジェットを配置してみよう!

さて、本日は TypeScript を使って Web マップにカスタムウィジェットを配置してみようと思います。みなさんは TypeScript を使っていますでしょうか?おそらく日本ではまだ馴染みのない言語の一つかもしれません。ただ、近年人気が高まってきており、近い将来はメジャーな言語の一つになるのではないかと思っています。

TypeScript とは

TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースのプログラミング言語である。TypeScript は JavaScriptに対して、省略も可能な静的型付けとクラスベースオブジェクト指向を加えた厳密なスーパーセットとなっている(Wikipedia より抜粋)。

簡単に言うと JavaScript の進化版といったところでしょうか。そして、TypeScript で書いたコードをコンパイルすると JavaScript のコードに変換することができます。もっと細かいことを知りたい方は以下 Web サイトに記載されているので、読んでみてください。

qiita.com

なぜ TypeScript を使ってみようと思ったのか

以下のように JavaScript → TypeScript への流れに取り残されないようにするためでしょうか。やはりエンジニアの端くれとして新しいことにチャレンジし続けたいですね。

  1. Google が TypeScript を社内の標準言語に採用するなど、TypeScript の存在感は年々高まってきているため
  2. 米国Esri が TypeScript を推しているため(将来的には 全てのコードが TypeScript になるかも?!)
  3. 個人的に将来を見据えて新しいことに挑戦したかったため

準備

まずは環境を準備しなくてはいけませんね。こちらは ESRIジャパンさんのブログなのですが、TypeScript の環境構築から簡単なサンプルコードを載せてくれています。こちらを参考にさせてもらいます。

community.esri.com

また、TypeScript でのカスタムウィジェット作成に関しては、ESRIジャパンさんが公開しているカスタムウィジェット ハンズオンを参考にしてみました。

github.com

今回チャレンジすること

概観図をカスタムウィジェットとして作ってみようと思います。概観図とは以下の画像の右上にある地図のことです。メインのマップの概観を表示させるものですね。

f:id:sanvarie:20200517151318p:plain

概観図の JavaScript 用のサンプルコードが以下に公開されているので、今回はこれを TypeScript に書き換えてみようと思います。 developers.arcgis.com

作ったもの

上記に記載したリソースをもとに以下のような構成でカスタムウィジェットつきのWeb マップを作ってみました。

f:id:sanvarie:20200517151959p:plain

サンプルコード

「OverView.tsx」「index.html」のサンプルコードです。

OverView.tsx

import Map from "esri/Map";
import MapView from "esri/views/MapView";
import Graphic from "esri/Graphic";
import * as watchUtils from "esri/core/watchUtils";
import {subclass, declared, property} from "esri/core/accessorSupport/decorators";
import Widget = require("esri/widgets/Widget");
import { renderable, tsx } from "esri/widgets/support/widget";

@subclass("esri.widgets.OverView")
class OverView extends declared(Widget) {

  constructor() {
    super();
    this._overViewChange = this._overViewChange.bind(this);
  }

  @property()
  @renderable()
  map: Map;

  @property()
  @renderable()
  mainView: MapView;

  @property()
  @renderable()
  mapView: MapView;

  @property()
  @renderable()
  graphic: Graphic;
  
  // postInitialize メソッドの追加
  postInitialize() {

    var overviewMap = new Map({
      basemap: "topo"
    });

    this.mapView = new MapView({
      container: "overviewDiv",
      map: overviewMap,
      center: [139.740286, 35.678601],
      zoom: 10,
      constraints: {
        rotationEnabled: false
      }
    });

    this.mapView.ui.components = [];
    
    var graphicSymbol = {
      type: "simple-fill",
      color: [100, 0, 0, 0.5]
    };
  
    this.graphic = new Graphic({
      geometry: null,
      symbol: graphicSymbol
    });
    
    this.mapView.graphics.add(this.graphic);

    watchUtils.init(this, "mainView.extent", () => this._overViewChange());
  }

  private _overViewChange () {
    if (this.mainView.stationary) {
      this.mapView
        .goTo({
          center: this.mainView.center,
          scale:
          this.mainView.scale *
            2 *
            Math.max(
              this.mainView.width / this.mapView.width,
              this.mainView.height / this.mapView.height
            )
        })
        .catch(function(error) {
          if (error.name != "view:goto-interrupted") {
            console.error(error);
          }
        });
    }

    this.graphic.geometry = this.mainView.extent;
  }

  render() {
    return (
      <div
      bind={this}>
      </div>
    );
  }
}

export = OverView;

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      name="概観図"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <title>Overview map - 4.15</title>
    <style>
      html,
      body,
      #viewDiv {
        padding: 0;
        margin: 0;
        height: 100%;
        width: 100%;
      }

      #overviewDiv {
        position: absolute;
        top: 12px;
        right: 12px;
        width: 600px;
        height: 200px;
        border: 1px solid black;
        z-index: 1;
        overflow: hidden;
      }
    </style>

    <link
      rel="stylesheet"
      href="https://js.arcgis.com/4.15/esri/themes/light/main.css"
    />
    <script>
    /*
    dojoConfig の設定
    */
    var locationPath = location.pathname.replace(/\/[^\/]+$/, "");
    window.dojoConfig = {
        packages: [
        {
            name: "app",
            location: locationPath + "/app"
        }
        ]
    };
    </script>
    <script src="https://js.arcgis.com/4.15/"></script>

    <script>
      require([
        "esri/Map",
        "esri/views/MapView",
        "app/OverView"
      ], function(Map, MapView, OverView) {
        
        var mainMap = new Map({
          basemap: "hybrid"
        });


        var mainView = new MapView({
          container: "viewDiv",
          map: mainMap,
          center: [139.740286, 35.678601],
          zoom: 15
        });

        mainView.when(function() {
          /*
          ウィジェットのインスタンス化
          */

          overView = new OverView({
            map:mainMap,
            mainView:mainView
          });

      mainView.ui.add(overView, "top-right");
      });
    });
    </script>
  </head>
  <body>
    <div id="viewDiv" class="esri-widget"></div>
    <div id="overviewDiv"></div>
  </body>
</html>

TypeScript に関してはほとんど素人同然なので変な書き方をしているかもしれませんが、完成させることができたのでひとまずほっとしております。

以下のように概観図がマップに配置されていることが確認できました。

f:id:sanvarie:20200517153044p:plain

メインのマップを移動させると概観図の赤枠(メインのマップの表示箇所)も移動することがわかります。

f:id:sanvarie:20200517153136p:plain

TypeScript を使ってみた感想

最初は書き方や TypeScript 独特のコンセプトに戸惑うこともあったのですが、慣れの問題かと思います。色々使ってみる中で感じたのは、やはり型が使えるというのが JavaScript に比べて大きなアドバンテージかなと思いました(今回はあまりそのメリットを享受していないのですが)。まだまだ知らないことも多いのですが、色々触りながら勉強してみようと思います。本日は以上です。

Python で XML を CSV に変換する方法

さて、本日はPython で XML を CSV に変換する方法について書いてみようと思います。前回のエントリーでは Python で CSV を XML に変換する方法について書きましたが、今回はその逆ですね。

使用する XML

前回のエントリーで作成した prefectures.xml を使用します。興味のある方はぜひ前回のエントリーを読んでみてください。

f:id:sanvarie:20200411132917p:plain

www.gis-py.com

出力する CSV

以下のような形で CSV を出力しようと思います。

f:id:sanvarie:20200411121747p:plain

実行環境

Windows 10 64bit
Python 3.6.6

サンプルコード

prefectures.xml を CSV に変換するサンプルです。

# -*- coding: utf-8 -*-
import csv
from xml.etree import ElementTree

def xml2csv():
    input_file = r"D:\data\csv\prefectures_from_xml.csv"
    output_file = r"D:\data\csv\prefectures.xml"

    # CSV 作成
    f = open(input_file, "w")

    # ヘッダー
    output_string = "公表年,公表月,公表日,都道府県,患者数(2020年3月28日からは感染者数),現在は入院等,退院者,死亡者\n"

    # ヘッダー書き込み
    f.write(output_string)

    # XML 読込
    tree = ElementTree.parse(output_file)

    # ルート
    root = tree.getroot()

    text_list = []

    for r in root:

        if r.tag == "date":
            text_list.append(r.find('year').text)
            text_list.append(r.find('month').text)
            text_list.append(r.find('day').text)

        elif r.tag == "prefecture":
            text_list.append(r.text)

        elif r.tag == "info":
            text_list.append(r.find('cases').text)
            text_list.append(r.find('hospitalized').text)
            text_list.append(r.find('released').text)
            text_list.append(r.find('death').text)

        if len(text_list) == 8:
            # CSV に書き込み
            output_string = text_list[0] \
                            + "," + text_list[1] \
                            + "," + text_list[2] \
                            + "," + text_list[3] \
                            + "," + text_list[4] \
                            + "," + text_list[5] \
                            + "," + text_list[6] \
                            + "," + text_list[7] + "\n"

            f.write(output_string)

            # リストをクリア
            text_list = []

    f.close

if __name__ == '__main__':
    xml2csv()

結果

想定通りの形で CSV が出力されました。

f:id:sanvarie:20200411150128p:plain

さいごに

今回はさくっと終わらせることができました。前回のエントリーで CSV→XML、今回のエントリーでXML→CSV に変換する方法を紹介しました。これで相互変換が必要になっても大丈夫かと思います。前回も使用しましたが、今回も ElementTree を使用しています。Python で XML を扱う際はよく使用するライブラリかと思いますので、ぜひ色々使ってみてください。今回は以上です。