2017-04-24

HTMLテーブルからのデータ抽出

「データセットの再編成 - 水系域別河川データセットの作成」「トポロジーに関するGISデータ品質の検査」で使用した国土数値情報「河川」データセットの全てのフィーチャー (河川端点、流路) は、属性として水系域コードを持っており、その値に基づいて水系域別に出力先ファイルを振り分けたり、特定の水系域に属するフィーチャーのみを抽出したりできました。

国土数値情報ダウンロードサービスサイトでは、水系域コードの一覧表 (コードと名称の対応表) を掲載したウェブページも公開されているので、そのソース (HTML文書) から表の内容を抽出すれば、水系域コードをキーとして水系域の名称も取得できるようになります。

FME 2016 以前でも、HTMLをXMLとして解析したり文字列処理を施したりすることによって、HTML文書から必要なデータを抽出することはできましたが、FME 2017 では、新たに HTMLExtractor トランスフォーマーが導入され、より簡単に、HTML文書から特定の要素またはその内容を抽出できるようになりました。

この HTMLExtractor を使い、HTML形式の水系域コード一覧表から各行の内容 (コードと名称) を抽出し、Excel形式のファイルに出力するワークスペース例を掲げます。

FME 2017.0.0.2 build 17280

ソースHTML文書の <table> 要素 (水系域コード一覧表が記述されている部分)
http://nlftp.mlit.go.jp/ksj/gml/codelist/WaterSystemCodeCd.html から引用 (2017-04-21)
<table border="1" cellspacing="0" cellpadding="5">
  <tr align="center">
    <td  bgcolor="#CCCCCC" width="100" ><b>コード</b></td>
    <td  bgcolor="#CCCCCC" width="300"><b>対応する内容</b></td>
      </tr>

<td>010001</td><td>ウエンナイ川</td></tr>
<tr>
<td>010002</td><td>声問川</td></tr>
<tr>
<td>010003</td><td>増幌川</td></tr>

(中略)

<tr>
<td>890918</td><td>大野川</td></tr>
<tr>
<td>890919</td><td>大分川</td></tr>
<tr>
<td>890920</td><td>山国川</td></tr>

 </table>

FMEワークスペース例





















Creator: フィーチャーを1個出力する。
HTTPCaller: 水系域コード一覧表を掲載しているHTML文書を取得する。
HTMLExtractor: HTML文書から全ての <tr> 要素 (表の行) を抽出してリスト属性に格納する。
AttributeKeeper: 不要な属性を削除する (リスト展開時の無用なデータコピーを防ぐため)。
ListExploder: リストを展開して行ごとのフィーチャーを作成する。
Tester: 最初の行 (列名) を除く。
StringSearcher: <td> 要素 (列) の値を抽出する (1行あたり2列)。
[XLSXW] ライター: Excel形式でファイルに出力する。


HTTPCaller は、フィーチャーを受け取ったときに Request URL パラメーターで指定されたURLに対してリクエストを発行し、ウェブサーバーからのレスポンスを格納した属性をフィーチャーに与えて出力します。水系域一覧表を掲載しているウェブページのURLを指定してGETメソッドのリクエストを発行すれば、レスポンスとして、そのURLのHTML文書全体が取得できます。

HTMLExtractor では、パラメーターを次のように設定することにより、取得したHTML文書から全ての <tr> 要素 (表の行) を抽出し、それらをリスト属性 _tr{} に格納しました。

HTMLExtractor パラメーター設定例




























_tr{} リストの各要素には、次のように2つの <td> 要素 (データ) を含む <tr> 要素 (行) が格納されます。

リスト要素値 = HTML文書から抽出された <tr> 要素
_tr{0}<tr align="center">
<td bgcolor="#CCCCCC" width="100"><b>コード</b></td>
<td bgcolor="#CCCCCC" width="300"><b>対応する内容</b></td>
</tr>
_tr{1}<tr>
<td>010002</td><td>声問川</td></tr>
_tr{2}<tr>
<td>010003</td><td>増幌川</td></tr>
(中略)

_tr{5473}<tr>
<td>890918</td><td>大野川</td></tr>
_tr{5474}<tr>
<td>890919</td><td>大分川</td></tr>
_tr{5475}<tr>
<td>890920</td><td>山国川</td></tr>
注: 水系域コード 010001「ウエンナイ川」の行が _tr{} リストに格納されませんでしたが、それは、ソースHTML文書において、当該水系域の行のための <tr> タグが欠落していたためです (2017-04-21時点)。ウェブブラウザは、この程度の欠落は自動的に補ったうえで画面表示しますが、HTMLExtractor には、そのような補完機能はありません。完全な一覧表を得るには、<tr> タグを補って修正したHTML文書を使うか、あるいは、 <tr> 要素でなく <td> 要素を抽出する (コード、名称を交互にひとつのリストに格納する) などの別のアプローチを考える必要があります。


AttributeKeeper で不要な属性を削除した後、ListExploder で _tr{} リストを展開すると、その要素数と同じ数だけフィーチャーがコピーされるとともに、リストの各要素の値がコピーされたフィーチャーの _tr 属性 (リスト名から {} を除いた名前) に配分されます。また、各フィーチャーには、元のリストにおける各要素のインデクス (0 から始まる連番) を格納した属性も付加されます。

