=begin =Status * only a Lon-Lat coordinate is supported * only simple pakking is supported * variable list is not complete for NCEP and ECMWF =end require "narray_miss" require "date" require "time" require "numru/gphys_ext" ## /---> ## backport of String#ord and String#getbyte (ruby 1.9 feature) ## to Ruby 1.8 or earlier =begin if ! 'a'.respond_to?(:ord) class String def ord self[0] end end end if ! 'a'.respond_to?(:getbyte) class String def getbyte(i) self[i] end end end =end ## <---/ module NumRu module GribUtils require "numru/gphys/grib_params" private =begin def str2uint1(str) return nil if str.length==0 return str.ord end def str2uint2(str) return nil if str.length==0 return (str.ord<<8)+str.getbyte(1) end def str2uint3(str) return nil if str.length==0 return (str.ord<<16)+(str.getbyte(1)<<8)+str.getbyte(2) end =end def str2int1(str) n = str.to_uint1 return n && n > 127 ? 128-n : n end def str2int2(str) n = str.to_uint2 return n && n > 32767 ? 32768-n : n end def str2int3(str) n = str.to_uint3 return n && n > 8388607 ? 8388608-n : n end def uint2str(i,len) str = [sprintf("%0#{len*2}x",i)].pack("H*") str.length==len || raise("str length is not valid") return str end def int2str(i,len) flag = i<0 i = -i if flag str = sprintf("%0#{len*2}x",i) str[0..0] = sprintf("%x",str[0..0].hex+8) if flag str = [str].pack("H*") str.length==len || raise("str length is not valid") return str end def float_value( str ) str.length==4 || raise("str length must be 4") sa = str2int1(str) s = (sa<0) ? -1 : 1 a = sa.abs b = str.to_uint3(1) return s*(2.0)**(-24.0)*b*(16.0)**(a-64.0) end def get_bits( str, nstep, nbits, sbu, ebu, type ) len = str.length bx = NArray.to_na(str,NArray::BYTE) nb = nbits/nstep y = NArray.new(type,nb).indgen(nb-1,-1) if nstep==8 x = bx.to_type(type) y = 256**y elsif nstep==4 x = NArray.new(type,2,len) x[0,true] = (bx&240)/16 x[1,true] = bx&15 x.reshape!(len*2) y = 16**y elsif nstep==2 x = NArray.new(type,4,len) x[0,true] = (bx&192)/64 x[1,true] = (bx&48)/16 x[2,true] = (bx&12)/4 x[3,true] = bx&3 x.reshape!(len*4) y = 4**y else x = NArray.new(type,8,len) x[0,true] = (bx&128)/128 x[1,true] = (bx&64)/64 x[2,true] = (bx&32)/32 x[3,true] = (bx&16)/16 x[4,true] = (bx&8)/8 x[5,true] = (bx&4)/4 x[6,true] = (bx&2)/2 x[7,true] = bx&1 x.reshape!(len*8) y = 2**y end x = x[sbu..-1-ebu] x.reshape!(nb,x.length/nb) return (x*y).sum(0) end def data2str(data, nstep, nbits) len = data.length data.reshape!(len) nb = nbits/nstep x = NArray.byte(nb,len) nb.times{|n| x[nb-n-1,true] = (data/2**(n*nstep))&(2**nstep-1) } nb2 = 8/nstep y = NArray.byte(nb2).indgen(nb2-1,-1) y = (2**nstep)**y ebu = nb2-x.length%nb2 ebu = 0 if ebu==nb2 x2 = NArray.byte(x.length+ebu) x2[0..-1-ebu] = x.reshape!(x.length) x2.reshape!(nb2,x2.length/nb2) x2 = (x2*y).sum(0) return [x2.to_s,ebu*nstep] end def gausslat(ny) # this method was written by Y.Kitamura # and modified by S.Nishizawa glat = NArray.sfloat(ny) gweight = NArray.sfloat(ny) eps = 1.0 while (eps + 1.0 != 1.0) eps = eps/2.0 end eps = eps*16.0 0.upto(ny/2-1) do |i| y = Math::sin(Math::PI*(ny + 1.0 - 2.0*(i+1.0))/(2.0*ny + 1.0)) tmp = 1.0 while ((tmp/y).abs > eps) p0 = 0.0 p1 = 1.0 1.step(ny-1, 2) do |j| p0 = ((2*j-1)*y*p1 - (j-1)*p0)/j p1 = ((2*j+1)*y*p0 - j*p1)/(j+1) end p2 = ny*(p0 - y*p1)/(1.0 - y*y) tmp = p1/p2 y = y - tmp end glat[i] = y glat[ny-i-1] = -y gweight[i] = 1.0/(p2*p2*(1.0 - glat[i]*glat[i])) gweight[ny-i-1] = gweight[i] end return NMath::asin(glat)*180/Math::PI end def get_time(ary) d = (ary[0]-D19000101).to_i h,m = ary[1] return d*24+h+m/60 end end # GribUtils class Grib private class GribSegment class << self include GribUtils def create(file) obj = GribSegment.new obj.file = file obj.is = GribIS.new(obj) obj.pds = GribPDS.new(obj) obj.gds = GribGDS.new(obj) obj.bms = GribBMS.new(obj) obj.bds = GribBDS.new(obj) obj.es = GribES.new(obj) obj.is.update_total_length return obj end def parse(file, mbio) obj = GribSegment.new obj.file = file obj.mbio = mbio obj.pos = file.pos is = GribIS.new(obj,ss = file.read(8)) unless is.version == 1 warn "This Grib version (#{is.version}) has not supported yet" return nil end unless is.grib? raise "This file is not Grib file (INITIAL SECTION)" end obj.is = is pds = GribPDS.new(obj,file.read(file.read(3).to_uint3-3)) obj.pds = pds str = pds.gds? ? file.read(file.read(3).to_uint3-3) : nil gds = GribGDS.new(obj,str) obj.gds = gds str = pds.bms? ? file.read(file.read(3).to_uint3-3) : nil bms = GribBMS.new(obj,str) obj.bms = bms len = file.read(3).to_uint3 bds = GribBDS.new(obj,file.pos,len) obj.bds = bds file.seek(len-3,IO::SEEK_CUR) es = GribES.new(obj,file.read(4)) unless es.grib? raise "This file is not Grib file (END SECTION)" end obj.es = es if is.total_length!=is.length+pds.length+gds.length+bms.length+bds.length+es.length raise "total length is not equal to the sum of each section\nfile may be broken" end return obj end end attr_accessor :file, :pos, :mbio attr_accessor :is, :pds, :gds, :bms, :bds, :es def set_xy(x,y) pds.set_gds(true) if x.att("long_name").downcase=="longitude" && y.att("long_name").downcase=="latitude" xv = x.val yv = y.val rel = false if (xv[1]-xv[0])==(xv[-1]-xv[-2]) && (yv[1]-yv[0])==(yv[-1]-yv[-2]) id = 0 else raise "not defined yet" end elsif y.att("long_name").downcase=="longitude" && x.att("long_name").downcase=="latitude" xv = y.val yv = x.val rel = true if (xv[1]-xv[0])==(xv[-1]-xv[-2]) && (yv[1]-yv[0])==(yv[-1]-yv[-2]) id = 0 else raise "not defined yet" end else raise "not defined yet" end gds.set_grid(id,xv,yv,rel) end def set_level(z) if Array===z id = Z_TYPES.invert[Z_TYPES.values.assoc(z[0])] id = 100 if id.nil? pds.set_z_id(id) pds.set_z_value(z[1]) else id = Z_TYPES.invert[Z_TYPES.values.assoc(z)] pds.set_z_id(id) end end def set_time(t) if String===t ye,mo,da,ho,mi, = Time.parse(t).to_a.reverse![4..-2] else if t.nil? ye = 0 mo = 1 da = 1 ho = 0 mi = 0 else d = D19000101 + t/24 ye = d.year mo = d.month da = d.day ho = t%24 mi = 0 end end pds.set_initial(ye,mo,da,ho,mi) end def set_var(var,name,units) is.set_version(1) pds.set_version(2) pds.set_center_id(0) pds.set_pid(0) id = PARAMS_2.invert[NAMES_UNITS.invert[NAMES_UNITS.values.assoc(name)]] id = 000 if id.nil? pds.set_name_id(id) pds.set_time_unit_id(0) pds.set_p1(0) pds.set_p2(0) pds.set_time_range_id(1) pds.set_ave(0) pds.set_miss(0) pds.set_sub_center_id(0) pds.set_dfact(0) if NArrayMiss===var mask = var.get_mask! if mask.count_false>0 pds.set_bms(true) bms.set_map_number(0) bms.set_map(mask) end end bds.set_grid(true) bds.set_simple(true) if units && pds.unit f,o = Units[units].factor_and_offset(Units[pds.unit]) var = var*f+o end if var.typecode==NArray::SINT || var.typecode==NArray::INT bds.set_ingeger(true) else bds.set_float(true) end bds.set_value(var) end def write tlen = is.update_total_length len = 0 len += @file.write(is.get) len += @file.write(pds.get) len += @file.write(gds.get) len += @file.write(bms.get) len += @file.write(bds.get) len += @file.write(es.get) tlen==len || raise("length is not correct") end end # end definition of class GribSegment class GribIS include GribUtils def initialize(sgm,str=nil) @sgm = sgm @is = str || "GRIB"+uint2str(8,3)+uint2str(2,1) end def length return 8 end def grib? return @is[0..3]=="GRIB" end def total_length return @is.to_uint3(4) end def update_total_length len = self.length+@sgm.pds.length+@sgm.gds.length+@sgm.bms.length+@sgm.bds.length+@sgm.es.length @is[4..6] = uint2str(len,3) return len end def version return @is.to_uint1(7) end def set_version(ver) @is[7..7] = uint2str(ver,1) return ver end alias :version= :set_version def get @is end end # end definition of class GribIS class GribPDS include GribUtils private def param nid = @pds.to_uint1(5) params = nil case version when 0 params = PARAMS_0 when 1 params = PARAMS_1 when 2,3 if nid<128 params = PARAMS_2 else if cid==7 # NCEP params = PARAMS_NCEP_2 elsif cid == 34 # JMA params = PARAMS_JMA end end else if cid==98 # ECMWF if version==128 params = PARAMS_ECMWF_128 elsif version == 162 params = PARAMS_ECMWF_162 end end end if params if para_id = params[nid] if para = NAMES_UNITS[para_id] return para else warn "parameter ID #{nid} in table version #{version} has not defined yet" end else warn "parameter ID #{nid} in table version #{version} has not defined yet" end else warn "parameter table version #{version}, center id #{cid}, version #{version}, parameter id #{nid} has not defined yet" end return ["unknown (#{nid})", nid.to_s, "-", nil] end def cid return @pds.to_uint1(1) end def initialize(sgm,str=nil) @sgm = sgm @pds = str || "\000"*25 end public def length @pds.length+3 end def version return @pds.to_uint1 end def set_version(ver) @pds[0..0] = uint2str(ver,1) return ver end alias :version= :set_version def center return CENTERS[cid] end def set_center_id(id) @pds[1..1] = uint2str(id,1) end alias :center_id= :set_center_id def pid return @pds.to_uint1(2) end def set_pid(id) @pds[2..2] = uint2str(id,1) return id end alias :pid= :set_pid def gid return @pds.to_uint1(3) end def set_gid(id) @pds[3..3] = uint2str(id,1) return id end alias :gid= :set_gid def gds? flag = @pds.to_uint1(4) return (flag>>7)&1==1 end def set_gds(gds) flag = bms? ? 64 : 0 if gds flag += 128 @pds[4..4] = uint2str(flag,1) @sgm.gds.exist set_gid(255) return true else @pds[4..4] = uint2str(flag,1) @sgm.gds.not_exist return false end end alias :gds= :set_gds def bms? flag = @pds.to_uint1(4) return (flag>>6)&1==1 end def set_bms(bms) flag = gds? ? 128 : 0 if bms flag += 64 @pds[4..4] = uint2str(flag,1) @sgm.bms.exist return true else @pds[4..4] = uint2str(flag,1) @sgm.bms.not_exist return false end end alias :bms= :set_bms def name return param[0] end def standard_name return param[3] end def set_name_id(id) @pds[5..5] = uint2str(id,1) return id end alias :name_id= :set_name_id def sname zid = @pds.to_uint1(6) sn = param[1] zt = Z_TYPES[zid] zn = zt ? zt[1] : zid.to_s return zn=="level" ? sn : sn+"_"+zn end def unit return param[2] end def z_type zid = @pds.to_uint1(6) zt = Z_TYPES[zid] if zt return zt[0] else warn "z type (#{zid}) is not defined yet" return zid.to_s end end def set_z_id(id) @pds[6..6] = uint2str(id,1) return id end alias :z_id= :set_z_id def z_sname zid = @pds.to_uint1(6) if zn = Z_TYPES[zid] return zn[1] else warn "z type (#{zid}) is not defined yet" return "unknown_#{zid}" end end def z_value return @z_value if @z_value type = @pds.to_uint1(6) str = @pds[7,2] ary = Z_LEVELS[type] if ary.nil? ary = [] else ary = ary.dup end if ary.length==1 ary[0] = ary[0].dup.update( {"value"=> str.to_uint2} ) elsif ary.length==2 ary[0] = ary[0].dup.update( {"value"=> str.to_uint1} ) ary[1] = ary[1].dup.update( {"value"=> str.to_uint1(1)} ) end @z_value = ary return ary end def set_z_value(val) type = @pds.to_uint1(6) ary = Z_LEVELS[type] val.length==ary.length || raise("length of val is not collect") if val.length==1 @pds[7,2] = uint2str(val[0],2) elsif val.length==2 @pds[7,1] = uint2str(val[0],1) @pds[8,1] = uint2str(val[1],1) else raise "length of val is too large" end return val end alias :z_value= :set_z_value def initial return [@pds.to_uint1(21), @pds.to_uint1(9), @pds.to_uint1(10), @pds.to_uint1(11), @pds.to_uint1(12), @pds.to_uint1(13)] end def set_initial(year,month,day,hour,min) cent = year/100+1 year = year%100 @pds[21,1] = uint2str(cent,1) @pds[9,1] = uint2str(year,1) @pds[10,1] = uint2str(month,1) @pds[11,1] = uint2str(day,1) @pds[12,1] = uint2str(hour,1) @pds[13,1] = uint2str(min,1) return initial end alias :initial= :set_initial def date d = Date.new((@pds.to_uint1(21)-1)*100 + @pds.to_uint1(9), @pds.to_uint1(10), @pds.to_uint1(11) ) h = [@pds.to_uint1(12), @pds.to_uint1(13)] return [d,h] end def time_unit return TIME_UNITS[ @pds.to_uint1(14) ] end def set_time_unit_id(id) @pds[14,1] = uint2str(id,1) return id end alias :time_unit_id= :set_time_unit_id def p1 return @pds.to_uint1(15) end def set_p1(i) @pds[15,1] = uint2str(i,1) return i end alias :p1= :set_p1 def p2 return @pds.to_uint1(16) end def set_p2(i) @pds[16,1] = uint2str(i,1) return i end alias :p2= :set_p2 def time_range return @pds.to_uint1(17) end def set_time_range_id(id) @pds[17,1] = uint2str(id,1) return id end alias :time_range_id= :set_time_range_id def ave return @pds.to_uint2(18) end def set_ave(val) @pds[18,2] = uint2str(val,2) return val end alias :ave= :set_ave def miss return @pds.to_uint1(20) end def set_miss(val) @pds[20,1] = uint2str(val,1) return val end alias :miss= :set_miss def sub_center return @pds.to_uint1(22) end def set_sub_center_id(id) @pds[22,1] = uint2str(id,1) return id end alias :sub_center_id= :set_sub_center_id def dfact return str2int2( @pds[23,2] ) end def set_dfact(val) @pds[23..24] = int2str(val,2) return val end alias :dfact= :set_dfact def get uint2str(length,3)<<@pds end end # end definition of class GribPDS class GribGDS include GribUtils private def gtype return @gds.to_uint1(2) end def get_x_y(grid) case gtype when 0,4 #lon_lat (+GussLat) flag = grid.to_uint1(10) flag_inc = (flag>>7)&1 flag = grid.to_uint1(21) flag_scani = (flag>>7)&1 flag_scanj = (flag>>6)^1 flag_ij = (flag>>5)&1 lon = Hash.new lon["short_name"] = "lon" lon["long_name"] = "longitude" lon["units"] = "degrees_east" nlon = grid.to_uint2 nlon==0xffff && raise("not defined yet") lo1 = str2int3( grid[7,3] ) lo2 = str2int3( grid[14,3] ) dlon = grid.to_uint2(17) if (!flag_inc)&&dlon==-0x7fff dlon = (lo2-lo1)/(nlon-1) else if lo1>lo2 if dlon*(nlon-1)!=lo1-lo2 lo2 += 360000 else dlon = -dlon end end end dlon2 = (lo2-lo1).to_f/(nlon-1) dlon2.round==dlon.round || warn("Warning: dlon is not same: #{dlon2/1000} != #{dlon.to_f/1000}") vlon = NArray.sfloat(nlon).indgen*dlon2+lo1 if flag_scani==1 && dlon>0 # THIS PART MIGHT BE MEANINGLESS (OR WRONG) vlon = vlon[-1..0] end lon["value"] = vlon/1000 lon["length"] = vlon.length lat = Hash.new lat["short_name"] = "lat" lat["long_name"] = "latitude" lat["units"] = "degrees_north" nlat = grid.to_uint2(2) nlat==0xffff && raise("not defined yet") la1 = str2int3( grid[4,3] ) la2 = str2int3( grid[11,3] ) dlat = grid.to_uint2(19) if gtype==0 if (!flag_inc)&&dlat==-0x7fff dlat = (la2-la1)/(nlat-1) else dlat = -dlat if la1>la2 end dlat2 = (la2-la1).to_f/(nlat-1) dlat2.round==dlat.round || raise("dlat is not same: #{dlat2/1000} != #{dlat.to_f/1000}\n") vlat = NArray.sfloat(nlat).indgen*dlat2+la1 vlat = vlat/1000 elsif gtype==4 j = NArray.sfloat(dlat*2).indgen(1) # glat = NMath::asin(NMath::cos((j-0.5)*Math::PI/dlat/2))*180/Math::PI glat = gausslat(dlat*2) glat = glat[-1..0] if la1>la2 tmp = (glat-la1.to_f/1000).abs is = tmp.eq(tmp.min).where[0] tmp = (glat-la2.to_f/1000).abs ie = tmp.eq(tmp.min).where[0] vlat = glat[is..ie] vlat.length==nlat || raise("length of latitude is not same: #{vlat.length} != #{nlat}") if (vlat[0]*1000-la1).abs > (vlat[1]*1000-la1).abs warn("Warning: first of latitude is not same: #{vlat[0]} != #{la1.to_f/1000}") end if (vlat[-1]*1000-la2).abs > (vlat[-2]*1000-la2).abs warn("Warning: last of latitude is not same: #{vlat[-1]} != #{la2.to_f/1000}") end lat["type"] = "Gaussian latitude" lat["number_of_latitude_circles"] = NArray[dlat*2] end if flag_scanj==1 && dlat>0 # THIS PART MIGHT BE MEANINGLESS (OR WRONG) vlat = vlat[-1..0] end lat["value"] = vlat lat["length"] = vlat.length if flag_ij==0 lon["ij"] = 0 lat["ij"] = 1 else lon["ij"] = 1 lat["ij"] = 0 end return [lon,lat] when 1 #mercator raise "not defined yet" when 3 #lambert raise "not defined yet" when 5 #polar raise "not defined yet" when 50 #spherical raise "not defined yet" when 90 #space raise "not defined yet" else raise "not defined yet" end end def grid2str(gtype,x,y,rev=false) case gtype when 0,4 #lat_lon grid = "\000"*26 nlon = x.length nlat = y.length grid[0,2] = uint2str(nlon,2) grid[2,2] = uint2str(nlat,2) vlon = x*1000 vlat = y*1000 grid[4,3] = int2str(vlat[0],3) grid[7,3] = int2str(vlon[0],3) flag = 1<<7 grid[10,1] = uint2str(flag,1) grid[11,3] = int2str(vlat[-1],3) grid[14,3] = int2str(vlon[-1],3) dlon = ((vlon[-1]-vlon[0])/(nlon-1)).abs grid[17,2] = uint2str(dlon,2) if gtype==0 dlat = ((vlat[0]-vlat[-1])/(nlat-1)).abs grid[19,2] = uint2str(dlat,2) else raise "not defined yet" end if rev flag=1<<5 else flag=0 end grid[21,1] = uint2str(flag,1) return grid when 1 #mercator raise "not defined yet" when 3 #lambert raise "not defined yet" when 5 #polar raise "not defined yet" when 50 #spherical raise "not defined yet" when 90 #space raise "not defined yet" else raise "not defined yet" end end def initialize(sgm,str=nil) @sgm = sgm @gds = str end public def exist @gds = "\000"*41 @gds[1,1] = uint2str(255,1) @sgm.is.update_total_length end def not_exist @gds = nil @sgm.is.update_total_length end def length if @gds return @gds.length+3 else return 0 end end def nv return @gds.to_uint1 end def pv pv = @gds.to_uint1(1) return nil if pv==255 || nv==0 return pv end def pl pl = @gds.to_uint1(1) return nil if pl==255 return nv*4+pl end def grid_type return GRID_TYPES[ gtype ] end def grid if gtype==3||gtype==1 grid = @gds[3..38] elsif gtype==90 grid = @gds[3..40] else grid = @gds[3..28] end return get_x_y(grid) end def set_grid(id,x,y,rev) @gds[2..2] = uint2str(id,1) if id==3||id==1 str = grid2str(id,x,y,rev) str.length==36 || raise("length is not correct") @gds[3..38] = str @sgm.is.update_total_length elsif id==90 str = grid2str(id,x,y,rev) str.length==38 || raise("length is not correct") @gds[3..40] = str @sgm.is.update_total_length else str = grid2str(id,x,y,rev) str.length==26 || raise("length is not correct") @gds[3..28] = str @sgm.is.update_total_length end return [id,x,y] end def list_pv return nil unless pv list = NArray.sfloat(nv) nv.times{|n| list[n] = float_value( @gds[pv-4+n*4...pv-4+(n+1)*4] ) } return list end def set_list_pv(list) nv = list.length/4 @gds[0..0] = uint2str(nv,1) raise "not defined yet" if gtype==3||gtype==1 pv = 43 elsif gtype==90 pv = 45 else pv = 33 end @gds[1..1] = uint2str(pv,1) @gds[pv-4..-1] = list return true end alias :list_pv= :set_list_pv def list_pl return @gds[pl-4..-1] if pl return nil end def set_list_pl(list) raise "not defined yet" pl = nv*4+pv @gds[1..1] = uint2str(pl,1) @gds[pl-4..-1] = list return true end alias :list_pv= :set_list_pv def eq?(str) @gds==str end def ==(other) if GribGDS===other return other.eq?(@gds) else return false end end def get @gds && uint2str(length,3)<<@gds end end # end definition of class GribGDS class GribBMS include GribUtils def initialize(sgm,str=nil) @sgm = sgm @bms = str end def exist @bms = "\000"*3 @sgm.is.update_total_length end def not_exist @bms = nil @sgm.is.update_total_length end def length if @bms return @bms.length+3 else return 0 end end def map_number nmap = @bms.to_uint2(1) nmap = false if nmap==0 return nmap end def set_map_number(n) @bms[1..2] = uint2str(n,2) return n end alias :map_number= :set_map_number def has_map? return nil if @bms.nil? return !map_number end def map return nil unless has_map? return get_bits( @bms[3..-1], 1,1, 0,@bms.to_uint1, NArray::BYTE ) end def set_map(mask) map,n = data2str(mask,1,1) @bms[0..0] = uint2str(n,1) @bms = @bms[0,3]<>7)&1 end def flag2 return (oct4>>6)&1 end def flag3 return (oct4>>5)&1 end def flag4 return (oct4>>4)&1 end def flag6 return oct14 && (oct14>>6)&1 end def flag7 return oct14 && (oct14>>5)&1 end def flag8 return oct14 && (oct14>>4)&1 end def initialize(sgm,pos=nil,len=nil) @sgm = sgm @pos = pos if @pos.nil? @bds = "\000"*8 @oct4 = 0 @oct14 = 0 else @length = len end end def get_value(file,pos,sb,eb,nbits,nstep) sbu = (sb&7)/nstep sb = sb/8 ebu = eb&7 ebu = (8-(eb&7))/nstep unless ebu==0 eb = (eb+7)/8 return NArray.sfloat(0) if eb==sb file.seek(pos+sb,IO::SEEK_SET) str = file.read(eb-sb) return get_bits(str,nstep,nbits,sbu,ebu,NArray::SFLOAT) end public def length if @pos return @length else return @bds.length+3 end end def grid? return flag1==0 end def set_grid(l) @oct4 = oct4&0x7f @oct4 += 128 unless l @bds[0,1] = uint2str(@oct4,1) return l end def spectrum? return flag1==1 end def set_spectrum(l) @oct4 = oct4&0x7f @oct4 += 128 if l @bds[0,1] = uint2str(@oct4,1) return l end def simple? return flag2==0 end def set_simple(l) @oct4 = oct4&0xbf @oct4 += 64 unless l @bds[0,1] = uint2str(@oct4,1) return l end def complex? return flag2==1 end def set_complex(l) @oct4 = oct4&0xbf @oct4 += 64 if l @bds[0,1] = uint2str(@oct4,1) return l end def float? return flag3==0 end def set_float(l) @oct4 = oct4&0xdf @oct4 += 32 unless l @bds[0,1] = uint2str(@oct4,1) return l end def integer? return flag3==1 end def set_integer(l) @oct4 = oct4&0xdf @oct4 += 32 if l @bds[0,1] = uint2str(@oct4,1) return l end def single? return flag6 && flag6==0 end def set_single(l) @oct14 = oct14&0x7f @oct14 += 128 unless l @oct4 = (oct4&0xef)+16 @bds[0,1] = uint2str(@oct4,1) @bds[10,1] = uint2str(@oct14,1) return l end def matrix? return flag6 && flag6==1 end def set_matrix(l) @oct14 = oct14&0x7f @oct14 += 128 if l @oct4 = (oct4&0xef)+16 @bds[0,1] = uint2str(@oct4,1) @bds[10,1] = uint2str(@oct14,1) return l end def bit_maps? return flag7 && flag7==1 end def set_bit_map(l) @oct14 = oct14&0xbf @oct14 += 64 if l @oct4 = (oct4&0xef)+16 @bds[0,1] = uint2str(@oct4,1) @bds[10,1] = uint2str(@oct14,1) return l end def constant? return flag8 && flag8==0 end def set_constant(l) @oct14 = oct14&0xdf @oct14 += 32 unless l @oct4 = (oct4&0xef)+16 @bds[0,1] = uint2str(@oct4,1) @bds[10,1] = uint2str(@oct14,1) return l end def offset return @offset if @offset @sgm.file.seek(@pos+3, IO::SEEK_SET) @offset = float_value(@sgm.file.read(4)) return @offset end def efactor return @efactor if @efactor @sgm.file.seek(@pos+1, IO::SEEK_SET) @efactor = 2.0**str2int2( @sgm.file.read(2) ) return @efactor end def value(shape,*arg) nbits_unuse = oct4&15 @sgm.file.seek(@pos+7, IO::SEEK_SET) nbits_pack = @sgm.file.read(1).to_uint1 if (nbits_pack%8) == 0 nstep = 8 elsif (nbits_pack%4) == 0 nstep = 4 elsif (nbits_pack%2) == 0 nstep = 2 else nstep = 1 end r = offset e = efactor map = @sgm.bms.map nmiss = map ? map.count_false : 0 if flag4==0 nlen = ((length-3-8)*8-nbits_unuse)/nbits_pack nlon,nlat = shape (nlon*nlat)==nlen+nmiss || raise("length is not collect") if arg.length!=0 index, shape2 = arg index = index.collect{|el| Fixnum===el ? el..el : el} il = index[0] i0 = il.first i1 = il.last i0 = i0 < 0 ? nlon+i0 : i0 i1 = i1 < 0 ? nlon+i1 : i1 index[0] = il.exclude_end? ? i0...i1 : i0..i1 il = index[1] i0 = il.first i1 = il.last i0 = i0 < 0 ? nlat+i0 : i0 i1 = i1 < 0 ? nlat+i1 : i1 index[1] = il.exclude_end? ? i0...i1 : i0..i1 j = nil i = nil if map var = NArrayMiss.sfloat(*shape2) map.reshape!(nlon,nlat) mask = map[*index] nd = mask.count_true var[mask.where] = @sgm.mbio.read2D(@pos+8, nbits_pack, nd, 1, 0, nd-1, 1, 0, 0, 1, nil, nil, e, r).reshape!(nd) =begin index[1].each{|j| jj = j-index[1].first sb = j*nlon+index[0].first sb = sb-map[true,0..jj-1].count_false if jj>0 sb = sb*nbits_pack eb = sb+mask[true,j].count_true*nbits_pack if sb!=eb var[mask[true,j].where,jj] = get_value(@sgm.file,@pos+8,sb,eb,nbits_pack,nstep) end } =end else var = @sgm.mbio.read2D(@pos+8, nbits_pack, nlon, nlat, index[0].first, index[0].end, 1, index[1].first, index[1].end, 1, nil, nil, e, r) =begin sb = index[1].first*nlon*nbits_pack eb = (index[1].end+1)*nlon*nbits_pack var = get_value(@sgm.file,@pos+8,sb,eb,nbits_pack,nstep) var.reshape!(nlon, var.length/nlon) var = var[index[0],true] =end end else if map eb = nlen*nbits_pack var = NArrayMiss.sfloat(nlon,nlat) # var[map.where] = get_value(@sgm.file,@pos+8,0,eb,nbits_pack,nstep) nd = map.count_true var[map.where] = @sgm.mbio.read2D(@pos+8, nbits_pack, nd, 1, 0, nd-1, 1, 0, 0, 1, nil, nil, e, r).reshape!(nd) else =begin eb = nlen*nbits_pack var = get_value(@sgm.file,@pos+8,0,eb,nbits_pack,nstep) var.reshape!(nlon,nlat) =end var = @sgm.mbio.read2D(@pos+8, nbits_pack, nlon, nlat, 0, nlon-1, 1, 0, nlat-1, 1, nil, nil, e, r) end end # var = var*e+r return var else raise "not defined yet" n1 = @file.read(2).to_uint2 @sgm.file.seek(1, IO::SEEK_CUR) str = @sgm.file.read(6) n2 = str.to_uint2 p1 = str.to_uint2(2) p2 = str.to_uint2(4) # @bds[15..16] # width = str2uint( @bds[18..??] ) # bitmap = str2uint( @bds[??+1..n1-4] ) end end def set_value(val) val = val.reshape!(val.length) if NArrayMiss===val val = val.get_array![val.get_mask!.where] end dfact = @sgm.pds.dfact val = val*10**dfact ref = val.min val = val-ref max = val.max nbits = 16 e = (Math::log(max)/Math::log(2)).ceil-nbits val = val*2**(-e) if (nbits%8) == 0 nstep = 8 elsif (nbits%4) == 0 nstep = 4 elsif (nbits%2) == 0 nstep == 2 else nstep == 1 end str, bu = data2str(val.to_type(NArray::INT),nstep,nbits) @oct4 = (@oct4&15)+bu*16 @bds[0,1] = uint2str(@oct4,1) @bds[1,2] = int2str(e,2) s = (ref<0) ? -1 : 1 ref = ref.abs a = (Math::log(ref)/Math::log(16)).ceil+64 a<0 && raise("invalid range") a = 127 if a>127 b = ref*2**24*16**(64-a) a = a*s @bds[3,4] = int2str(a,1)<0) vn.dup.each{|name| var = @vars[name] sgm = nil if var.rank==2 sgm = GribSegment.create(@file) val = [var.get,var.att("long_name"),var.att("units")] z = var.att("level") t = var.att("time") vn.delete(name) elsif var.rank==4 sha = var.shape izmax < sha[2] && izmax = sha[2] if iz=1 nz = pds.z_value.length if nz!=0 z = Array.new(nz).collect{Array.new} n=nil m=nil sgms.each{|sgm| zv = sgm.pds.z_value nz.times{|m| if zv[m] z[m].push( zv[m]["value"] ) else z[m].push(m) end } } nz.times{|m| z[m].uniq! z[m].length==z[0].length || raise("length is not same") } if z[0].length>1 d = va.def_dim(pds.z_sname,2) d.put_att("long_name",pds.z_type) if pds.z_type=="Hybrid level" list_pv = gds.list_pv zval = pds.z_value[0] tmp = Array.new z[0].each{|n| tmp += [n-1,n] } tmp.uniq! tmp.sort! ap = list_pv[0...list_pv.length/2] b = list_pv[list_pv.length/2..-1] z[0] = {"value"=>NArray.to_na(z[0]),"name"=>zval["name"]} z[1] = {"value"=>ap[tmp], "name"=>"ap_half_lev", "long_name"=>"hybrid ap at half levels", "units"=>"Pa"} z[2] = {"value"=>b[tmp], "name"=>"b_half_lev", "long_name"=>"hybrid b at half levels", "units"=>"1"} elsif nz==1 zval = pds.z_value[0] z = NArray.to_na(z[0]) d.put_att("units",zval["units"]) d.put_att("value_type",zval["name"]) else nz.times{|mm| m = nz-m-1 zval = pds.z_value[m] z[m+1] = {"value"=>NArray.to_na(z[m]),"name"=>zval["name"],"units"=>zval["units"]} } z[0] = {"value"=>NArray.sint(z[0].length).indgen,"name"=>"level number"} end d.put(z) else va.put_att("level", pds.z_type) pds.z_value.each{|zz| va.put_att(zz["name"].gsub(/\s/,"_"), "#{zz["value"]} #{zz["units"]}") } end else va.put_att("level", pds.z_type) end len = sgms.length time = Array.new(len) len.times{|i| time[i] = get_time(sgms[i].pds.date) } # sgms.each{|sgm| # time.push( get_time(sgm.pds.date) ) # } time.uniq! if time.length>1 time = NArray.to_na(time) d = va.def_dim("time",-1) d.put(time) d.put_att("long_name","time") d.put_att("units","hours since 1900-01-01 00:00:0.0") else date = pds.date va.put_att("time","#{date[0]} #{"%02d"%date[1][0]}:#{"%02d"%date[1][1]}:0.0") end end return va end end # end definition of class methods GribVar def initialize(file,name) @file = file @name = name @attr = Hash.new @miss = false @dims = Array.new end def file @file end def name @name end def set_sgms(sgms) @obj = sgms end def set_miss(miss) @miss = miss end def rank dim_names.length end def total total = 1 ndims.times{|i| total = total*dim(i).length } total end def dim_names @dims.collect{|d| d.name } end def dim(index) index = dim_names.index(index) if String===index return nil if index.nil? @dims[index] end def ndims @dims.length end def def_dim(name,index) d = GribDim.new(self,name) if index==-1 @dims.push(d) else @dims[index] = d end return d end def put_att(key,val) @attr[key]=val end alias :set_att :put_att def att(key) @attr[key] end def att_names @attr.keys end def shape @dims.collect{|d| d.length } end def typecode if @obj.nil? nil elsif Array===@obj NArray::SFLOAT else @obj.typecode end end def get(*indices) if @obj.nil? return nil elsif NArray===@obj||NArrayMiss===@obj return @obj[*indices] else sha = shape if indices.length!=0 if indices[0] == false indices[0] = [true]*(sha.length-indices.length+1) elsif indices[-1] == false indices[-1] = [true]*(sha.length-indices.length+1) elsif indices.include?(false) raise "invalid indices" elsif sha.length!=indices.length raise "invalid indices" end sha.length.times{|n| ind = indices[n] if ind==true indices[n] = 0..sha[n]-1 next elsif Fixnum===ind sha[n] = 1 next elsif Range===ind f = ind.first e = ind.end e = sha[n]-1 if e==-1 e -= 1 if ind.exclude_end? sha[n] = e-f+1 indices[n] = f..e next else raise "invalid indices" end } if rank>2 mask = NArray.byte(*shape[2..-1]) mask[*indices[2..-1]]= 1 first = Array.new(indices.length-2) first.length.times{|i| ind = indices[2+i] if Fixnum===ind first[i] = ind < 0 ? shape[2+i]+ind : ind elsif Range===ind first[i] = ind.first else raise "invalid indices" end } else mask = true end else mask = true end value = @miss ? NArrayMiss.sfloat(*sha) : NArray.sfloat(*sha) if rank==2 vz = nil vt = nil index = [] elsif rank==4 vz = dim(2).val vt = dim(3).val index = Array.new(2) elsif !dim_names.include?("time") vz = dim(2).val vt = nil index = Array.new(1) else vt = dim(2).val vz = nil index = Array.new(1) end @obj.each{|sgm| pds = sgm.pds if vz index[0] = vz.eq(pds.z_value[0]["value"]).where[0] end if vt index[-1] = vt.eq(get_time(pds.date)).where[0] end next if (NArray===mask && mask[*index]==0) bds = sgm.bds if indices.length==0 || (indices[0]==true&&indices[1]==true) val = bds.value(shape[0..1]) else val = bds.value(shape[0..1],indices[0..1],sha[0..1]) end d = pds.dfact val = val*10.0**(-d) if NArray === mask index.length==first.length || raise("invalide indices") index.length.times{|i| index[i] = index[i]-first[i] } end value[true,true,*index] = val } sha.delete(1) value.reshape!(*sha) return value end end alias :[] :get alias :val :get def put(val) @obj = val end def inspect "GribVar: #{@file.path}?var=#{@name}" end end end ##################################################### if $0 == __FILE__ include NumRu if ARGV.length>0 infname = ARGV.shift else infname = "../../../testdata/T.jan.grib" end Grib.is_a_Grib?(infname) || raise("file is not a Grib dataset") p grib = Grib.open(infname) print "\nVars\n" grib.var_names.each{|vn| p v = grib.var(vn) p v.dim_names v.dim_names.each{|dn| p dn; p v.dim(dn).get } p v.shape v.att_names.each{|an| print an, " => ", v.att(an), "\n" } puts "\n" p v.val } end