2012/11/03

自分のための「基盤地図情報からshapefile」(2)

さて、前回は、基盤地図情報をshapefile に変換するための前準備として、
PyShp の設定方法について述べた。今回は、実際に実装を試みる。

ちなみに、自分用を前提としているので、エラー処理はしていないし、
何せ、Python を使いはじめて、9ヶ月くらい?
我流で書いているので正しい書き方かは解らない。

大したコードでは無いので、流用は自由。一言、書いて頂ける有難いが、
無断転用も含めてご自由に。ただし、一切、責任は取りませんので悪しからず。
その代わり、バグがあったら教えて下さい。

なお、pyshp のライセンスは、MITライセンスとなっている。詳細は以下にあるので、
本当に、利用したいという人は、一読しておいた方が良いかもしれない。

http://code.google.com/p/pyshp/

さてさて、では早速、今回実験したコードを見てみる。
最初に、宣言部分と今回利用したライブラリから。
このブログは自分ため。だから、親切な解説はしない。解らない人は自力で。

ソースコードは以下からダウンロード可能(2012年12月11日現在)。
https://docs.google.com/open?id=0B9OtIs5BjRVha1VmemsxTVAta2s

最初は、クラスの定義から。色々と書いていくと、どうも煩雑になってしまう。
そのため、今回は、UMLクラス図を使って、クラスを整理してみた。
まずは、基本となるGMLオブジェクトに関わるクラス


ここでは、GMLの「幾何オブジェクト」と「時間オブジェクト」を定義している。
地理情報標準空間スキーマ(ISO19107)に基づいているので、少々複雑。
しかも、突貫で作っているので、多少の不都合はあるが、今はこの状態で。

これらのクラスの最大の役割は、GML形式のXML要素が与えられた時に、
必要な属性情報を、入力された要素から抽出インスタンス化すること。
クラスとして定義することで、汎用的に使えるようになっている。

このクラス図が読めることが前提なので、細かい説明はしない。
いずれ、APIリファレンスも作る予定なので、併せてみれば理解できるハズ。
UMLPython も解らない人は、少々、厳しいか。

あえて、注釈をつけておくとしたら、Gmlクラスの役割について。
ネームスペースの部分を毎回書くのが面倒なので、定数として定義しているだけ。
以下のように実装してある。

class Gml(object):
__slots__ = ("__nsGml")

def __init__(self):
self.__nsGml = '{http://www.opengis.net/gml/3.2}'

def __getNsGml(self):
return self.__nsGml

getNsGml = property(__getNsGml)


一応、簡単な解説のみ。class Gml(object): の部分でGmlというクラスを定義し、
このクラスが、Python におけるObject型を継承しているを示している。

__slots__ の部分では、このクラスで利用可能な属性名を限定している。
Python という言語、かなり奇妙な言語で、勝手に属性を加えることができてしまう。
この機能を封じるために、__slots__の部分で使用する属性を限定する。

__init__ の部分は、このクラスが実装された際に、行う初期化処理。
__nsGml という属性に、'{http://www.opengis.net/gml/3.2}' という値を代入している。
属性前に、「__」 が付いているのは、この属性がプライベートになっていることを示す。

属性の読み書きは、settergetter で設定することができる。ちなみに、
__nsGml 属性は、getterのみを設定している。つまり、読み取り専用属性
最初に初期化して以降、この値を変更することはできない。