Tester で先頭の行 (ウェブブラウザ画面上での列名表示用、リストの要素インデクス = 0) を除外してから、残りの全ての行について StringSearcher によって列 (<td> 要素 x 2) の内容 (コード、名称) を抽出しました。

StringSearcher パラメーター設定例























主要なパラメーターは、次の三つです。

  • Search In (元の文字列を格納している属性): _tr
  • Contains Regular Expression (抽出したい部分文字列に一致する正規表現): <td>(.+?)</td>
  • Subexpression Matches List Name (部分式に一致した部分文字列と位置を格納するリスト名): _td

_tr 属性の値 (<tr> 要素) には、Contains Regular Expression パラメーターに設定した正規表現に一致する部分文字列 (<td> 要素) が2つ含まれており、部分式 (正規表現内の丸カッコで囲んだ部分) に一致する部分文字列 (<td> 要素の内容) とその位置 (元の文字列の先頭を 0 とする部分文字列先頭文字のインデクス) が _td リスト (_td{}.part, _td{}.startIndex) に格納されます。つまり、水系域コードと名称、および、それらの位置 が次の要素に格納されることになります。

  • _td{0}.part: 水系域コード (正規表現の部分式に一致した部分文字列)
  • _td{0}.startIndex: 元の文字列における水系域コードの先頭の文字の位置
  • _td{1}.part: 水系域名称 (正規表現の部分式に一致した部分文字列)
  • _td{1}.startIndex: 元の文字列における水系域名称の先頭の文字の位置

これらのうち _td{0}.part_td{1}.part を StringSearcher のインターフェース上に現し (脚注1参照) 、それぞれ、Excelライターフィーチャータイプの対応する属性に接続 (マッピング) すれば、完成です。

実行結果 (Excelスプレッドシート)



注1: Workbench キャンバス上のトランスフォーマーのインターフェースに表示されているリスト属性名の右クリックメニュー > Expose Elements (リストの要素を現す) から Select List Elements (リスト要素の選択) 画面を開き、インデクスを指定 (単独、最初と最後のインデクスをハイフンで連結した範囲、それらのカンマ区切りによる複数指定可) することによって、任意の要素をインターフェースに現すことができます (下図参照)。















注2: ListExploder によってフィーチャーに配分される _tr 属性の値 (HTML <tr> 要素) は、XML断片 (XML fragment) として扱うことができるので、StringSearcher の代わりに XMLFlattener によって <td> 要素の内容を抽出することもできます。その場合に作成されるリスト属性名はHTML (XML) 要素名と同じ td{} となり、水系域コードと名称は、次の要素に格納されます。
  • td{0}: 水系域コード
  • td{1}: 水系域名称

XMLFlattener パラメーター設定例





















2017-04-20

トポロジーに関するGISデータ品質の検査

トポロジー (topology) とは、元々は日本語で「位相幾何学」と呼ばれる数学の一分野のことですが、GISの世界では、要素間の空間的な関係 (接する、含む、含まれる等) が定義されている幾何図形の集合について、個々の要素の位置や形状が変化しても要素間の空間的な関係は保たれるという性質、あるいは、そのような性質を持つデータ構造をトポロジー (構造) と呼ぶことが一般的なようです。

道路網、水道やガスの管網、河川の水系など、ノード (節点)、および両端点でノードと接するエッジ (辺) で構成されるネットワークとしてモデル化できる地物の集合を表すGISデータセットは、トポロジーを持つことが期待されるものの典型であると言えます。

GISデータセットの品質としてトポロジーを持つことが要求される場合、その作成過程では、地物間の空間的な関係が保たれるよう、適切な基準、手法によって品質の評価、およびその結果に基づく品質の管理を行う必要があり、FMEはそのためのツールとしても有用であると考えられます。

以下、FMEによってトポロジーに関する品質の評価が容易に行えることを示すため、国土数値情報「河川」データ (Shapefile形式 Point: 河川端点, Line: 流路) からひとつの水系に属する河川端点と流路を抽出し、それらについて次の事項を検査するワークスペース例を掲げます。

1. 河川端点の集合と流路の端点の集合の一致
同一水系に属する河川端点の集合と流路の端点 (流路によって構成されるネットワークのノード) の集合は一致しなければならない。
--> ChangeDetector によって一方の集合にしか含まれない要素を検出し、検出数が 0 ならばOK。

2. 流路の連続性
同一水系に属する全ての流路は、単一の連結したネットワークを構成しなければならない。
--> NetworkTopologyCalculator によって連結範囲ごとに流路をグループ化し、グループ数が 1 ならばOK。

これらはFMEワークスペース例を作成するために設定した架空の品質要求とその評価手法です。実際のデータ品質に関する仕様は「国土数値情報(河川)製品仕様書 第3.1版」に記載されています。

FME 2017.0.0.2 build 17280

FMEワークスペース例













