Ruby によるGML版基盤地図情報パーザーを改善
d:id:hfu:20090716 の続きとして、基盤地図情報パーザーを改善しました。
改善項目
改善項目は、次のとおりです。
- XML を読む部分で、REXML::StreamListener を使っていたが、基盤地図情報が Shift_JIS で書かれているためか、REXML::ParseException が発生することがあった。速度の向上も期待して、nokogiri を使用するように変更した。
- ZIP ファイルの中でフォルダを作成してデータが格納されているパターン(FG-GML-03201-ALL-Z002.zip)が発見されたので、この場合でもデータを読むように変更した。
- 現在読んでいる ZIP ファイル中のファイル名称から、Parser#city_code, Parser#type, Parser#dev_date, Parser#serial を読めるようにした。
改善後
# 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 'rubygems' require 'nokogiri' require 'zip/zip' class Parser < Nokogiri::XML::SAX::Document 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} def initialize @mode = 'default' @feat = Hash.new {|h, k| h[k] = ''} end attr_reader :city_code, :type, :dev_date, :serial def start_element(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 end end def end_element(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' end end def characters(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.foreach(path) {|zip_entry| if /FG-GML-(\d{5})-(.*?)-(\d{8})-(\d{4})\.xml$/.match zip_entry.name @city_code = $1 @type = $2 @dev_date = $3 @serial = $4 process_stream(zip_entry.get_input_stream) {|feat| yield feat } end } } end def process_stream(stream, &block) @block = block parser = Nokogiri::XML::SAX::Parser.new(self) parser.parse(stream) end end if __FILE__ == $0 parser = Parser.new parser.process_zip_directory('../../../src/fgd/2500/') {|feat| print "#{parser.city_code}: #{feat.inspect}\n" } end
次回、改善したパーザーでの実験結果を書こうと思っていますが、nokogiri の採用によって、かなりの程度、処理速度が向上したようです。両極端の比較になるかと思いますが、JRuby で REXML を使う旧版を使ったときと、Ruby (CRuby, MRI) 1.8.7 で nokogiri を使う新版を使った時とでは、処理速度が 10 倍以上異なるようです。