この属性を外部から読む時の名前、つまり属性名は、getNsGml であり、
外から呼び出すときには、この名前を使って、{http://www.opengis.net/gml/3.2}
という文字列を取得する。

他のクラスに関しては...ダラダラと書いているだけなので、説明する必要はなかろう。

次に、基盤地図情報をマッピングするためのクラスの定義。
まぁ、基本的には、このような感じになる。「やっつけ仕事」で実装したので、
製品仕様書を確認しながら修正した方が良さそうである。現段階は、実験段階。



ここで定義されているクラスも、基本的には、GML形式のデータを読み込み、
入力されたXML要素から、属性情報を抽出するのが主たる目的である。
これに加えて、Shapefile に出力機能を持たせている。

現段階では、行政区画(AdministrativeArea)行政区画代表点(AdministrativePoint)
行政区画線(AdministrativeBoundary)、道路構成線(LoadEdge)の4クラスを実装できるようになっていて、
それぞれ、これらのクラスを集約するクラスを持っている。

3つの集約クラス、AdministrativeAreasAdministrativePointsAdministrativeBoundaries
は、複数のインスタンスを集約するのが目的。このクラスが、shapefile への出力や、
XML形式のデータを読み込んだり、といった処理をまとめて行う。

重要な点は、これら3つのクラスの初期化の部分。基本的に、どれも同じなので、
以下は、AdministrativePoints の初期化部分の例。

class AdministrativePoints(JPGISClass):
__slots__ = ("__admPts", "__shp")

def __init__(self, shape = None):
JPGISClass.__init__(self)
self.__admPts = set()
self.__shp = shapefile.Writer(shapefile.POINT)
self.__shp.field('FID')
self.__shp.field('LFSPANFR')
self.__shp.field('DEVDATE')
self.__shp.field('ORGGILVL')
self.__shp.field('ORGMDID')
self.__shp.field('VIS')
self.__shp.field('TYPE')
self.__shp.field('NAME')
self.__shp.field('ADMCODE')

初期化する際に、PyShpshapefile オブジェクトを実装し、
さらに、このタイミングで、shapefile の形式と、
必要な属性(フィールド)を設定しておく。

いずれ、shapefile からのインスタンス化も、と考えていて、
引数に、shape=None を定義しているが、このコードを見て解る通り、
現段階では、全く意味を成していない

それは、まぁ、良いことにする。


さらに、3つのクラスを集約するクラスは、「JPGISClass」というクラスを継承している。
このクラスでは、唯一、convertEncという関数が定義されているだけ。
この関数は、GML形式のXMLデータエンコードをShift-JISからUTFに変換する。

基盤地図情報は、Shift-JIS を使っているが、そのおかげで色々と厄介な問題が発生する。
UTF-8に変換するための処理がここで定義されている。Shift-JIS は、やっぱり困る
このクラスを実装すると、以下のようになる。

class JPGISClass(object):
def convertEnc(self, filename):
# Read a original xml file as a text file format.
txt = open(filename).read()

# Convert the text file to unicode.
unic = unicode(txt, 'cp932')

# Convert the encoded unicode text file to the UTF-8 file.
utf8 = unic.replace("Shift_JIS", "utf-8").encode('utf-8')

# Return converted text string.
return utf8



とにかく、この部分の処理が重要。Python は、日本語には優しくない。
特に、Shif-JIS というローカライズド・エンコーディングの扱いは大変。
ファイルサイズの問題があるとは言え、この点は、何とかしてもらいたいものである。

さて、ダラダラと、各クラスとその説明を書いても仕方ないので、
ここで、全体的な処理の流れを確認してみる。
ダウンロード済みのデータが、以下のようになっているとして...

/path/to/download/PackDLMap
├── FG-GML-14101-05-Z001.zip
├── FG-GML-14101-06-Z001.zip
├── FG-GML-14101-08-Z001.zip
     ・
     ・
     ・
├── FG-GML-14118-05-Z001.zip
├── FG-GML-14118-06-Z001.zip
└── FG-GML-14118-08-Z001.zip


今回、書いたPython スクリプトの実行方法は以下の通り。

python cis.jpgis.py -d '/path/to/download/PackDLMap' 

このように、するだけで、ファイルの解凍も含めて自動処理してくれる。
我ながら、便利なツール。これができれば、QGIS から直接読み込むこともできる。
まぁ、QGISを使いたいのであれば、別の方法もあるらしいが。

さて、このスクリプトが、実際に何をしているかというと、
def main(argv): で定義されている部分が、最初に動作しはじめて、
zipファイルの解凍xmlデータのパースshpefileへの書き出しを順次行う。



要するに、別のルーチンに飛んで処理を行なっているだけ。
個々の処理は、それほど、複雑では無いので、まぁ、見たら解るだろう。
そもそも、これは、あくまで「自分のため」のメモ程度

さてさて、最終的に上手く出力できると、以下のような図が出てくるハズ。
この図は、横浜市のデータを一括ダウンロードして、shapefile に変換したもの。
同様に、クラスを拡張すれば、他のデータも変換できるハズ。
ただし、一点だけ、注意すべきことがある。
現在のところ、ドーナツポリゴンが処理できていない...
PyShp の仕様を読んでも良く解らない。

やはり、PostGIS に実装した方が良いのかもしれない。
それにしても、shapefile は、どうも使い勝手が悪いので、好きに慣れない。
DBに入れる方が、早いかもしれない。

2 件のコメント:

  1. うわ〜、文章メチャクチャや...今日は、フラフラなので、明日にでも書き直そう。疲労の度合いが、完全に文章に出てるわ。こりゃアカン。

    返信削除
  2. Pythonの基盤地図変換スクリプトを修正しました。道路構成線もダウンロードできるようになりました。https://docs.google.com/open?id=0B9OtIs5BjRVha1VmemsxTVAta2s

    返信削除