[ESRISHAPE] リーダー: 検査対象水系に関係する都道府県の国土数値情報「河川」データセットを読み込む
Tester: 水系域コードによって検査対象水系に属するフィーチャーのみ抽出
GeometryFilter: ジオメトリタイプによって河川端点 (Point) と流路 (Line) に振り分ける
Matcher: 河川端点の重複を解消
TopologyBuilder: 流路ネットワークのノードとエッジを出力
ChangeDetector: 河川端点と流路端点 (ネットワークのノード) の相違を抽出
NetworkTopologyCalculator: 流路ネットワークが連結している範囲を判定
Aggregator: 連結範囲ごとのグループで流路を集約 (Line -> MultiLine)


国土数値情報「河川」データ (河川端点、流路) は都道府県単位で提供されているため、[ESRISHAPE] リーダーでは、検査対象水系に関係する全ての都道府県のデータセットからフィーチャーを読み込みます。

全ての河川端点、流路フィーチャーは、水系域コードを属性として持っているので、Tester によってその属性の値が検査対象水系の水系域コードに等しいフィーチャーのみを抽出したうえで、GeometryFilter によって河川端点 (Point) と流路 (Line) に振り分けました。

複数の都府県にまたがる流路は都府県境界で分割され、分割点に該当する河川端点は両都府県のデータセットに格納されています。選択した水系が複数の都府県にまたがる場合、県境上の河川端点が重複してしまうので、Matcher によって重複を解消しました。同じ位置に複数の河川端点があった場合、そのうちひとつだけが SingleMatched ポートから出力されます。


TopologyBuilder にラインジオメトリを持つフィーチャーを入力すると、ライン端点間の空間的関係 (接する) に基づくネットワークが構成され、相互の接続先を特定するための属性を持ったノード (ポイント)、エッジ (ライン) が、それぞれ Node ポート、Edge ポートから出力されます。流路を入力すれば、全ての流路の端点 (重複はない) が Node ポート、全ての流路が Edge ポートから出力されることになります。

注: TopologyBuilder の Assume Clean Data (入力データはクリーンである) パラメーターのデフォルト値は No であり、そのままだと、入力ラインが交差していた場合に交点で分割され、その交点も Node ポートから出力されます。しかし、ここでは「河川端点」と流路ネットワークのノードが一致するかどうかを検査するのが目的なので、同パラメーターに Yes を設定し、流路ライン間の交差があったとしても、それらを分割しない (余分な交点ノードを発生させない) ようにしました。


ChangeDetector の Original ポートに河川端点 (重複解消後)、Revised ポートに流路ネットワークのノードを入力すると、それらの間の一致判定の結果に基づいて、出力ポート別に次のフィーチャーが出力されます。

出力ポート出力フィーチャー
Unchanged一致する Revised フィーチャーがあった Original フィーチャー
Added一致する Original フィーチャーがなかった Revised フィーチャー
Deleted一致する Revised フィーチャーがなかった Original フィーチャー

注: この例の Matcher, ChangeDetector ではジオメトリのみの一致判定を行っていますが、パラメーターの設定によって、ジオメトリと属性の一致判定、属性のみの一致判定を行うこともできます。


NetworkTopologyCalculator は、入力ラインの端点間の接続関係に基づいてネットワークが連結している範囲を判定し、連結範囲ごとにユニークなネットワークID (連番) を定義し、各範囲に属する全てのラインに属性として付加します。次の Aggregator では、Group By パラメーターにネットワークID属性を指定することにより、連結範囲ごとに集約しました。


那珂川水系 (水系域コード: 830302) についての実行結果
入力データ: 国土数値情報「河川」福島県, 茨城県, 栃木県
FME Data Inspector での表示。背景は地理院タイル「白地図 (Outline)」
流路ネットワークの連結範囲 (グループ) ごとに異なる色で表示しています。























福島県と栃木県の県境付近 (背景非表示)
印 (3箇所) は、ChangeDetector の Added ポートから出力されたフィーチャー、つまり「一致する河川端点がなかった流路ノード」を示しています。
県境に沿っている流路は、県境をまたぐたびに不連続になっているようです。

















2017-04-07

メッシュデータのラスター化 - 土地利用細分メッシュ [更新]

「メッシュデータのラスター化 - 土地利用細分メッシュ」(2015-07-18, 以下[初版]と言います) で、国土数値情報「土地利用細分メッシュ」データをラスターに変換するワークスペース例を紹介しました。

[初版]のワークスペースは、Shapefile形式のデータから読み込んだ1/10細分メッシュ区画の矩形2Dポリゴン (1次メッシュ1区画あたり64万個) について、それらが属性として持っている土地利用種別コードに対応する画素値をZ座標とする3Dポリゴンに変換し、さらに、それらに基づいて NumericRasterizer によってラスターを作成するものでした。

現時点のFME最新バージョン - FME 2017 でも[初版]のワークスペース (FME 2015 で作成) は実行できますが、FME 2017 で Python FME Objects API にラスター処理関係の機能が追加されたことから、数十行程度の小さな Python スクリプトによって、XML形式の土地利用細分メッシュデータから抽出した土地利用種別コードのリスト (1次メッシュ1区画あたり800列 x 800行 = 64万コード, スペース/改行区切り文字列) に基づいてラスターを作成することも可能になりました。

土地利用細分メッシュXMLからラスターへの変換は、いくつかのトランスフォーマーの組み合わせでもできることですが、Python スクリプトを使うことによって格段に性能が良くなります。以下、FME 2017 の新機能を利用したアップグレード版として紹介します。

