#!/usr/bin/ruby require 'selsurf' class GradsWX < SelSurf WXMAP = { # 0: EMPTY 0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 39, # 39: SMOKE - no good for haze but GrADS don't have it # 36: SAND RAISED BY WIND 5 => 39, 6 => 36, 7 => 36, 8 => 36, 9 => 36, # 34: LIGHT FOG - we really need normal FOG # 38: LIGHTNING BUT NO THUNDER 10 => 34, 11 => 34, 12 => 34, 13 => 38, 14 => 16, # 2: THUNDER BUT NO LIGHTNING # 1: FUNNEL CLOUD 15 => 16, 16 => 16, 17 => 2, 18 => 15, 19 => 1, 20 => 28, 21 => 16, 22 => 21, 23 => 37, 24 => 13, 25 => 15, 26 => 19, 27 => 31, 28 => 34, 29 => 2, 30 => 36, 31 => 36, 32 => 36, 33 => 36, 34 => 36, 35 => 36, 36 => 35, 37 => 35, 38 => 35, 39 => 35, 40 => 34, 41 => 34, 42 => 34, 43 => 34, 44 => 34, 45 => 34, 46 => 34, 47 => 34, 48 => 34, 49 => 34, 50 => 28, 51 => 28, 52 => 29, 53 => 29, 54 => 30, 55 => 30, 56 => 27, 57 => 26, 58 => 28, 59 => 29, 60 => 16, 61 => 16, 62 => 17, 63 => 17, 64 => 18, 65 => 19, 66 => 13, 67 => 12, 68 => 37, 69 => 37, 70 => 21, 71 => 21, 72 => 22, 73 => 22, 74 => 23, 75 => 23, 76 => 33, 77 => 25, 78 => 33, 79 => 24, 80 => 15, 81 => 14, 82 => 14, 83 => 19, 84 => 20, 85 => 19, 86 => 20, 87 => 31, 88 => 32, 89 => 31, 90 => 32, 91 => 3, 92 => 8, 93 => 4, 94 => 9, 95 => 3, 96 => 6, 97 => 8, 98 => 5, 99 => 11, } MISS = -999.0 def initialize super @seq = "A000" end def ddff2uv dd, ff, iw return [MISS, MISS] unless (Numeric === dd and Numeric === ff) return [0.0, 0.0] if (dd.zero? or ff.zero?) ff *= 1.94384 unless 3..4 === iw drad = dd / 18.0 * Math::PI u = -ff * Math::sin(drad) v = -ff * Math::cos(drad) [u, v] end def convert ent, dcd ent[:id] = dcd['@ID'] ent[:mimj] = dcd['@MiMj'] ent[:t] = if Numeric === dcd['TTT'] then dcd['TTT'] * 0.1 else MISS end ent[:d] = if Numeric === dcd['Td.3'] then dcd['Td.3'] * 0.1 else MISS end ent[:slp] = if Numeric === dcd['P.4'] then dcd['P.4'] else MISS end ent[:dp] = if Numeric === dcd['ppp'] then dcd['ppp'] else MISS end ent[:cld] = case dcd['N'] when 0..1 then 20 # clear when 2..6 then 21 # scattered (METAR's SCT is 3 to 4 oktas) when 7 then 22 # BKN when 8 then 23 # OVC when 9 then 24 # obscured else if 'BOGUS' === dcd['@ID'] then 6 # X mark elsif 4..7 === dcd['ix'] then 8 # triangle else 25 # 'M' for real missing end end ww = dcd['ww'] if ww.nil? and [8, 24].include?(ent[:cld]) and ((1..989) === dcd['RRR']) if ent[:t] > 5 then ww = 60 elsif ent[:t] > 0 then ww = 68 else ww = 70 end end ent[:wx] = (WXMAP[ww] || MISS) u, v = ddff2uv(dcd['dd'], dcd['ff'], dcd['iw']) ent[:u] = u ent[:v] = v ent[:VV] = case dcd['VV'] when 0..2 then dcd['VV'] * 0.1 when 0..55 then (dcd['VV'] * 0.4).floor * 0.25 when 56..80 then dcd['VV'] - 50 when 81..89 then dcd['VV'] * 5 - 370 when 90 then 0.02 when 91 then 0.05 when 92 then 0.2 when 93 then 0.5 when 94 then 1.0 when 95 then 2.0 when 96 then 4.0 when 97 then 10.0 when 98 then 20.0 when 99 then 50.0 else MISS end unless ent.values_at(:slp, :t, :u) == [MISS, MISS, MISS] ent[:miss] = false end end def saveent dfp, ent = nil if ent.nil? then dfp.write ['', 0.0, 0.0, 0.0, 0, 0].pack('a8fffLL') return end #STDERR.puts ent.inspect if ent[:id] == '57687' return if ent[:miss] unless ent[:flalo] STDERR.puts "#{ent[:id]}: missing coords" return end ids = case ent[:id] when /^\d\d\d\d\d$/ then [ent[:id][1,4], '-', ent[:id][0,1]].join when /^SHIP$/ then 'SHIP' + @seq when /^BOGUS$/ then 'GUES' + @seq when /^\w+$/ then case ent[:mimj] when /BBXX/ then 'SHIP' + ent[:id] when /ZZYY/ then 'BUOY' + ent[:id] end else 'XXXX' + @seq end @seq = @seq.succ lat = ent[:flalo][0] lon = ent[:flalo][1] dfp.write [ids, lat, lon, 0.0, 1, 1].pack('a8fffLL') rec = [ent[:slp], ent[:dp], ent[:t], ent[:d], ent[:u], ent[:v], ent[:cld], ent[:wx], ent[:VV]] if $VERBOSE printf("%-8.8s|%+5.1f|%+6.1f|%5.0f|%+5.0f|%+6.1f|%+6.1f|%+6.1f|%+6.1f|%5d|%5d|%02d\n", ids, lat, lon,*rec) end dfp.write rec.pack('f9') end def report gt = Time.gm(@year, @mon, @yy, @gg).strftime('%Hz%d%b%Y') File.open("zgrads.ctl", "w") {|cfp| cfp.write <<-ENDCTL DSET ^zgrads.dat DTYPE station STNMAP zgrads.map UNDEF #{MISS} TITLE station report #{gt} TDEF 1 linear #{gt} 6hr VARS 9 ps 0 99 Surface Pressure [10 Pa] pdel 0 99 Surface Pressure Change [10 Pa] ts 0 99 Surface Temperature [C] ds 0 99 Surface Dewpoint T [C] us 0 99 Surface U-Wind [knot] vs 0 99 Surface V-Wind [knot] cld 0 99 Cloudiness [GrADS code] wx 0 99 Significant Weather [GrADS code] vis 0 99 Visibility [Code Table 4377] ENDVARS ENDCTL } File.open('zgrads.dat', 'w') {|dfp| dfp.binmode @dcd[:fixed].each {|ent| saveent dfp, ent } @dcd[:mobile].each {|ent| saveent dfp, ent } saveent dfp } end end GradsWX.new.run(ARGV) if $0 == __FILE__