Geo::FeatureList を作りました

hfu2007-11-05

geotools.rb に、Hash に基づく地物 (Feature) の Ruby 表現を前提とした新しい Shapefile 読み書きクラス Geo::FeatureList を作りました。Ruby のオブジェクトをそのまま Shapefile に保存できるようになっています。

地物は Hash

Geo::Reader 及び Geo::Writer では、地物 (Feature) を表現するオブジェクトの存在を排除していました*1
しかし、今回 Geo::FeatureList では、地物はハッシュであるという考え方を取ることにしました*2
そして、幾何属性や ID など、特別な属性は属性名の規約によってのみ区別することにしました。具体的には、以下のキーを使っています。

特別な属性 地物ハッシュにおけるキー
幾何属性 :the_geom*3
ID :the_id*4

また、Ruby において、ハッシュのキーには一貫して Symbol を使うことにしました。
下は、上記のモデルに従った地物の表現例です。

{:the_geom => Geo::import_array_geometry(0, 0), :name => 'the origin', :population => 14364, :temperature => 24.7}

ShapefileRuby オブジェクトを「直接」書き込みたい

これまで Geo::Reader, Geo::Writer を作成してきて、GeoTools による DBF ファイルへのデータの読み書きには、以下の課題があることが分かりました:

  • DBF の仕様により、値は 254 バイトまでの文字列として格納されてしまうらしい。そのため、浮動小数点数などは、ファイルを無駄遣いする割に精度が悪く、パーズ時間も無駄にかかっているようである。
  • GeoTools は、各型の最大長の幅を、DBF 内に確保してしまう。地名を格納するにも 254 文字の領域が確保されるなど、効率が悪い場合が多い。
  • GeoTools は、整数や小数点数の値をプリミティブ型クラスとして返すので、JRuby がさらにそれを Ruby の型に変換してくれることを含め、DBF-Java-Ruby の間の型変換にまつわる効率の悪さが心配される。
  • 計算の途中結果をハッシュや配列で直接 Shapefile に保存しておきたい。

そこで、Ruby の Marshal、Zlib と Base64 を使い、Marshal できる Ruby オブジェクトは文字列化して Shapefile に書き込めるようにし、シリアライズ機能は Geo::FeatureList に組み込んで、下記の規約を守るだけで Ruby オブジェクトを読み書きできるようにしました。

属性の型 地物 Hash に与えるキー Shapefile 内部での属性名
Shapefile/DBF の数値や文字列 「通常」文字列 e.g. abc 「通常」文字列 e.g. abc
Ruby のオブジェクト _ で始まる文字列 e.g. _abc _ で始まる文字列 e.g. abc
大きな Ruby のオブジェクト _ で始まる文字列 e.g. _abc *n* で始まる文字列(複数)e.g. *3*abc

すなわち、属性のキーの最初の1文字が _ であれば、Ruby オブジェクトをシリアライズして格納します。DBF の1フィールドでは足りない場合には、自動的に複数のフィールドを使うようにしますが、そのことはユーザに対して隠蔽されます。

その他の特徴

  • 読み込みと書き込み、書き換えを一つのクラスで行うようにしてみました。メソッド each を呼び出すことが読み込みモードとなり、地物を読み込んだ状態でメソッド write を引数なしで呼び出すことにより書き換えが行われ、(地物を読み込まない状態で)地物ハッシュを引数としてメソッド write を呼び出すことにより、書き込みが行われます。

こうすることによって混乱が生じる場合が考えられますが、自分の作業として想定される主な作業のコーディングをシンプルにする目的で、このような特徴を持たせることにしました。

仕様

http://svgmapdata.sakura.ne.jp/geotools/ をご覧ください。

使用例

Shapefile 内の華氏温度を摂氏温度に変換する Ruby スクリプトは、以下のようになるでしょう:

require 'geotools'

def f2c(f)
  5 * (f - 32) / 9.0
end

Geo::FeatureList.open('output.shp') do |w|
  Geo::FeatureList.open('input.shp') do |r|
    r.each do |feat|
      feat[:temperature] = f2c(feat[:temperature])
      w.write(feat)
    end
  end
end

このように、ユーザレベルのスクリプトが、Geo::Reader, Geo::Writer を使ったときに比べ、(私の主観では)より単純になります。

上記の実装がされた geotools.rb

上記の実装がされた geotools.rbhttp://svgmapdata.sakura.ne.jp/geotools/ で公開しています。
Geo::FeatureList の作成に合わせて、Geo::Modifier を obsolete 扱いにすることにしました。

参考文献

Geo::FeatureList を作るに当たっては、下記の資料は参考にしていません。

フューチャリスト宣言 (ちくま新書)

フューチャリスト宣言 (ちくま新書)

未来派野郎

未来派野郎

*1:これは、地物という概念の定義があまりに抽象的で、オブジェクト指向言語上での実装で地物を表現するクラスを作ることに疑問を覚えたからでした。Geo::Reader においては、ある意味ブロックのスコープが地物であると言えるかもしれません。

*2:JavaScript のオブジェクト、特に JSON の考え方に影響されているかもしれません。

*3:これまで通り、com.vividsolutions.jts.geom.Geometry のインスタンスを値として持つことにします。

*4:Shapefile から読み出されるときに、GeoTools から返される ID を格納しますが、この ID を使うことは少ないかもしれません。