[2017-06-15] 2017-06-02 から、国土数値情報ダウンロードサイトで「土地利用細分メッシュ(ラスタ版)」データ (平成26年度 GeoTIFF形式, JGD2000) がダウンロードできるようになりました。「土地利用細分メッシュ (ラスター) データセットの作成」(2017-06-10) もご参照ください。

FME 2017.0.0.2 build 17280

FMEワークスペース例











[XML] リーダー: 国土数値情報「土地利用細分メッシュ」XMLデータを読み込む
AttributeManager: 一部の属性名の変更、不要な属性の削除

[CSV2] リーダー: 土地利用種別コードと画素値の対応表を読み込む
ListBuilder: 土地利用種別コードと画素値の全てのペアをひとつのリスト属性に格納する

FeatuerMerger リスト属性を土地利用細分メッシュフィーチャーに結合する
PythonCaller: Python スクリプトにより、ラスター (8ビット符号なし整数1バンド) に変換する
AttributeFileReader: パレットの定義 (画素値 - RGBの対応) を記述したテキストを読み込む
RasterPaletteAdder: パレットをラスターに設定する
[PNGRASTER] ライター: PNG形式のファイルに出力する


国土数値情報「土地利用細分メッシュ」(平成26年) 1次メッシュ区画 5339, 5340 の変換結果
FME Data Inspector による表示 (背景は地理院地図の淡色地図)
FME 2017.0 では、Data Inspector のビューの背景 (Background Map) として、地理院地図 (Japan GSI Maps) も表示できるようになりました。
















[XML] リーダーでは、土地利用細分メッシュXMLの ksj:Dataset/ksj:LanduseSubdivisionMesh 要素をフィーチャータイプとして読み込むことにしました。ワークスペースにリーダーを追加するときに、次のようにパラメーターを設定します。

  • Configuration Type: Feature Paths
  • Elements to Match: Dataset/LanduseSubdivisionMesh
  • Flatten Options: [Options...] > Enable Flattening チェックボックスをチェック

[XML] リーダーパラメーター設定画面



























平坦化 (Flatten) オプションを有効にすると、ksj:LanduseSubdivisionMesh 要素に属する全てのXMLノード (要素、属性) が平坦化され、フィーチャー属性として読み込まれます。個々のXML要素の値を格納するフィーチャー属性の名前は、LanduseSubdivisionMesh より下位の XPath (XMLドキュメントツリーにおける上位から下位の順でノード名を / 区切りで連結した文字列) から名前空間接頭辞を除き、ドット区切りに置き換えたものになります。

例えば、XPath (ドキュメントルートからのフルパス)
ksj:Dataset/ksj:LanduseSubdivisionMesh/ksj:coverage/gml:boundedBy/gml:Envelope/gml:lowerCorner
で指定されるXML要素の内容 (値) を格納するフィーチャー属性名は、
coverage.boundedBy.Envelope.lowerCorner
になります。「XMLの読込 - 断片化と平坦化」もご参照ください。

AttributeManager では、以後の処理に必要な属性だけ残す (他を削除する) とともに、属性名を変更して簡素にしました。これは、必須のことではありません。

土地利用細分メッシュラスター作成用の属性
変更前の属性名変更後内容
coverage.boundedBy.Envelope.lowerCornerlowerCorner1次メッシュ区画南西隅の座標 (緯度 経度)
coverage.boundedBy.Envelope.upperCornerupperCorner1次メッシュ区画北東隅の座標 (緯度 経度)
coverage.rangeSet.DataBlock.tupleListtupleList土地利用種別コードのリスト (800列 x 800行)
meshcodemeshcode1次メッシュコード

[CSV2] リーダーで、土地利用細分メッシュデータ (平成21, 26年) で使用されている土地利用種別コード (文字列) とラスター画素値 (整数) の対応表を読み込みました。対応表の内容は[初版]で使用したものと同じです。

注: FME 2017 では CSV リーダー/ライターもアップグレードしました。新バージョンの CSV リーダー/ライター用のフォーマット識別子は CSV2 です。

土地利用種別コード-画素値.csv
コード,土地利用種別,画素値
0100,田,1
0200,その他の農用地,2
0500,森林,3
0600,荒地,4
0700,建物用地,5
0901,道路,6
0902,鉄道,7
1000,その他の用地,8
1100,河川地及び湖沼,9
1400,海浜,10
1500,海水域,11
1600,ゴルフ場,12
0000,解析範囲外,0


次の ListBuilder によって、土地利用種別コードと画素値の全てのペアをひとつのリスト属性に格納したうえで、FeatureMerger によって、そのリスト属性を LanduseSubdivisionMesh フィーチャーに結合しました。このリスト属性を参照することにより、XMLから読み込まれた土地利用種別コードをラスター画素値に置き換えることができます。

PythonCaller は、ワークスペースの実行時、フィーチャーを受け取るたびに Python スクリプトを実行して入力フィーチャーに何らかの変更 (ジオメトリや属性の追加、削除、変更等) を施してから出力します。また、必要に応じて、新たなフィーチャーを生成したり、入力フィーチャーを削除したりするようなスクリプトを設定することもできます。

PythonCaller パラメーター設定画面





















