2017年12月21日木曜日

S-101 with FOSS4G (1) ISO8211の解析

これは「FOSS4G Advent Calendar 2017」の21日目の記事です。

はじめに

現在一般的に普及している航海用の電子海図は、国際水路機関(IHO)がS-57という名前で公開している規格に基づくものです。以前にも書いたとおり、S-57で作成されたファイルは、GDAL/OGRでベクターファイルとして読み込むことができ、QGISでも表示することができます。

このS-57に代わる新しい電子海図の規格として、IHOでは、S-101という仕様を作っています(なぜ新しい規格が必要なのか等の背景については、別の記事で書いていますので、そちらをご参照ください)。この記事の執筆時点では、S-101はまだ完成しておらず、IHOのタイムスケジュールによると2019年の後半に初版が出るものとみられます。
S-100系のタイムスケジュール(IHO HSSC9の資料より)

ということで、(まだ誰もデータを作ってないので当たり前といえば当たり前ですが、)今のところGISソフトでS-101データを読み込む仕組みは、私が知る限りはまだ存在しないので、とりあえずQGISでS-101を表示するくらいまではやってみたいなと思い始めました。

QGISでS-101を表示するまでの道のり

言ってはみたものの、ちょっと考えただけでも下記のことをやらなければならず、道のりはかなり長そうなので、まず今回は下記の1だけやってみることにします。
  1. PythonでS-101のデータをISO 8211構造として読めるようにする。 ←今回はこれ
  2. ISO 8211構造をS-100のGFMに近いモデルに変換する。
  3. S-101 Feature Catalogueを使ってenum値などを変換する。
  4. QGISのレイヤーに変換して表示するプラグインを作る。
  5. S-100特有の課題の解決(属性の階層構造、Information Type、各種Associationなど)
  6. (GDAL/OGRで直接扱えるようになるといいなあ)

環境

とりあえず次の環境でやりました。
  • Ubuntu 16.04 LTS (Windows Subsystem for LinuxでWindows 10の上にインストール)
  • Python 2.7.6

データの入手

どの国も正式なデータを作っていませんので、IHOが公開しているテストデータを使うしかありません(これも少し古いのですが、これしかないので仕方ありません)。
こちらからデータをダウンロードし、zipを展開して、適当なデータファイル(拡張子が.000のもの)を拾います。
https://www.iho.int/mtg_docs/com_wg/S-100WG/TSG3/S101TestDatasetsExchangeSet.zip

ファイルの解析

PythonでISO 8211のファイルを扱うライブラリがオープンソースで公開されているので、それを使わせていただくことにします。
https://github.com/freekvw/iso8211

このソースをgit cloneで取得して中を見てみると、コマンドラインツール(拡張子なしのiso8211という名前のファイル)が付属しています。どうやらこれで.000の中身を見られそうなので、まずはきちんと読めるかどうか確認してみます。
$ ./iso8211
Interactive command environment
(use exit or `EOF' to exit the environment)
 
iso 8211: file ../AADLULBD01.000
Opening DDF /home/xxxxxxxx/AADLULBD01.000
Warning: In field `CSID' the field controls `1100;&   ' indicate format `I'
         but the actual format controls are `(b11,b14,b11)'
(以下、Warningが大量にでます)
Warningが大量にでますが、読み込めてはいるようです。
中身を少し見てみます。
iso 8211: show record 1
Record 1 at offset 3025
    Leader
        record length              = 8928
        leader id                  = `D'
        base address of field data = 133
        entry map:
            size of field length   = 4
            size of field position = 4
            size of field tag      = 4
    Directory contains 9 entries
    Field 0
        tag  = 'DSID'
        data = b11   'RCNM': 'U:0x0a (10)'
               b14   'RCID': 'U:0x00000001 (1)'
               A     'ENSP': 'S-100 Part 10a'
               A     'ENED': '1.1'
               A     'PRSP': 'INT.IHO.S-101.1.0.0'
               A     'PRED': '1.0.0'
               A     'PROF': '1'
               A     'DSNM': 'AADLULBD01__.000'
               A     'DSTL': ''
               A(8)  'DSRD': '20150706'
               A     'DSLG': 'EN'
               A     'DSAB': ''
               A     'DSED': '1'
               b11   'DSTC': 'U:0x0e (14)'
               b11   'DSTC': 'U:0x12 (18)'
    Field 1
        tag  = 'DSSI'
        data = b48   'DCOX': 'F:0x0000000000000000'
               b48   'DCOY': 'F:0x0000000000000000'
               b48   'DCOZ': 'F:0x0000000000000000'
               b14   'CMFX': 'U:0x00989680 (10000000)'
               b14   'CMFY': 'U:0x00989680 (10000000)'
               b14   'CMFZ': 'U:0x00000064 (100)'
               b14   'NOIR': 'U:0x00000005 (5)'
               b14   'NOPN': 'U:0x000007d6 (2006)'
               b14   'NOMN': 'U:0x000004bc (1212)'
               b14   'NOCN': 'U:0x00000881 (2177)'
               b14   'NOXN': 'U:0x0000026b (619)'
               b14   'NOSN': 'U:0x00000084 (132)'
               b14   'NOFR': 'U:0x000008ff (2303)'
(以下略) 
なんとなくよさそうです。というかこれデータのダンプするのに非常にいいツールなので、業務でも使わせてもらいます。

次に、Pythonのソースコードでデータ構造を扱ってみます。
こんな感じでできそうです。
>>> import iso8211
>>> ddf = iso8211.DDF()
>>> ddf.open("../AADLULBD01.000", "r")  # ファイルを開く
>>> record = ddf[2]  # 3番目のレコードを取得
>>> record.show()
Record 3 at offset 12103
    Leader
        record length              = 62
        leader id                  = `D'
        base address of field data = 41
        entry map:
            size of field length   = 2
            size of field position = 2
            size of field tag      = 4
    Directory contains 2 entries
    Field 0
        tag  = 'IRID'
        data = b11   'RCNM': 'U:0x96 (150)'
               b14   'RCID': 'U:0x00000001 (1)'
               b12   'NITC': 'U:0x00ca (202)'
               b12   'RVER': 'U:0x0001 (1)'
               b11   'RUIN': 'U:0x01 (1)'
    Field 1
        tag  = 'ATTR'
        data = b12   'NATC': 'U:0x008b (139)'
               b12   'ATIX': 'U:0x0001 (1)'
               b12   'PAIX': 'U:0x0000 (0)'
               b11   'ATIN': 'U:0x01 (1)'
               A     'ATVL': '4'
>>> field = record[0]  # 親フィールドを取得
>>> field = record[1]  # 最初の子フィールドを取得
>>> tag = field.tag  # フィールドのタグを取得
>>> print(tag)
ATTR
>>> subfields = field.split()  # フィールドのすべてのサブフィールドを取得
>>> print(subfields)
[('NATC', b12, '\x00\x8b'), ('ATIX', b12, '\x00\x01'), ('PAIX', b12, '\x00\x00'), ('ATIN', b11, '\x01'), ('ATVL', A, '4')]

今回はここまでです。
なお、この段階では、まだファイルをISO 8211として読んでいるだけなので、S-57のENCファイルでもまったく同じことができます。

2017年の振り返り

今年は初めてFOSS4G Tokyoで登壇させていただきました。お世辞にもうまくできたとは言えませんが、コミュニティで発表させていただくという貴重な経験をさせていただいたことは、私にとって何物にも代えがたい財産になりました。OSGeoの関係者のみなさまや、私の話に興味を持ってくださったみなさまに、この場を借りて感謝いたします。