2019年 5 月 22 日 (水) ~ 24 日 (金)、東京ミッドタウン 六本木にて第15回GIS コミュニティフォーラム及びプレフォーラム・セミナーが開催されました!
公開可能なセッションの発表資料は ESRIジャパンのサポートサイトで公開しております。また、GeoNetブログでは開発者向けのセッションで使用したコードなどを順次ご紹介していきます。発表資料や GeoNet記事、GitHub へのリンクなどの詳細は第 15 回 GIS コミュニティフォーラム開催報告記事をご覧ください。
この記事は「ArcGIS ユーザーのためのデータハンドリング ~さまざまなデータ活用の第一歩~」セッションのスピンオフです。
本編ではいくつかのデータを例に Python を活用したデータハンドリングの手法をご紹介しました。今回のブログでは、その中の、「ひと手間必要なデータ④」でご紹介した、気象庁が提供している XML電文 (震源・震度に関する情報) と予報区の GIS データをハンドリングしてマップに可視化する手法に、本編ではご紹介しきれなかった、 Web スクレイピング処理を加えた、一連の処理をご紹介します。
今回は、Python を使って気象庁が提供している XML 電文を Web スクレイピング処理によって抽出、成型し、「予報区のGISデータ」から作成するポリゴン フィーチャと結合します。そして地震発生時点のフィーチャ クラスとして保存します。
次の流れでコードを解説します。
Tips:スクレイピング実施時の注意について
Web ページ上の情報には著作権や利用規約が存在します。
また、過度なアクセスは Web サーバーに負荷をかけてしまうことになります。
スクレイピングを行う際は、あらかじめサービス側の規約で許可されているか確認の上ご対応お願いいたします。
まずは処理を実行するために必要なパッケージのインポートと変数を定義します。
本編で説明した、XML の操作を行うために必要な「xml.etree.ElementTree」と GIS データを操作するために必要な「arcpy」に加え、本編では使用しなかった「urllib.request」をインポートします。Urllib.request は URL への接続、ファイルの読込みに使用します。
import xml.etree.ElementTree as ET import urllib.request import arcpy WORK_SPACE = r"D:\data\DemoProject\DemoProject.gdb" SHAPE_FILE = r"D:\data\DemoProject\20190125_AreaForecastLocalE_GIS\地震情報/細分区域.shp"
そして、「urllib.request」の機能を使用して 指定した URL (http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml) から XML データを取得し、「xml.etree.ElementTree」の機能を使用して 取得した情報から title タグが「震源・震度に関する情報」となっている情報のみを再抽出しています。
def load_xml(): """ 気象庁のサイトよりXMLの情報を取得する """ url = 'http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml' xml_string = download(url) elem = ET.fromstring(xml_string) target_dict = {} atom_ns = 'http://www.w3.org/2005/Atom' for e in elem.findall('.//{%s}entry' % atom_ns): s_title = e.find('./{%s}title' % atom_ns).text s_updated = e.find('./{%s}updated' % atom_ns).text s_url = e.find('./{%s}link' % atom_ns).attrib s_content = e.find('./{%s}content' % atom_ns).text if s_title=='震源・震度に関する情報': target_dict[s_content] = [s_url, s_updated] # コンテンツごとのURLを作成 for key in target_dict.keys(): d = target_dict[key] _url = d[0]['href'] _xml_string = download(_url) #XML読込 read_xml(_xml_string) def download(url): """ URL で指定したXMLを取得する """ req = urllib.request.Request(url) with urllib.request.urlopen(req) as response: xml_string = response.read() return xml_string
Tips:XML の名前空間について
コード上で指定している「atom_ns = 'http://www.w3.org/2005/Atom'」はXML の名前空間です。XML はタグを独自に作成することが魅力の一つですが、同一名のタグを作成することもできてしまうため、重複することもあります。そのため、XML には"名前空間"という仕組みがあり、同一のタグ名でも名前空間が異なれば異なる要素として取り扱うことができます。
XML ファイルから「地震発生時刻」と「震度」、「エリアコード」の情報を抽出しています。そして、「arcpy」の機能を使い、ジオプロセシングツールを使用して予報区の GIS データ (地震情報/細分区域.shp) から抽出した「地震発生時刻」の値を名称にポリゴン フィーチャを作成しています。
def read_xml(_xml_): """ XMLをパースします。Area要素内のCodeがフィーチャクラスに存在する場合、 MaxIntを使用してフィーチャクラスの震度カラムを更新します。 """ elem = ET.fromstring(_xml_) for elem in elem.iter(): if elem.tag == "{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}OriginTime": s_originTime = elem.text #地震発生時刻を取得 #フィーチャクラスの名前作成 outfc = arcpy.ValidateTableName("地震情報_" + s_originTime) #フィーチャクラスの作成 create_flg = create_feature(outfc) if create_flg == False: break if elem.tag == "{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}Pref": for el in list(elem): if el.tag == "{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}Area": for e in list(el): code = el.find("{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}Code") #コード取得 maxInt = el.find("{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}MaxInt") #震度取得 update(maxInt.text, code.text, outfc) #フィーチャクラスの更新 def create_feature(outfc): #ワークスペースにすでに同一名のフィーチャクラスがないかチェック if arcpy.Exists(outfc): return False #Shapeファイルをフィーチャクラスに変換 arcpy.CopyFeatures_management(SHAPE_FILE, outfc) #フィーチャクラスにカラム追加 arcpy.AddField_management(outfc, "震度", "Text") #座標系変換 arcpy.DefineProjection_management(outfc, arcpy.SpatialReference(4612)) return True
処理2)で抽出した「震度」と「エリアコード」の情報を「arcpy」のカーソル機能を使用して、ポリゴン フィーチャがもつエリアコードと紐づけを行い、該当するフィーチャに対して震度の情報を更新しています。
def update(maxInt, code, outfc): """ フィーチャクラスを更新します """ with arcpy.da.UpdateCursor(outfc,"震度", "code = %s" % "'" + code + "'") as cursor: for row in cursor: row[0] = maxInt #震度カラムを更新 cursor.updateRow(row)
これまで説明したコードを一つにまとめると以下のようなコードになります。
変数に指定しているデータをご自身の環境に合わせて変更していただければそのまま使用できます。
import xml.etree.ElementTree as ET import urllib.request import arcpy WORK_SPACE = r"D:\data\DemoProject\DemoProject.gdb" SHAPE_FILE = r"D:\data\DemoProject\20190125_AreaForecastLocalE_GIS\地震情報/細分区域.shp" def read_xml(_xml_): """ XMLをパースします。Area要素内のCodeがフィーチャクラスに存在する場合、 MaxIntを使用してフィーチャクラスの震度カラムを更新します。 """ elem = ET.fromstring(_xml_) for elem in elem.iter(): if elem.tag == "{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}OriginTime": s_originTime = elem.text #地震発生時刻を取得 #フィーチャクラスの名前作成 outfc = arcpy.ValidateTableName("地震情報_" + s_originTime) #フィーチャクラスの作成 create_flg = create_feature(outfc) if create_flg == False: break if elem.tag == "{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}Pref": for el in list(elem): if el.tag == "{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}Area": for e in list(el): code = el.find("{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}Code") #コード取得 maxInt = el.find("{http://xml.kishou.go.jp/jmaxml1/body/seismology1/}MaxInt") #震度取得 update(maxInt.text, code.text, outfc) #フィーチャクラスの更新 def create_feature(outfc): #ワークスペースにすでに同一名のフィーチャクラスがないかチェック if arcpy.Exists(outfc): return False #Shapeファイルをフィーチャクラスに変換 arcpy.CopyFeatures_management(SHAPE_FILE, outfc) #フィーチャクラスにカラム追加 arcpy.AddField_management(outfc, "震度", "Text") #座標系変換 arcpy.DefineProjection_management(outfc, arcpy.SpatialReference(4612)) return True def update(maxInt, code, outfc): """ フィーチャクラスを更新します """ with arcpy.da.UpdateCursor(outfc, "震度", "code = %s" % "'" + code + "'") as cursor: for row in cursor: row[0] = maxInt #震度カラムを更新 cursor.updateRow(row) def load_xml(): """ 気象庁のサイトよりXMLの情報を取得する """ url = 'http://www.data.jma.go.jp/developer/xml/feed/eqvol.xml' xml_string = download(url) elem = ET.fromstring(xml_string) target_dict = {} atom_ns = 'http://www.w3.org/2005/Atom' for e in elem.findall('.//{%s}entry' % atom_ns): s_title = e.find('./{%s}title' % atom_ns).text s_updated = e.find('./{%s}updated' % atom_ns).text s_url = e.find('./{%s}link' % atom_ns).attrib s_content = e.find('./{%s}content' % atom_ns).text if s_title=='震源・震度に関する情報': target_dict[s_content] = [s_url, s_updated] # コンテンツごとのURLを作成 for key in target_dict.keys(): d = target_dict[key] _url = d[0]['href'] _xml_string = download(_url) #XML読込 read_xml(_xml_string) def download(url): """ URL で指定したXMLを取得する """ req = urllib.request.Request(url) with urllib.request.urlopen(req) as response: xml_string = response.read() return xml_string if __name__ == '__main__': #ワークスペース指定 arcpy.env.workspace = WORK_SPACE #XMLダウンロード load_xml()
コードを実行すると以下のような属性データを持つフィーチャ クラスのデータが作成されます。
今回のブログでは本編で語れなかった、スクレイピングの処理を追加したデータハンドリングをご紹介しました。
本記事でご紹介したコードは ESRIジャパンの GitHub でも公開しています。また、ハンドリングを行ったデータを使って Web GIS のデータに対する更新処理、タスクスケジューラーを使った定期更新の処理など、できることはたくさんあります。関連情報にも参考となる情報を記載いたしますので、ArcGIS を活用した、今後のデータハンドリングにぜひお役立てください。