実行すべきスクリプト (クラスまたは関数) を Python Script パラメーターに、そのクラス名/関数名を Class or Function to Process Features パラメーターに設定します。スクリプトで Python FME Objects API を利用する場合は、fmeobjects モジュールをインポートします。

詳細については次のページを参照してください。

このワークスペース例の PythonCaller に設定したスクリプト (全文) は、次のとおりです。

import fmeobjects

class MyUInt8BandTilePopulator(fmeobjects.FMEBandTilePopulator):
    def __init__(self, dataArray):
        self.dataArray = dataArray
        
    def clone(self):
        return MyUInt8BandTilePopulator(self.dataArray)
    
    def getTile(self, startRow, startCol, tile):
        numRows, numCols = tile.getNumRows(), tile.getNumCols()
        endRow, endCol = startRow + numRows, startCol + numCols
        data = [self.dataArray[r][startCol:endCol] for r in range(startRow, endRow)]
        newTile = fmeobjects.FMEUInt8Tile(numRows, numCols)
        newTile.setData(data)
        return newTile
        
class KsjLanduseSubdivisionMeshRasterizer(object):
    def __init__(self):
        # Nodata値設定用タイル
        self.nodata = fmeobjects.FMEUInt8Tile(1, 1)
        self.nodata.setData([[0]])
        
    def input(self,feature):
        # 土地利用種別コードから画素値への変換用辞書
        codes = feature.getAttribute('_list{}.コード')
        values = feature.getAttribute('_list{}.画素値')
        codeToValue = {c:int(v) for c, v in zip(codes, values)}
        
        # 土地利用種別コードリストを取得し, 画素値データ配列に変換
        tupleList = feature.getAttribute('tupleList')
        data = [codeToValue.get(c, 0) for c in tupleList.split()]
        dataArray = [data[i:i+800] for i in range(0, 640000, 800)]
        
        # 範囲の最小/最大座標
        lower = feature.getAttribute('lowerCorner').split()
        upper = feature.getAttribute('upperCorner').split()
        xmin, ymin = float(lower[1]), float(lower[0])
        xmax, ymax = float(upper[1]), float(upper[0])

        # ラスターオブジェクト作成 (解像度は 800x800 pixels 固定)
        rasterProperties = fmeobjects.FMERasterProperties(800, 800,
            (xmax-xmin)/800, (ymax-ymin)/800,
            0.5, 0.5, xmin, ymax, 0.0, 0.0)
        raster = fmeobjects.FMERaster(rasterProperties)
        
        # バンドオブジェクト作成, ラスターに追加
        bandTilePopulator = MyUInt8BandTilePopulator(dataArray)
        bandProperties = fmeobjects.FMEBandProperties('Landuse',
            fmeobjects.FME_INTERPRETATION_UINT8,
            fmeobjects.FME_TILE_TYPE_FIXED, 800, 800)            
        band = fmeobjects.FMEBand(bandTilePopulator,
            rasterProperties, bandProperties, self.nodata)
        raster.appendBand(band)

        # フィーチャーにラスターを設定して出力        
        feature.setGeometry(raster)
        self.pyoutput(feature)


このスクリプトで作成されるラスターは、8ビット符号なし整数1バンドの数値ラスターですが、次のようなパレットを設定することにより、カラー表示が可能になります。AttributeFileReader と RasterPaletteAdder によるパレットの設定方法は[初版]と同じです。

土地利用ラスターパレット.txt
RGB24
0 0,0,0
1 85,170,0
2 255,255,0
3 0,100,0
4 170,85,0
5 255,170,0
6 170,170,170
7 170,0,170
8 170,170,0
9 0,170,255
10 255,170,127
11 0,0,255
12 170,255,127

[PNGRASTER] ライターフィーチャータイプでは、meshcode 属性の値をフィーチャータイプ名 (Raster File Name) に設定することにより、出力先ファイル名 = 1次メッシュコード (4桁数字) としました。[PNGRASTER] ライターは、デフォルトで自動的にワールドファイルも作成します。


多くの場合、標準のトランスフォーマーの組み合わせによって要件を満たしたワークスペースを作成できるので、FMEを利用するうえで Python プログラミングは必須ではありません。

しかし、一部のプロセスをスクリプトに置き換えれば格段に性能が良くなることはあり、また、標準のトランスフォーマーだけではワークフローが極めて大規模、複雑になることもあります。そのような場合に、実行時の性能が良く、かつ、簡潔なワークスペースとするためにスクリプトを組み込むのは効果的です。

土地利用細分メッシュXMLからラスターへの変換は、土地利用種別コードのリストに基づいてラスターを作成するプロセスをスクリプトに置き換えることによって、格段に性能が良くなりました。

多くのソフトウェアで、ユーザーによるカスタマイズやプラグインの開発を行うためのプログラム言語として Python が採用されており、そのようなソフトウェアのユーザーは、これまでに培ってきた Python スキルを FME ワークスペース開発に生かすことができます。また逆に、FME ユーザーはワークスペース開発で培ったスキルを他のソフトウェアを活用するために生かせます。どこから始めるにしても、Python を学んで損することはないと思います。まだ Python を使ったことがない方は是非、チャレンジしてください。

2017-04-04

PNG標高タイルからDEMラスターへの変換

