Geo::FeatureList で MultiPolygon のあとに Polygon を書き込むと例外発生

hfu2007-12-19

id:yellow_73 さんのご指摘により、Geo::FeatureList で MultiPolygon を write したあと Polygon を write しようとすると、例外が発生することが分かりました。この問題の当面の回避策をご紹介します。

不具合再現

d:id:yellow_73:20071219 で、以下のようなご指摘をいただきました。

気を良くして県別ポリゴンで同じことをしようとしたら、
DefaultFeature.java:229:in `org.geotools.feature.DefaultFeature.setAttribute': org.geotools.feature.IllegalAttributeException: expected com.vividsolutions.jts.geom.MultiPolygon , but got com.vividsolutions.jts.geom.Polygon (NativeException)
と怒られました。
マルチポリゴンとしてデータを入れてるのに(シングルの)ポリゴンが来た、とか怒ってるくさいところまでは分かるのですが…。

2007-12-19

この現象を再現するスクリプトを作ってみました:

# multi.rb
require 'geotools'

Geo::FeatureList.open('multi.shp') do |w|
  wkts = ['MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))',
    'POLYGON ((4 4, 5 4, 5 5, 4 4))']
  wkts.each do |wkt|
    g = Geo::import_wkt_geometry(wkt)
    w.write({:the_geom => g})
  end
end

このスクリプトで、ご指摘の例外が発生します:

kuro:19 hfu$ jruby multi.rb 
DEBUG: jruby mode
DefaultFeature.java:229:in `org.geotools.feature.DefaultFeature.setAttribute': org.geotools.feature.IllegalAttributeException: expected com.vividsolutions.jts.geom.MultiPolygon , but got com.vividsolutions.jts.geom.Polygon (NativeException)
	from DefaultFeature.java:293:in `org.geotools.feature.DefaultFeature.setAttribute'
	from NativeMethodAccessorImpl.java:-2:in `sun.reflect.NativeMethodAccessorImpl.invoke0'
(以下略)

最近、(JRuby では使えない SQLite3 を使いたい場合があるという下心で、)Rjb でも再び geotools.rb を使えるようにしましたので、CRuby でも同じ例外が発生するかどうか確認しました:

$ ruby multi.rb 
DEBUG: rjb primitive_conversion mode
/opt/local/lib/ruby/site_ruby/1.8/i686-darwin8.9.1/geotools.rb:571:in `method_missing': expected com.vividsolutions.jts.geom.MultiPolygon , but got com.vividsolutions.jts.geom.Polygon (IllegalAttributeException)
	from /opt/local/lib/ruby/site_ruby/1.8/i686-darwin8.9.1/geotools.rb:571:in `r2gt'
	from /opt/local/lib/ruby/site_ruby/1.8/i686-darwin8.9.1/geotools.rb:563:in `each'
	from /opt/local/lib/ruby/site_ruby/1.8/i686-darwin8.9.1/geotools.rb:563:in `r2gt'
	from /opt/local/lib/ruby/site_ruby/1.8/i686-darwin8.9.1/geotools.rb:681:in `write'
	from multi.rb:8
	from multi.rb:6:in `each'
	from multi.rb:6
	from /opt/local/lib/ruby/site_ruby/1.8/i686-darwin8.9.1/geotools.rb:410:in `open'
	from multi.rb:3

当面の回避策

この問題は、ユーザスクリプトレベルで Polygon を MultiPolygon に変換することによって回避できます。com.vividsolutions.jts.geom.GeometryFactory#createMultiPolygon を使うことになります。createMultiPolygon が Java 配列をとるメソッドなので、スクリプトが多少長くなってしまいますが、上記の multi.rb に対して、下記 multi_fixed.rb のような変更をすることで、例外の発生を防ぐことができます:

# multi_fixed.rb
require 'geotools'

gf = Geo::Tools::GeometryFactory.new

Geo::FeatureList.open('multi.shp') do |w|
  wkts = ['MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((2 2, 3 2, 3 3, 2 2)))',
    'POLYGON ((4 4, 5 4, 5 5, 4 4))']
  wkts.each do |wkt|
    g = Geo::import_wkt_geometry(wkt)
    if g.getClass.getName == 'com.vividsolutions.jts.geom.Polygon'
      if(Geo::Tools::IMPLEMENTATION == 'rjb')
        g = gf.createMultiPolygon([g])
      elsif(Geo::Tools::IMPLEMENTATION == 'java')
        array = com.vividsolutions.jts.geom.Polygon[1].new
        array[0] = g
        g = gf.createMultiPolygon(array)
      end
    end
    w.write({:the_geom => g})
  end
end

*1

$ jruby multi_fixed.rb 
DEBUG: jruby mode
(問題なく終了)
ruby multi_fixed.rb 
DEBUG: rjb primitive_conversion mode
(問題なく終了)

長期的な対策について

この問題は、geotools.rb のレベルか、JavaGeoTools のレベルで修正が必要だと思っており、前者なら私による修正、後者なら GeoTools の人たちにバグレポートをすればいいのかなと思っているところです。長期的な対策について整理できたら、あとで書くかもしれません。

*1:Geo::Tools::IMPLEMENTATION という名前があまり良くないと感じたので、この定数は互換性のためにしばらくそのまま残しておくことにして、Geo::Tools::BRIDGE という定数も準備しようと思っています。可能な値は、:rjb, :jruby ということで。かえって混乱するかもしれないので、もう少しよく考えます。