Ruby によるGML版基盤地図情報パーザーを改善・確認・ベンチマーク
d:id:hfu:20090723 の続きとしてプログラムを改善し、動作確認とベンチマークを兼ねて基盤地図情報のレコード数を数えてみました。
プログラムを改善
d:id:hfu:20090723 の改善で 25000 データに反応しなくなっていたので、25000 データも処理できるようにしました。25000 データの場合、Parser#city_code に都道府県コードを持ちます。
# 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) $stderr.print "#{Time.now}: #{src_dir}\n" Find.find(src_dir) {|path| next unless /zip$/.match path Zip::ZipFile.foreach(path) {|zip_entry| if /FG-GML-(\d*?)-(.*?)-(\d{8})-(\d{4})\.xml$/.match zip_entry.name @city_code = $1 @type = $2 @dev_date = $3 @serial = $4 $stderr.print " #{zip_entry.name}\n" 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
縮尺別にレコード数を数えるプログラム
上のプログラムのクラス Parser の動作確認と、ダウンロードした基盤地図情報の確認、Parser のベンチマークを兼ねて、基盤地図情報のレコード数を縮尺別に数えるプログラムを作りました。
require 'parse_nokogiri' # 前の節のプログラムを parse_nokogiri.rb とする print (['city_code'] + Parser::FEATURE_TYPES + ['count', 'process_time', 'rps']).join(","), "\n" Dir.glob('../../../src/fgd/2500/?????').sort.each {|path| city_code = File.basename(path) stat = {} Parser::FEATURE_TYPES.each {|name| stat[name.to_sym] = Hash.new {|h, k| h[k] = 0} } start_time = Time.now count = 0 Parser.new.process_zip_directory(path) {|feat| count += 1 stat[feat[:feature_type]][feat[:orgGILvl].to_i] += 1 } process_time = Time.now - start_time r = [city_code] Parser::FEATURE_TYPES.each {|name| r << "\"#{stat[name.to_sym].inspect}\"" } r << count r << process_time r << count / process_time print "#{r.join(',')}\n" }
プログラム実行結果
結果はあとで書くことにします。