ラスター画素値演算の例 - PNG標高タイル方式による基盤地図情報DEMの表現」でRGB画像ラスターによるDEM - 数値標高モデルの表現方法を取り上げましたが、それと同じ方式で作成された基盤地図情報数値標高モデルのPNG標高タイルが公開されています。

国土地理院 | 地理院タイル一覧 | 標高タイル(基盤地図情報数値標高モデル)

前回は、DEMラスターをPNG標高タイル方式のRGB画像ラスターに変換しましたが、今回はその逆に、地理院地図のPNG標高タイルをDEMラスターに変換してみます。どちらも RasterExpressionEvaluator による画素値演算の分かりやすい例だと思います。

FME 2017.0.0.1 build 17271

FMEワークスペース例

















JpGsiTileFetcher (カスタムトランスフォーマー): PNG標高タイルを取得
RasterExpressionEvaluator: PNG標高タイル (RGB24) をUInt64ラスターに変換
RasterExpressionEvaluator_2: UInt64ラスターをDEMラスター (Real64) に変換
RasterBandNodataSetter: Nodata値 (-9999) を設定
RasterMosaicker: 複数のタイルラスターを結合

JpGsiTileFetcher を使うと、ワークスペースの実行時に、入力フィーチャーのジオメトリをカバーする範囲について、パラメーターで指定したズームレベル、種類の地理院タイルデータを取得することができます。このカスタムトランスフォーマーは、次のページで公開しています。

FMEサポート | 国内データ変換のサポート

このカスタムトランスフォーマーのパラメーターを次のように設定すれば、入力フィーチャーのジオメトリをカバーする範囲のズームレベル 14 のPNG標高タイルが取得できます。もちろん、実行時にはインターネットに接続していなければならず、また、入力フィーチャーには座標系が適切に設定されている必要があります。

  • GSI Tile Data ID: dem_png ※基盤地図情報DEM10B PNG標高タイルのデータID
  • Format Extension: png
  • Zoom Level: 14

三宅島の区域を例として、上記ワークスペースによってPNG標高タイルを取得、DEMラスターに変換した結果は次のとおりです。

左: 入力フィーチャーのジオメトリ (三宅島の区域), 中: PNG標高タイル, 右: DEMラスター
掲載用に画像サイズを縮小したので、解像度は実際のPNG標高タイルやDEMラスターとは異なります。












地理院地図 | 標高タイルの詳細仕様によれば、PNG標高タイルの画素値 (RGB値) から標高値 h (m) への換算式は次のとおりです。

x = 216R + 28G + B ... (1)
x < 223 の場合 h = xu ... (2)
x = 223 の場合 h = NA ... (3)
x > 223 の場合 h = (x-224)u ... (4)
u: 標高分解能 (0.01m)

RasterExpressoinEvaluator パラメーター
InterpretationExpression
UInt64 @pow(2,16)*A[0] + @pow(2,8)*A[1] + A[2]

最初の RasterExpressionEvaluator では、PNG標高タイル = 8ビット符号なし整数 x 3バンド (R, G, B) ラスター を、(1) 式によって求めた x を画素値とする UInt64 (64ビット符号なし整数) x 1 バンドラスターに変換しました。

RasterExpressionEvaluator_2 パラメーター
InterpretationExpression
Real64 @if(A[0]==@pow(2,23), -9999.0, @if(A[0]<@pow(2,23), A[0]*0.01, (A[0]-@pow(2,24))*0.01))

2番目の RasterExpressionEvaluator では、上記変換後の UInt64 ラスター (画素値 = x) に基づき、(2), (3), (4) 式によって標高値を求め、Real64 (64ビット浮動小数点数) x 1 バンドラスターに変換しました。(3) 式の NA は値がないことを意味していますが、ラスターの画素にはなんらかの値を設定する必要があるので、標高としてあり得ない値: -9999 を設定しました。この値は、後で RasterBandNodataSetter によって Nodata値として取り扱うように設定します。

式 (Expression) 中の A[n] は、入力ラスターの n 番目 (n は 0 から始まる連番) のバンドの画素値を示します。最初の RasterExpressionEvaluator の入力ラスターはRGB3バンドであり、式中の A[0], A[1], A[2] が R, G, B の値を示しています。2番目の RasterExpressionEvaluator の入力ラスターは (1) 式で求めた x の値を格納している 1 バンドラスターなので、A[0] (x の値) のみを使用しています。

@pow(a, b) 関数は a の b 乗を返し、@if(a, b, c) 関数は a が真のときは b、偽のときは c を返します。換算式と対比し易いように、2 の累乗を行うのに @pow 関数を使いましたが、2n の値を定数として式の中に記述しても構いません。例えば、@pow(2,16) は 65536、@pow(2,8) は 256 に置き換えられます。

RasterBandNodateSetter で -9999 を Nodata値として設定し、RasterMosaicker でタイルごとのラスターを結合してひとつのラスターに変換すればできあがりです。

ワークスペース例には示していませんが、DEMラスターを格納するのに適したフォーマット (GeoTIFF, ERDAS IMAGINE など) のライターを追加し、RasterMosaicker の後に接続すれば、結果をファイルに出力することができます。

2017-04-02

XML文書の更新 - 国土数値情報「行政区域」

