Rjb 1.0.9 に対応しました
概要
Java 側のメソッドで戻り値の型が java.lang.Object になっている場合には、 java.lang.String のオブジェクトがそのまま、 Ruby の String に変換されることなく返却されることが分かりました。
http://arton.no-ip.info/diary/20070915.html#c08
と arton さんに報告したところ、下記の通り、早くも Rjb 1.0.9 がリリースされました。
Rjb-1.0.9。
戻り値の型がjava.lang.Objectかつ実際の型がjava.lang.StringかつRjb::primitive_conversionが真の場合に、RubyのString型のオブジェクトに変換するようにしました。
その他、
http://arton.no-ip.info/diary/20070923.html#p01
Rjb 1.0.9 を実際に使ってみたところ、報告した Rjb - JRuby 間の相違が解消されたことが確認できました。
また、geotools.rb でこのリリースへの対応を行ったところ、多少の速度向上 (計測誤差の範囲内かもしれません) も見られました。
アップデート
Rjb 1.0.9 は、すでに RubyGems に入っているので、Mac OS X でも以下のように簡単にアップデートできました。
$ export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home $ sudo gem install rjb Password: Select which gem to install for your platform (i686-darwin8.9.1) 1. rjb 1.0.9 (ruby) 2. rjb 1.0.9 (mswin32) 3. rjb 1.0.8 (mswin32) 4. rjb 1.0.8 (ruby) 5. Skip this gem 6. Cancel installation > 1 Building native extensions. This could take a while... Successfully installed rjb-1.0.9
テスト
アップデート後、以下のコード:
require 'rjb' ArrayList = Rjb::import('java.util.ArrayList') l = ArrayList.new l.add('string') def show(o) print "#{o.inspect} (#{o.respond_to?('_invoke') ? 'Java::' + o.getClass.getName : 'Ruby::' + o.class.to_s})\n" end show(l.get(0)) Rjb::primitive_conversion = true show(l.get(0))
を実行したところ、
$ $ ruby list_test2.rb #<#<Class:0x1149898>:0x114845c> (Java::java.lang.String) "string" (Ruby::String)
となり、java.lang.Object 型として返される java.lang.String のインスタンスが Ruby の String に型変換されることを確認しました。
geotools.rb での速度向上
geotools.rb では、Rjb 1.0.9 リリース前には、java.lang.String のインスタンスを自分のコード内で変換していました:
52: if attr.respond_to?('_invoke') # java.lang.String を変換 53: attr = attr.toString if attr.getClass == Geo::Tools::String 54: end # 今のところ、これで遅くなっている
Rjb 1.0.9 のリリースを受け、Rjb 1.0.9 (以降)が使われている場合にはこの処理を行わないよう、geotools.rb を書き換えました。
上記3行の処理を行わないようにしたことで、処理時間がどのくらい変わるかを確認してみました。確認のためのコードは:
require 'geotools' start_time = Time.now Geo::Reader.foreach('../transl_1_1.shp') do |geom, attrs| end print "#{Time.now - start_time}s\n"
といったものです。
速度確認結果
pritive_conversion = true で String を自動型変換 (for Rjb 1.0.9) | 63秒 |
primitive_conversion を使うが、String を手動型変換 (for Rjb 1.0.7 - 1.0.8) | 66秒 |
geotools.rb レベルで型変換 (for - Rjb 1.0.6) | 107秒 |
JRuby | 85秒 |
現在の geotools.rb の実装
現在の geotools. rb の実装は、以下のようになっています。
# this code is under development and subject to major change. require 'iconv' module Geo # Geo::Tools module, to include nesessary classes from Geotools module Tools QUALIFIED_NAMES = %w{java.lang.System java.lang.String java.lang.Integer java.lang.Double java.lang.Long java.io.File java.util.HashMap org.geotools.data.shapefile.ShapefileDataStore org.geotools.feature.AttributeTypeFactory org.geotools.feature.FeatureTypeBuilder org.geotools.feature.type.GeometricAttributeType com.vividsolutions.jts.io.WKTReader org.geotools.referencing.crs.EPSGCRSAuthorityFactory org.geotools.referencing.operation.DefaultCoordinateOperationFactory org.geotools.geometry.DirectPosition2D org.geotools.data.vpf.file.VPFFile org.geotools.data.vpf.VPFLibrary org.geotools.gce.geotiff.GeoTiffWriter org.geotools.gce.geotiff.GeoTiffWriteParams org.geotools.coverage.grid.GridCoverageFactory com.vividsolutions.jts.geom.Envelope org.geotools.geometry.Envelope2D org.geotools.factory.Hints org.geotools.geometry.jts.JTS org.geotools.filter.text.cql2.CQL} begin require 'rjb' QUALIFIED_NAMES.each do |qn| sn = qn.split('.').last module_eval "#{sn} = Rjb::import('#{qn}')" end IMPLEMENTATION = 'rjb' rescue LoadError require 'java' QUALIFIED_NAMES.each do |qn| include_class qn end IMPLEMENTATION = 'java' end end # Geo module variables @@wkt_reader = nil @@epsg_crs_authority_factory = nil # Geo module 'good-wrapper' / 'Grossklasstum' classes class Reader if Tools::IMPLEMENTATION == 'java' print "DEBUG: jruby mode\n" def iterate while(@iter.hasNext) feat = @iter.next attrs = {} @attr_names.each do |attr_name| attrs[attr_name] = feat.getAttribute(attr_name) end yield feat.getDefaultGeometry, attrs end end else begin Rjb::primitive_conversion = true print "DEBUG: rjb primitive_conversion mode\n" if Rjb::VERSION == '1.0.7' or Rjb::VERSION == '1.0.8' print "DEBUG: Rjb 1.0.7/1.0.8 mode. Install 1.0.9 for better performance.\n" def iterate while(@iter.hasNext) feat = @iter.next attrs = {} @attr_names.each do |attr_name| attr = feat.getAttribute(attr_name) if attr.respond_to?('_invoke') attr = attr.toString if attr.getClass == Geo::Tools::String end attrs[attr_name] = attr end yield feat.getDefaultGeometry, attrs end end else def iterate while(@iter.hasNext) feat = @iter.next attrs = {} @attr_names.each do |attr_name| attr = feat.getAttribute(attr_name) attrs[attr_name] = attr end yield feat.getDefaultGeometry, attrs end end end rescue NoMethodError print "DEBUG: rjb conventional mode\n" def iterate while(@iter.hasNext) feat = @iter.next attrs = {} @attr_names.each do |attr_name| attr = feat.getAttribute(attr_name) if attr.getClass.equals(Tools::Integer) attr = attr.intValue elsif attr.getClass.equals(Tools::Double) attr = attr.doubleValue elsif attr.getClass.equals(Tools::String) if @sjis_workaround attr = Iconv.conv('UTF-8', 'Shift_JIS', attr.getBytes('iso-8859-1')) else attr = attr.toString end elsif attr.getClass.equals(Tools::Long) attr = attr.longValue end attrs[attr_name] = attr end yield feat.getDefaultGeometry, attrs end end end end # keys for options: :sjis_workaround (boolean), :whitelist (array) # :bbox (array [xmin, ymin, xmax, ymax]) def Reader::foreach(shapefile, options = {}) r = Reader.new(shapefile, options) r.iterate do |geom, attrs| yield geom, attrs end r.close end def initialize(shapefile, options = {}) @sjis_workaround = options[:sjis_workaround] @sjis_workaround = false if @sjis_workaround == nil if(Tools::IMPLEMENTATION == 'java' && @sjis_workaround) raise "sjis_workaround for JRuby is not implemented." end store = Tools::ShapefileDataStore.new(Tools::File.new(shapefile).toURL) if options.has_key?(:bbox) b = options[:bbox] f = Geo::Tools::CQL::toFilter("BBOX(the_geom, #{b[0]}, #{b[1]}, #{b[2]}, #{b[3]})") # TODO: NONE and ALL are warned to be already initialized constants [Rjb] @iter = store.getFeatureSource.getFeatures(f).features else @iter = store.getFeatureSource.getFeatures.features end feat_type = store.getFeatureSource.getSchema if options[:whitelist] == nil @attr_names = [] feat_type.getAttributeCount.times do |i| name = feat_type.getAttributeType(i).getName @attr_names << name unless name == 'the_geom' end else @attr_names = options[:whitelist] #TODO: whitelist checking necessary? end end def close @iter.close end end class Writer def Writer::open(shapefile) w = Writer.new(shapefile) yield w w.close end def initialize(shapefile) @shapefile = shapefile @writer = nil @first = true end def setup(geom, attrs) attrs.delete('the_geom') ftb = Tools::FeatureTypeBuilder.newInstance(@shapefile) attrs.each do |key, value| if value.methods.include?('_classname') attr_class = value.getClass elsif value.class == String attr_class = Tools::String elsif value.class == Fixnum attr_class = Tools::Integer elsif value.class == Float attr_class = Tools::Double else raise "attribute #{key} has unrecognizable class #{value.class}" end ftb.addType(Tools::AttributeTypeFactory.newAttributeType(key, attr_class)) end if geom.class == String geom = import_wkt_geometry(geom) end ftb.setDefaultGeometry(Tools::GeometricAttributeType.new('the_geom', geom.getClass, true, nil, nil, nil)) ft = ftb.getFeatureType store = Tools::ShapefileDataStore.new(Tools::File.new(@shapefile).toURL) store.createSchema(ft) @writer = store.getFeatureWriter(@shapefile, store.getFeatureSource(@shapefile).getTransaction) @first = false end private :setup def write(geom, attrs = {}) setup(geom, attrs) if @first feat = @writer.next if geom.class == String geom = import_wkt_geometry(geom) end feat.setDefaultGeometry(geom) attrs.each do |key, value| feat.setAttribute(key, value) end @writer.write end def close @writer.close unless @writer == nil end end class Transform def initialize(src_crs, dst_crs) cof = Geo::Tools::DefaultCoordinateOperationFactory.new @co = cof.createOperation(src_crs, dst_crs) @mt = @co.getMathTransform end def transform(x, y) # z? r = Geo::Tools::DirectPosition2D.new @mt.transform(Geo::Tools::DirectPosition2D.new(x, y), r) return r.x, r.y end def transform(geom) Geo::Tools::JTS::transform(geom, @mt) end ## TODO: accessor to @mt or @co end # create GridCoverage of buf which just fits bbox def Geo::create_grid_coverage(buf, crs, bbox) Geo::Tools::System::setProperty('com.apple.eawt.CocoaComponent.CompatibilityMode', 'false') f = Tools::GridCoverageFactory.new n_pix_x = buf[0].size n_pix_y = buf.size env = Tools::Envelope2D.new(crs, bbox[0] - (bbox[2] - bbox[0]) / n_pix_x / 2, bbox[1] + (bbox[3] - bbox[1]) / n_pix_y / 2, bbox[2] - bbox[0], bbox[3] - bbox[1]) if Tools::IMPLEMENTATION == 'java' f.create('', buf.to_java(:float), env) else f._invoke('create', 'Ljava.lang.CharSequence;[[FLorg.opengis.geometry.Envelope;', '', buf, env) end end def Geo::write_grid_coverage(coverage, filename) w = Tools::GeoTiffWriter.new(Tools::File.new(filename)) # p = Tools::GeoTiffWriteParams.new # p.setCompressionMode(Tools::GeoTiffWriteParams.MODE_EXPLICIT) # p.setCompressionType('LZW') # p.setCompressionQuality(0.75) # format = w.getFormat ### -> rt.java problem: no sun.jdbc.odbc.ee.DataSource for Mac OS X ### so, no compression for GeoTiff file # params = format.getWriteParameters # params.parameter( # format.GEOTOOLS_WRITE_PARAMS.getName.toString).setValue(p) # w.write(coverage, params.values.toArray) w.write(coverage, nil) end # Geo module convenient methods def Geo::import_wkt_geometry(wkt) @@wkt_reader = Geo::Tools::WKTReader.new if @@wkt_reader == nil @@wkt_reader.read(wkt) end def Geo::import_epsg_crs(epsg_code) @@epsg_crs_authority_factory = Geo::Tools::EPSGCRSAuthorityFactory.new if @@epsg_crs_authority_factory == nil if epsg_code.class == Fixnum return @@epsg_crs_authority_factory.createCoordinateReferenceSystem("EPSG:#{epsg_code}") elsif epsg_code.class == String return @@epsg_crs_authority_factory.createCoordinateReferenceSystem(epsg_code) else raise "Geo::import_epsg_crs: can not handle epsg_code = #{epsg_code}" end end def Geo::bbox2polygon(b) Geo::import_wkt_geometry("POLYGON ((#{b[0]} #{b[1]}, #{b[2]} #{b[1]}, #{b[2]} #{b[3]}, #{b[0]} #{b[3]}, #{b[0]} #{b[1]}))") end def dms2dec(d, m, s) d + m / 60.0 + s / 3600.0 end def dec2dms(dec) #TODO raise "not implemented." end end # ad hoc tests if __FILE__ == $0 ## TODO: better separate tests as unit tests. start_time = Time.now Geo::Writer.open('test.shp') do |w| Geo::Reader.foreach('transl_1_1.shp', {:whitelist => ['exs', 'soc'], :bbox => [135, 35, 136, 36]}) do |geom, attrs| #print "#{attrs.inspect}\n" w.write(geom, attrs) end end print "#{Time.now - start_time} sec.\n" exit # ここから下は別の話題 ix = Geo::import_epsg_crs(2451) # EPSG:2451 - 平面直角座標系 IX 系 wgs84 = Geo::import_epsg_crs(4326) # EPSG:4326 - WGS84 t = Geo::Transform.new(ix, wgs84) # IX 系から WGS84 への座標変換器 t_inv = Geo::Transform.new(wgs84, ix) # WGS84 から IX 系への座標変換器 pt = t.transform(0, 0) # IX 系の原点を WGS84 に座標変換 pt_inv = t_inv.transform(pt[0], pt[1]) # その点を IX 系に戻す。元に戻るか? print "IX origin is #{pt.inspect} in WGS84\n" print "#{pt_inv.inspect} must be (0, 0)\n" end