GeoRuby を Ruby 1.9 でも動かす方法

GeoRuby は Array#to_s の関係で、Ruby の 1.8 では問題なく動作しますが、1.9 ではうまく動作しない部分があります。とりあえず、Shapefile を読む部分について、GeoRuby を 1.9 対応にするための修正プログラムを作成してみました。

経過

d:id:hfu:20100513:1273695379Shapefile の属性読み出しコードは、Ruby 1.9 ではうまく動作しないとの情報を得ました。確かに、次のような結果となります。

$ ruby1.9 open_rdedg.rb 
open_rdedg.rb:11:in `block (2 levels) in <main>': undefined method `each' for nil:NilClass (NoMethodError)
	from /opt/local/lib/ruby1.9/gems/1.9.1/gems/GeoRuby-1.3.4/lib/geo_ruby/shp4r/shp.rb:122:in `block in each'
	from /opt/local/lib/ruby1.9/gems/1.9.1/gems/GeoRuby-1.3.4/lib/geo_ruby/shp4r/shp.rb:121:in `each'
	from /opt/local/lib/ruby1.9/gems/1.9.1/gems/GeoRuby-1.3.4/lib/geo_ruby/shp4r/shp.rb:121:in `each'
	from open_rdedg.rb:10:in `block in <main>'
	from /opt/local/lib/ruby1.9/gems/1.9.1/gems/GeoRuby-1.3.4/lib/geo_ruby/shp4r/shp.rb:56:in `open'
	from open_rdedg.rb:9:in `<main>'

そこで、該当する部分を確認し、原因を追いかけてみると、Array#to_s を使っているメソッド GeoRuby::Shp4r::Dbf::Reader#active_record? あたりが原因であることが分かります。
to_s (Array) - Rubyリファレンス
にあるとおり、Ruby 1.9 からは、Array#to_s は Array#inspect の結果を戻しますから、Ruby 1.8 での挙動を前提にしたこのあたりの記述は、Ruby 1.9 では意図に反する動作をしてしまいます。

d:id:hfu:20100513:1273695379Shapefile の属性読み出しコードをとりあえず動作させるには、Ruby 1.9 における Array の動作を書き戻して、

class Array
  def to_s
    self.join
  end
end

とすることも出来ますが、Ruby の組み込みクラスの挙動を書き換えることになり、気持ちも悪ければ危険でもあります。

そこで、GeoRuby の側を Ruby 1.9 に対応させることにしました。

処置

まず、
るびま
すなわち
Rubyの互換性警告ライブラリを作ってみました / Je viens de faire un librairie qui alerte la compatibilité de Ruby. - ふぇみにん日記(2009-08-15)
にある、compatibility_warning.rb を導入して、情報出力を停止したコード:

require 'rubygems'
require 'geo_ruby'
require 'iconv'
require 'compatibility_warning'

include GeoRuby::Shp4r
include GeoRuby::SimpleFeatures
c = Iconv.new('UTF-8', 'Shift_JIS')

ShpFile.open('rdedg.shp') {|shp|
  shp.each {|r|
    r.data.each {|k, v|
#      print "#{c.iconv(k)}: #{c.iconv(v.to_s)}\n"
    }
  }
}

を、Ruby 1.8 で動作させます。すると、

pro:13 hfu$ ruby open_rdedg.rb 
(compatibility warning) Array#to_s used in /opt/local/lib/ruby/gems/1.8/gems/GeoRuby-1.3.4/lib/geo_ruby/shp4r/dbf.rb:94:in `active_record?'
(compatibility warning) Array#to_s used in /opt/local/lib/ruby/gems/1.8/gems/GeoRuby-1.3.4/lib/geo_ruby/shp4r/dbf.rb:148:in `unpack_string'

のような出力が得られます。

これで、直すべき部分が同定されましたので、該当部分のメソッド定義を書き換える、次のコードを作成します。

#geo_ruby_fix19.rb

module GeoRuby
  module Shp4r
    module Dbf
      class Reader
        private
        def active_record?
          @data_file.read(1).unpack('H2').join == '20' rescue false
        end

        def unpack_string(field)
          unpack_field(field).join
        end
      end
    end
  end
end

次に、いま作成した修正コードを読み込むように修正した、元のプログラム:

#open_rdedg.rb
require 'rubygems'
require 'geo_ruby'
require 'iconv'
require 'geo_ruby_fix19'

include GeoRuby::Shp4r
include GeoRuby::SimpleFeatures
c = Iconv.new('UTF-8', 'Shift_JIS')

ShpFile.open('rdedg.shp') {|shp|
  shp.each {|r|
    r.data.each {|k, v|
      print "#{c.iconv(k)}: #{c.iconv(v.to_s)}\n"
    }
  }
}

Ruby 1.9 で実行すると、次のように問題なく動作することが確認できます。

$ ruby1.9 open_rdedg.rb
id: …

今後の作業では、require 'geo_ruby_fix19' を入れておこうと思います。そうしておけば、Ruby 1.8 でも Ruby 1.9 でも動作するからです。

本来行うべき処置について

こういった修正は、いわゆる「本家」に戻すべきだと思うのですが、「本家」は活動が泊まっているようであり、github 上の2つのフォークも、どちらかというとそれぞれの方の目的のためのフォークであるようですので、「本家に戻す」処置については、あとで考えるつもりです。他の方によって「本家に戻す」ことを妨げるものではありません。