XML文書から必要な情報を的確に抽出するには、何を、どの要素/属性で、どのような構造によって記述するかなどを定義した「応用スキーマ (Application Schema)」を参照する必要があります。通常、応用スキーマは 「○○仕様書」等の形態で文書化されており、また、XML文書の妥当性検証やデータ抽出等を自動化しやすいように、符号化規則をXML形式で記述した応用スキーマファイルが付属していることもあります。

応用スキーマは、特定の種類のデータセットをXML形式で記述する場合に従わなければならない仕様であり、あるXML文書についての応用スキーマが開示されなかったり、開示されたとしてもXML文書がその応用スキーマに適合していなかったりした場合には、一般にそのXML文書を正しく読むことはできません。

XML形式で提供するデータは、XMLの規格や応用スキーマに厳密に適合していなければなりませんが、その点でルーズであることにより、利用が難しいデータを見かけることもあります。

国土数値情報「行政区域」データにおけるXML要素名等は、応用スキーマ文書「国土数値情報(行政区域)製品仕様書 第 2.3 版」で定義されていますが、平成25年のデータにおける行政区域クラス、およびその属性を記述しているXML要素名はそれに適合していません(2017年4月2日にダウンロードした 平成25年 東京都 のデータで確認)。

クラス属性XML要素名 (製品仕様書2.3版)XML要素名 (平成25年データ)
行政区域AdministrativeBoundaryAdministrativeArea
範囲boundsare
都道府県名prefectureNameprn
支庁・振興局名subPrefectureNamesun
郡・政令都市名countyNamecon
市区町村名cityNamecn2
行政区域コードadministrativeAreaCodeaac

平成25年のデータ作成時の応用スキーマでは現在と異なる要素名が定義されていたのか、平成25年のデータが規格外なのか、あるいはその他の事情があるのか、理由は定かではありませんが、いずれにせよ、現在の製品仕様書に準拠して作成されたアプリケーションでこのデータを利用しようとするならば、XML要素名を変更する必要があります。

FMEでは、XML文書を処理するために多くのトランスフォーマーが用意されています。ここではそのひとつ、XMLUpdater トランスフォーマーを使い、国土数値情報「行政区域」の平成25年のデータ (XML文書) を、現在の製品仕様書に適合するように更新するワークスペース例を掲げます。

FME 2017.0.0.1 build 17271

FMEワークスペース例















[DATAFILE] リーダー: 国土数値情報「行政区域」データ (更新前XML文書) を読み込む
StringReplacer: 更新前XMLの誤記修正 (詳細は後述)
XMLUpdater: 行政区域の属性を記述しているXML要素名の変更
XMLFormatter: 更新後XMLの書式やエンコーディングの設定
[DATAFILE] ライター: 更新後XMLをファイルに出力

行政区域クラスXML要素の例
<!-- 更新前 -->
<ksj:AdministrativeArea gml:id="gy2">
    <ksj:are xlink:href="#sf2"/>
    <ksj:prn>東京都</ksj:prn>
    <ksj:sun></ksj:sun>
    <ksj:con>足立区</ksj:con>
    <ksj:cn2></ksj:cn2>
    <ksj:aac codeSpace="AdministrativeAreaCode.xml">13121</ksj:aac>
</ksj:AdministrativeArea>
<!-- 更新後 -->
<ksj:AdministrativeBoundary gml:id="gy2">
    <ksj:bounds xlink:href="#sf2"/>
    <ksj:prefectureName>東京都</ksj:prefectureName>
    <ksj:subPrefectureName/>
    <ksj:countyName>足立区</ksj:countyName>
    <ksj:cityName/>
    <ksj:administrativeAreaCode codeSpace="AdministrativeAreaCode.xml">13121</ksj:administrativeAreaCode>
</ ksj:AdministrativeBoundary>

[DATAFILE] リーダー/ライターによって、XMLを含む任意のフォーマットのファイルの読み書きができます。リーダーの Read Whole File at Once (ファイル全体を一度に読む) パラメーターを Yes に設定してファイルを読み込むと、そのファイルの内容 - この場合はXML文書の全体を格納した data_file_data 属性を持つフィーチャーが出力されます (1ファイルにつき1フィーチャー)。

このワークスペース例を作成している過程で、国土数値情報「行政区域」の平成25年のデータのルート要素に誤記があるのを見つけました。

<?xml version="1.0" encoding="UTF-8"?>
  <ksj:Dataset
  gml:id= "N03Dataset"
  xmlns:ksj="http://nlftp.mlit.go.jp/ksj/schemas/ksj-app"
  xmlns:gml="http://www.opengis.net/gml/3.2"
  xmlns:xlink="http://www.w3.org/1999/xlink"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:schemaLocation="http://nlftp.mlit.go.jp/ksj/schemas/ksj-app KsjAppSchema-N03-v2_0.xsd">

xmlns:<接頭辞>="<XML名前空間名>" は、文書内で使用するXML名前空間と、所属先の名前空間を識別するために要素名/属性名に付加する接頭辞を宣言する構文です。しかし、xmlns:schemaLocation="..." は、右辺の書式 "<URL><空白><応用スキーマファイル名>" から見て明らかにスキーマロケーションを示すことが意図されており、xsi:schemaLocation="..." とすべきところの誤記であると判断されます。

