#!/usr/bin/ruby class String alias :byteslice :slice unless "a".respond_to?(:byteslice) alias :bytesize :size unless "a".respond_to?(:bytesize) def getui3 ofs #hi, lo = self[ofs, 3].unpack('Cn') hi, lo = self.byteslice(ofs, 3).unpack('Cn') (hi << 16) + lo end def bitslice_uint bofs, bwid # the first octet oofs = bofs / 8 # the last bit bend = bofs + bwid - 1 # the last octet oend = bend / 8 # number of octets to extract onum = oend - oofs + 1 ival = 0 onum.times {|iofs| ival <<= 8 cval = self.byteslice(oofs + iofs, 1).unpack('C').first # printf "[%7u: %#02x %#08b]\n", oofs + iofs, cval, cval ival |= cval } ival >>= (7 - bend % 8) ival &= ((1 << bwid) - 1) ival end end class GRIB2Dump @@tab = nil def tabinit return if @@tab @@tab = {} for line in DATA row = line.strip.split(/\t/) case row.first when /^#/ #do nothing when /^:/ t = $' @@tab[t] = {} unless @@tab[t] @@tab[t][row[1,2].join("\t")] = { :unit => row[3], :desc => row[4] } else #tab label wid type desc t = row[0] @@tab[t] = [] unless @@tab[t] h = { :label => row[1], :width => row[2], :type => row[3], :desc => row[4] } @@tab[t].push h end end end def initialize fp @fp = fp @begin = fp.pos @sec0 = fp.read(16) msglen = @sec0[8,8].unpack('NN') @msglen = (msglen[0] << 64) + msglen[1] @endpos = @begin + @msglen - 4 @vars = {} @vars4 = nil tabinit end def readsec cur = @fp.pos return nil if cur >= @endpos secl = (head = @fp.read(4)).unpack('N').first throw(:malformat, "too big section #{secl}") if cur + secl > @endpos body = @fp.read(secl - 4) head + body end def run decode 'IDS', @sec0 while sec = readsec nsec = sec.unpack('x4C').first xputs sprintf("= Section #{nsec} (size #{sec.bytesize})") case nsec when 1 then decode 'IS', sec when 3 then decode 'GDS', sec when 4 then @vars4 = @vars.dup decode 'PDS', sec @vars = @vars4 when 5 then decode 'DRS', sec when 6 then decode 'BMS', sec when 7 then decode 'DS', sec end end magic = @fp.read(4) throw(:malformat, "Missing magic '7777' at end of GRIB") if magic != '7777' end def xputs s s.sub!(/$/, ')') if s.sub!(/^\t/, ' (') w = s.gsub(/ +/){|st| "\t" * st.size}.split(/ /) buf = [] while true if w.empty? or buf.join(' ').size + w.first.size > 78 puts buf.join(' ').gsub(/\t/, ' ') break if w.empty? buf = [' ' * 40] end buf.push w.shift end end def dumpcode val, wid, fmt tab, subkey = fmt[:type], '-' if /\// === tab then tab = $` subkey = $'.gsub(/\[(\w+)\]/) { @vars[$1] } end t = @@tab[tab] key = [subkey, val].join("\t") throw(:mistable, "code table #{tab} (key #{key})") unless t throw(:mistable, "entry #{key} in code table #{tab}") unless t[key] desc = t[key][:desc] u = t[key][:unit] desc += " [#{u}]" unless '-' == u xputs "\t= #{desc}" end def dumpflags val, wid, fmt tab = fmt[:type] t = @@tab[tab] throw(:mistable, "flag table #{tab}") unless t bwid = wid * 8 bwid.times {|i| vbit = val & (1 << (bwid - i - 1)) bit = vbit.zero? ? 0 : 1 fi = i + 1 p [:XF, val, fi, bit, fmt[:type]] if $DEBUG key = [fi, bit].join("\t") if ent = t[key] hit = case ent[:unit] when /\/\[\.(\d+)\]/ then cbit = $1.to_i p [:XFC, cbit] if $DEBUG hit = !(val & (1 << (bwid - cbit))).zero? else true end xputs "\t:bit #{fi} = #{bit}: #{ent[:desc]}" if hit end } end def dumpitem item, wid, fmt, ofs, tab sval = '(err)' val = nil xd = item.unpack('H*').first.upcase case fmt[:type] when /^!GRIB/ then sval = (val = item).dump when /^(uint|CT|CCT|FT)/ then val = case wid when 1 then item.unpack('C').first when 2 then item.unpack('n').first when 3 then ("\x00" + item).unpack('N').first when 4 then item.unpack('N').first when 8 then vec = item.unpack('NN') (vec[0] << 64) + vec[1] end sval = (/^F+$/ === xd) ? 'missing' : val.to_s when /^sint$/ then val = if /^[89ABCDEF]/ === xd then case wid when 1 then -(item.unpack('C').first & ~0x80) when 2 then -(item.unpack('n').first & ~0x8000) when 3 then -(("\x00" + item).unpack('N').first & ~0x800000) when 4 then -(item.unpack('N').first & ~0x80000000) end else case wid when 1 then item.unpack('C').first when 2 then item.unpack('n').first when 3 then ("\x00" + item).unpack('N').first when 4 then item.unpack('N').first end end sval = (/^F+$/ === xd) ? 'missing' : val.to_s when /^YMDHMS$/ then val = Time.gm(*item.unpack('nC5')) sval = val.strftime('%Y%m%dT%H%M%SZ') when /^yMDHM$/ then i = item.unpack('C5') # !!! fake !!! i[0] += 1900 i[0] += 100 if i[0] < 1970 val = Time.gm(*i).strftime('%Y%m%dT%H%M%SZ') sval = "-" when /^!ct\/(\w+)$/ then nv = $1 c = format('%02u', item.unpack('C').first - 1) sval = val = @vars[nv].to_s.sub(/^\d\d/, c) when /^!lv\/(\w+)$/ then case @vars[$1].to_i when 101, 104, 106, 108, 110, 112, 114, 116, 120, 121, 128, 141 then val = sval = item.unpack('CC').join('/') else val = sval = item.unpack('n').first end when /^!ft\/(\w+)\/(\w+)\/(\w+)$/ then p1, p2, xtab = $1, $2, $3 sval = (val = item.unpack('C').first).to_s if val == 10 arg = (@vars[p1] << 8 + @vars[p2]) xputs " (= #{arg})" end fmt = fmt.dup fmt[:type] = xtab when /^float$/ then val = item.unpack('g').first sval = (/^F+$/ === xd) ? 'missing' : format('%14.7g', val) when /^scfloat$/ then f, v = item.unpack('cN') f = -128 - f if f < 0 val = v.to_f * (10.0 ** -f) sval = (/^F+$/ === xd) ? 'missing' : val.to_s when /^mfloat$/ then a = item.unpack('C').first sign = if (a & 0x80).zero? then 1.0 else -1.0 end a &= ~0x80 b = (item.unpack('N').first & 0x00FFFFFF) val = sign * b * 16.0 ** (a - 64 - 6) sval = (/^F+$/ === xd) ? 'missing' : format('%14.7g', val) when %r{^((?:[*/]\w+)+)$} then v = item.unpack('N').first v = -(v & 0x7FFF_FFFF) unless (v & 0x8000_0000).zero? v = v.to_f val = v val = v if catch(:missing) { todo = fmt[:type].dup while todo.sub!(/^([*\/])(\w+)/, '') op, var = $1, $2 case op when '*' then throw(:missing) unless @vars[var] v *= @vars[var] when '/' then throw(:missing) unless @vars[var] v /= @vars[var] end end nil } sval = (/^F+$/ === xd) ? 'missing' : sprintf('%14.8g', val) else throw(:malformat, "bad fmt type #{fmt[:type]}") end s = sprintf("%3.3s%03u: %-14.14s %-17.17s %s\n", tab + '_', ofs + 1, xd, sval, fmt[:desc]) xputs s case fmt[:label] when /=(\w+)/ then @vars[$1] = (/^F+$/ === xd ? nil : val) when /=@(\w+)/ then @vars[$1] = [] unless @vars[$1] @vars[$1] << val end msg = catch(:mistable) { case fmt[:type] when /^CC?T/ then dumpcode val, wid, fmt when /^FT/ then dumpflags val, wid, fmt end nil } xputs "\t# missing #{msg}" if msg end def dumpdata buf, ofs # puts [:dumpdata, buf.bytesize, ofs, @vars].inspect throw(:malformat, "DRT must be 0") unless @vars['DRT'] == 0 throw(:malformat, "BMI must be 255") if @vars['BMI'] throw(:malformat, "Ni missing") unless @vars['Ni'] throw(:malformat, "Nj missing") unless @vars['Nj'] ni, nj, r, e, d, nbits, scan = @vars.values_at(*%w{Ni Nj R E Ds Nb Sc}) bofs = ofs * 8 nj.times {|j| puts ":--- row #{j} ---" ncols = ni # adjacent rows different size if scan.bit7 unless (scan & 0x2).zero? # even rows (odd j) reduced by one if scan.bit8 # odd rows (even j) reduced by one if scan.bit8.zero? ncols -= 1 if ((j + (scan & 0x1)) % 2).zero? end ncols.times {|i| val = buf.bitslice_uint(bofs, nbits) y = (r + val * (2.0 ** e)) * (10 ** -d) printf ":%8u: %#03.4x %8.4g\n", i, val, y bofs += nbits } } end def decode tab, buf tpl = @@tab[tab].dup throw(:fatal, "table #{tab} missing") unless Array === tpl ofs = 0 idx = 0 while fmt = tpl[idx] wid = case fmt[:width] when /^\d+$/ then fmt[:width].to_i when /^-$/ then 0 when /^!(\w+)$/ then @vars[$1] when /^\[/ then 0 else throw(:malformat, "width syntax error #{fmt[:width]}") end case fmt[:type] when 'loop' then loopcount = case fmt[:width] when /^\[(\w+)\]$/ then @vars[$1] when /^\[(\w+)!(\w+)\]$/ then @vars[$1] ? 0 : @vars[$2] when /^\[(\w+),(\w+)\]$/ then @vars[$1] or @vars[$2] else throw(:malformat, "loop count syntax error #{fmt[:width]}") end sz = 1 sz = sz.succ while tpl[idx+sz] and tpl[idx+sz][:type] != 'endloop' tpl[idx+1, sz] = tpl[idx+1, sz-1] * loopcount when /^>/ then calltab = $'.gsub(/\[(\w+)\]/){ @vars[$1] } throw(:malformat, "template #{calltab} missing") unless @@tab[calltab] subtpl = @@tab[calltab].dup subtpl.push tpl[idx+1] tpl[idx+1, 1] = subtpl when /^@$/ then dumpdata buf, ofs if $VERBOSE when /^-$/ then # do nothing else dumpitem buf.byteslice(ofs, wid), wid, fmt, ofs, tab unless wid.zero? end ofs += wid idx = idx.succ end end end class GRIB1Dump < GRIB2Dump def initialize fp @fp = fp @begin = fp.pos @sec0 = fp.read(8) @msglen = @sec0.getui3(4) @endpos = @begin + @msglen - 4 @vars = {} tabinit end def readsec cur = @fp.pos if cur >= @endpos return nil end secl = (head = @fp.read(3)).getui3(0) throw(:malformat, "too big section #{secl}") if cur + secl > @endpos body = @fp.read(secl - 3) head + body end def run decode 'IDS1', @sec0 decode 'PDS1', pds = readsec flags = pds[7,1].unpack('C').first decode 'GDS1', readsec unless (flags & 0x80).zero? decode 'BMS1', readsec unless (flags & 0x40).zero? decode 'BDS1', readsec magic = @fp.read(4) throw(:malformat, "Missing magic '7777' at end of GRIB") if magic != '7777' puts "\f\n" end def decode tab, buf super if tab == 'PDS1' and buf.bytesize > 40 then # ECMWF hack extcode = buf[40,1].unpack('C').first xtab = format('PDX%03u', extcode) if @@tab[xtab] then xputs "\t--- PDX: ECMWF extension #{extcode} in PDS after octet 41 ---" decode xtab, buf[40..-1] else xputs "\t# ECMWF extension #{extcode} missing" end end end end class GRIBDump def initialize argv @argv = argv end def dispatch fp puts :dispatch if $DEBUG msg = catch(:malformat) { buf = fp.read(4) case gribver = buf.unpack('x3C').first when 1 then fp.seek(-8, IO::SEEK_CUR) GRIB1Dump.new(fp).run when 2 then fp.seek(-8, IO::SEEK_CUR) GRIB2Dump.new(fp).run else puts "bad version #{gribver}" end nil } puts msg if msg end def dumpfile fnam puts "File #{fnam}" File.open(fnam, 'r') { |fp| fp.binmode state = 0 while c = fp.getc puts [:scan, state, c].inspect if $DEBUG case [state, c] when [0, ?G] then state = ?G when [?G, ?R] then state = ?R when [?R, ?I] then state = ?I when [?I, ?B] then dispatch(fp); state = 0 else state = 0 end end } end def run msg = catch(:fatal) { for fnam in @argv dumpfile(fnam) end nil } puts msg if msg rescue Errno::EPIPE end end GRIBDump.new(ARGV).run __END__