GML版基盤地図情報のRubyハッシュへのコンバータ
GML版基盤地図情報のRubyハッシュへのコンバータを作りました。
d:id:hfu:20080401 の続きとして、道路縁だけでなくすべての基盤地図情報地物に対応し、ダウンロードした ZIP ファイルを含むフォルダを直接解釈できるようにし、地物ごとにハッシュを yield するコンバータを作りました。
あまり趣味的なことをすることが道義的に許される状況にないのですが、お世話になっている方々にもあるいは役にも立つかも知れないし、そうでない方々にも役立つかも知れないということで、作成かつ発信させていただくことにしました。個人的な動機は、基盤地図情報と一緒に配布されているコンバータが Mac OS X で高速には動作させにくく、出力が Shapefile であり、その属性名が日本語であって扱いにくいことから、Mac OS X で軽快に動作する単純なコンバータが欲しかったからです。また、そのコンバータを使って基盤地図情報の調べ物の敷居を低くしたかったからです。
このコンバータの先に geotools.rb を付けても良いですし、sequel 経由で PostGIS に入れても良いと思っています。Ruby でも JRuby でも動作します。
実装
# Usage of the works is permitted provided that this insturment is retained # with the works, so that any entity that uses the works is notified of this # instrument. # # DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY. require 'find' require 'rexml/document' require 'rexml/streamlistener' require 'rubygems' require 'zip/zipfilesystem' class Parser include REXML::StreamListener ATTR_NAMES = %w{fid lfSpanFr lfSpanTo devDate orgGILvl orgMDId name type admCode alti} GEOM_NAMES = %w{pos loc area} FEATURE_TYPES = %w{AdmArea AdmBdry BldA BldL CommBdry CommPt CstLine ElevPt RailCL RdCompt RdEdg WL} #INTERIOR_OR_EXTERIOR = %w{gml:interior gml:exterior} def initialize @mode = 'default' #@interior_or_exterior = 'default' @feat = Hash.new {|h, k| h[k] = ''} end def tag_start(name, attrs) if FEATURE_TYPES.include? name @feat = {:feature_type => name.to_sym} elsif ATTR_NAMES.include? name @mode = name elsif GEOM_NAMES.include? name @mode = name @wkt = case @mode when 'pos': 'POINT (' when 'loc': 'LINESTRING (' when 'area': 'POLYGON (' end #elsif INTERIOR_OR_EXTERIOR.include? name # @interior_or_exterior = name end end def tag_end(name) if FEATURE_TYPES.include? name @block.call(@feat) elsif ATTR_NAMES.include? name @mode = 'default' elsif GEOM_NAMES.include? name @wkt.chop!.chop! if @mode == 'area' @wkt += ')' @feat[:the_geom] = @wkt @mode = 'default' #elsif INTERIOR_OR_EXTERIOR.include? name # @interior_or_exterior = 'default' end end def text(text) return if @mode == 'default' text.chomp! size = text.size return if size == 0 if ATTR_NAMES.include? @mode @feat[@mode.to_sym] = text elsif GEOM_NAMES.include? @mode geometry(text) end end def geometry(posList) @wkt += '(' if @mode == 'area' posList.split("\n").each {|s| coords = s.chomp.split(' ') next unless coords.size == 2 @wkt += "#{coords[1]} #{coords[0]}, " } @wkt.chop!.chop! @wkt += '), ' if @mode == 'area' end def process_zip_directory(src_dir, &block) Find.find(src_dir) {|path| next unless /zip$/.match path Zip::ZipFile.open(path) {|zipfile| zipfile.dir.foreach('/') {|f| next unless /xml$/.match f next unless /GML/.match f process_stream(zipfile.file.new(f)) {|feat| yield feat } } } } end def process_stream(stream, &block) @block = block REXML::Document.parse_stream(stream, self) end end if __FILE__ == $0 Parser.new.process_zip_directory('../../../src/fgd/2500/') {|feat| p feat } end
現在のところ、ダウンロード提供されていることが確認できている基盤地図情報項目にのみ対応していますが、クラス定義開始の直下にある定数を、http://fgd.gsi.go.jp/spec/2008/FGD_DLFileSpecV2.0.pdf を参考にするなりして増強すれば、スキーマのみ公開されているその他の基盤地図情報項目にも対応できることになるはずです。
ポリゴンについて、現在 exterior を解釈しているのか interior を解釈しているのかが分かるようにしていましたが、基盤地図情報の GML インスタンスでは、必ず exterior が1回、最初に出現する(これは、WKT の exterior/interior 出現パターンと同一である)ようでしたので、exterior/interior 判断の部分はコメントアウトしました。用途によってはコメントアウトを外すこともあるかもしれません。