XMLの仕様に忠実なソフトウェアは、この誤記があることによってXML文書全体の読み込みを拒否することがあります。FMEがXML文書の更新等を行うために使用している XQuery プロセッサー (Zorba XQuery Processor) もこの誤記を許容しないので、まず、StringReplacer によって "xmlns:schemaLocation" を "xsi:schemaLocation" に置き換えました。

この修正を施したうえで XMLUpdater によって、行政区域の属性を記述するためのXML要素名を、現在の応用スキーマに準拠した名前に変更しました。

XMLUpdater には2つの入力ポート - Document と Update があり、更新したいXML文書を属性として持っているフィーチャーは Document ポートの方に入力します。

Update ポートから入力したフィーチャーの属性をXML更新用のパラメーター (Value) として使うこともできますが、この例では、その必要はありません。ただし、XMLUpdater を起動するには、少なくともひとつのフィーチャーを Update ポートに入力する必要があります。そのため、Creator を Update ポートを接続して、何も属性を持たないフィーチャーを1個だけ入力しています。

XMLUpdater パラメーター設定画面




























XMLUpdater で要素名を変更する場合は、パラメーター設定画面の Updates テーブルで、変更するノード (XML要素) ごとに次の事項を設定します。
  • Update Type: Rename (ノード名を変更) を選択
  • XML Path: 変更対象ノードのXML文書内での位置を示す XPath
  • Value Type: Plain Text (変更後のノード名を文字列で指定する場合)
  • Value: 変更後の文字列
このようにパラメーターを設定した XMLUpdater によってXML文書を更新することは、次のような XQuery 式を実行するのと同等です  (ワークスペースの実行時、FMEは XMLUpdater のパラメーター設定に基づいて XQuery 式を組み立てて実行します)。

declare namespace ksj="http://nlftp.mlit.go.jp/ksj/schemas/ksj-app";
for $x in //ksj:AdministrativeArea return rename node $x as 'ksj:AdministrativeBoundary',
for $x in //ksj:are return rename node $x as 'ksj:bounds',
for $x in //ksj:prn return rename node $x as 'ksj:prefectureName',
for $x in //ksj:sun return rename node $x as 'ksj:subPrefectureName',
for $x in //ksj:con return rename node $x as 'ksj:countyName',
for $x in //ksj:cn2 return rename node $x as 'ksj:cityName',
for $x in //ksj:aac return rename node $x as 'ksj:administrativeAreaCode'

注1: この例では、[DATAFILE] リーダーでXML文書を読み込み、それを属性として持っているフィーチャーを XMLUpdater の Document ポートから入力しましたが、XMLUpdater の XML Input パラメーターで "XML File" を選択することにより、XML File パラメーターでファイル名を指定してXML文書を直接読み込ませる方法もあります。その方が効率が良さそうですが、国土数値情報「行政区域」平成25年のデータには前述の誤記があり、それを修正しないと XMLUpdater はXML文書を解析できなかったため、採用できませんでした。

注2: XMLUpdater トランスフォーマーではノード名の変更のほかに、ノードの置換、ノードの内容の置換、ノードの削除、ノードの内容の削除、ノードの挿入を行うことができます。

注3: XMLXQueryUpdater トランスフォーマーでは XQuery 式を直接指定し、それに基づくXML更新を行うことができます。XMLUpdater では対応できない複雑で高度なXML更新も、XMLXQueryUpdater を使えば可能になる場合があります (本記事末尾の [補足] 参照)。


次の XMLFormatter では、必要に応じて、インデント数、余分な空白の取り扱い、値を持たない要素の記述方法、エンコーディングなど、XML文書の書式全般を設定できます。この例では更新後XMLのエンコーディングを UTF-8 に設定しましたが、更新前XMLのエンコーディングも UTF-8 なので、必須ではありません。

ただし、[DATAFILE] ライターフィーチャータイプでは Character Encoding パラメーターを明示的に UTF-8 に設定する必要があります。

[DATAFILE] ライターフィーチャータイプ




























補足: XMLXQueryUpdater (やや高度な XQuery 式) の例

結果をチェックしていて、国土数値情報「行政区域」では、東京23区名が countyName (郡・政令都市名) 要素に記述されていることに気がつきました。平成25年に限らずどの年のデータでもそのようなので、仕様上は間違いないのだとは思いますが、一般的には、どちらかと言えば「市区町村名」の範疇で取り扱う方が多いような気がします。

仮に、countyName 要素に区名 "○○区" が記述されている場合、それを cityName 要素に記述し、countyName 要素からは削除することが必要になったとすれば、XMLXQueryUpdater によって次のような XQuery 式を実行すれば良さそうです。

declare namespace ksj="http://nlftp.mlit.go.jp/ksj/schemas/ksj-app";
for $x in //ksj:countyName[fn:ends-with(text(), '区')]
return (replace value of node $x/../ksj:cityName with $x/text(),
    replace value of node $x with '')

  • 末尾が「区」である文字列 (区名) を内容とする全ての ksj:countyName 要素について、
  • その sibling (兄弟姉妹) 関係にある ksj:cityName 要素の内容をその区名で置き換え、
  • ksj:countyName 要素の内容は空文字列 '' で置き換える (つまり、内容を削除する)。