require 'strscan' class Hash def to_ltsv keys.sort.map{|k| v = self[k] sv = case v when Float then format('%3.1f', v) else v.to_s end [k, sv].join(':') }.join("\t") end def to_yaml ["---", keys.sort.map{|k| v = self[k] qk = case k when /\A[A-Za-z][-\w\/@.]+\z/n then k else "'#{k}'" end qv = case v when Float then format('%3.1f', v) when Numeric then v.to_s when /\A[A-Za-z][-\w\/@.]*\z/n then v else "'#{v}'" end [qk, qv].join(': ') }].join("\n") end end class Tac def self.atoi str case str when /^\d+$/n then str.to_i when /^0?\/+$/n then '/' when /^[0-9][0-9]+\/+$/n then str.tr('/', '0').to_i else '*' end end # snTTT of FM 12 SYNOP etc - top digit is sign def self.atosi sgn, str i = atoi(str) return i unless Integer === i case sgn when /^0$/n then i when /^1$/n then -i else '/' end end # TTTa of FM 35 TEMP etc - last digit odd if negative def self.atoia str case str when /^\d*[02468]$/n then str.to_i when /^\d*[13579]$/n then -str.to_i when /\//n then '/' else '*' end end # DD of FM 35 TEMP etc - unit depends on top digit def self.atoDD str case str when /^[5-9]\d+$/n then str.to_i - 50 when /^\d*$/n then str.to_i * 0.1 when /\//n then '/' else '*' end end # ddfff of FM 35 TEMP etc - middle digit +5'ed to represent 5 degree # returns array [direction/deg, wind speed] def self.atoddfff str case str when /\A[0-9][0-9][56789][0-9][0-9]\z/n then [str[0,2].to_i * 10 + 5, str[2,3].to_i - 500] when /[0-9]{5}\z/n then [str[0,2].to_i * 10, str[2,3].to_i] else ['/', '/'] end end # PPPP etc of FM 71 CLIMAT def self.atoPPPP str case str when /^0\d+$/n then str.to_i + 10_000 when /^\d+$/n then str.to_i when /^\/+$/n then '/' else '*' end end # stubs to be overriden def ex_init; nil end def decode; nil end def heritage; nil end def heritage= val; nil end def initialize(ahl, mimj, heritage) @ahl = ahl @mimj = mimj @hold = [] @fnam = nil @hold.push heritage if heritage @dcd = { "AHL" => @ahl, "@MiMj" => @mimj } @msgs = [] @frozen = nil @state = nil @z = nil ex_init() end def eputs msg h = {'!'=>'ERR', 'AHL'=>@ahl, 'MiMj'=>@mimj, 'ID'=>@dcd['@ID'], '~'=>msg} @msgs.push [nil, h] true end def wputs msg h = {'!'=>'WRN', 'AHL'=>@ahl, 'MiMj'=>@mimj, 'ID'=>@dcd['@ID'], '~'=>msg} @msgs.push [nil, h] true end def fnam= val @fnam = val end def push ttype, token throw(:BUG, "push(#{ttype}, #{token}) after freeze") if @frozen @hold.push token end def flush callback return if @frozen @dcd['~'] = @hold.join(' ') decode @msgs.each { |tag, val| callback.call(tag, val) } callback.call(@mimj, @dcd) @frozen = true end class METAR < self def decode @dcd['@ID'] = @hold[0] @dcd['@ID'] = @hold[1] if /^COR$/n === @dcd['@ID'] end end SPECI = METAR # SHIP class BBXX < self State = { nil => :D_D, :YYGGi => :_99LLL, :QLLLL => :iihVV, :hhhhi => :iihVV } REG_YYGGi = /^([012][0-9]|3[01])([01][0-9]|2[0-3])[0134\/]/n # these groups are recognised without condition def dcd_preempt(group) case group when /^(?:NIL|nil)$/n then return (@state = :nil) when /^555$/n then return (@state = :sec5) end nil end # sections 0 and 1 are decoded by position def dcd_bypos(i, group) hit = case @state when :D_D group2 = @hold[i+1] if REG_YYGGi =~ group and /^99/n =~ group2 @dcd['@ID'] = '*' @dcd['YY'] = Tac.atoi(group[0, 2]) @dcd['GG'] = Tac.atoi(group[2, 2]) @dcd['iw'] = Tac.atoi(group[4, 1]) @state = self.class::State[:YYGGi] else @dcd['@ID'] = group @state = :YYGGi end when :YYGGi case group when REG_YYGGi then @dcd['YY'] = Tac.atoi(group[0, 2]) @dcd['GG'] = Tac.atoi(group[2, 2]) @dcd['iw'] = Tac.atoi(group[4, 1]) @state = self.class::State[@state] when /^HSSS$/n then wputs("ignored (#{group}) where YYGGi expected") else eputs("stop word (#{group}) where YYGGi expected") @state = :err end when :IIiii @dcd['@ID'] = group @state = self.class::State[@state] when :_99LLL @dcd['La.3'] = Tac.atoi(group[2, 3]) @state = :QLLLL when :QLLLL @dcd['Qc'] = Tac.atoi(group[0, 1]) @dcd['Lo.4'] = Tac.atoi(group[1, 4]) @state = self.class::State[@state] when :MMMUU @dcd['MMM'] = Tac.atoi(group[0, 3]) @dcd['ULa'] = Tac.atoi(group[3, 1]) @dcd['ULo'] = Tac.atoi(group[4, 1]) @state = :hhhhi when :hhhhi h = Tac.atoi(group[0, 4]) @dcd['im'] = im = Tac.atoi(group[4, 1]) if Integer === h case im when 5..8 then h = (h * 0.3048 + 0.5).floor end end @dcd['h0.4'] = h @state = self.class::State[@state] when :iihVV @dcd['iR'] = Tac.atoi(group[0, 1]) @dcd['ix'] = Tac.atoi(group[1, 1]) @dcd['h'] = Tac.atoi(group[2, 1]) @dcd['VV'] = Tac.atoi(group[3, 2]) @state = :Nddff when :_0ddff @dcd['dd'] = Tac.atoi(group[1, 2]) @dcd['ff'] = Tac.atoi(group[3, 2]) @state = :sec1 when :Nddff @dcd['N'] = Tac.atoi(group[0, 1]) @dcd['dd'] = Tac.atoi(group[1, 2]) @dcd['ff'] = Tac.atoi(group[3, 2]) @state = :sec1 end hit end # tokens causing state change - recognised only after "bypos" is done def dcd_secleader(group) hit = case group when /^222[0-9\/][0-9\/]$/n then @state = :sec2 when /^222$/n then @state = :sec2 when /^333$/n then @state = :sec3 when /^444$/n then @state = :sec4 when /^55$/n then wputs("(55) for (555)") @state = :sec5 when /^(REMARKS|CHECK)$/n then eputs("stop word (#{group}) in #{@state}") @state = :err end hit end # the rest of decode def dcd_main(group) if :sec1 === @state then case group when /^00(...)/n then @dcd['ff'] = Tac.atoi($1) when /^1(.)(...)/n then @dcd['TTT'] = Tac.atosi($1, $2) when /^29(...)/n then @dcd['UUU'] = Tac.atoi($1) when /^2(.)(...)/n then @dcd['Td.3'] = Tac.atosi($1, $2) when /^3(....)/n then v = Tac.atoi($1) case v when String then :ignore when 0..799 then v += 10_000 when 800..900 then v += 9000 end @dcd['P0.4'] = v when /^4([09\/]...)/n then v = Tac.atoi($1) case v when String then :ignore when 0..799 then v += 10_000 when 800..900 then v += 9000 end @dcd['P.4'] = v when /^4([12578])(...)/n then @dcd['a3'] = Tac.atoi(group[1, 1]) @dcd['hhh'] = Tac.atoi(group[2, 3]) when /^40[0-9][0-9]$/n then wputs("sec1 - zero inserted (#{group}) for 4PPPP") @dcd['P.4'] = Tac.atoi(group[1, 3]) + 10_000 when /^5(.)(...)/n then a, ppp = $1, $2 @dcd['a'] = Tac.atoi(a) @dcd['ppp'] = Tac.atoi(ppp) if /^[5-8]/n === a and Numeric === @dcd['ppp'] then @dcd['ppp'] = -@dcd['ppp'] end when /^6(...)(.)/n then @dcd['RRR'] = Tac.atoi(group[1, 3]) @dcd['t'] = Tac.atoi(group[4, 1]) when /^7(..)(.)(.)/n then @dcd['ww'] = Tac.atoi(group[1, 2]) @dcd['W1'] = Tac.atoi(group[3, 1]) @dcd['W2'] = Tac.atoi(group[4, 1]) when /^8(.)(.)(.)(.)/n then @dcd['Nh'] = Tac.atoi(group[1, 1]) @dcd['CL'] = Tac.atoi(group[2, 1]) @dcd['CM'] = Tac.atoi(group[3, 1]) @dcd['CH'] = Tac.atoi(group[4, 1]) when /^9[0-9\/]{4}$/n then @dcd['GG'] = Tac.atoi(group[1, 2]) @dcd['gg'] = Tac.atoi(group[3, 2]) when /^\/\/\/\/\/$/n then :ignore else eputs("sec1 - bad group (#{group})") end elsif :sec2 === @state then case group when /^0([0246\/])(...)$/n then @dcd['ss'] = Tac.atoi(group[1, 1]) @dcd['Tw.3'] = Tac.atoi(group[2, 3]) when /^0([1357])(...)$/n then @dcd['ss'] = Tac.atoi(group[1, 1]) @dcd['Tw.3'] = Tac.atosi('1', group[2, 3]) when /^1....$/n then @dcd['PwaPwa'] = Tac.atoi(group[1, 2]) @dcd['HwaHwa'] = Tac.atoi(group[3, 2]) when /^2....$/n then @dcd['PwPw'] = Tac.atoi(group[1, 2]) @dcd['HwHw'] = Tac.atoi(group[3, 2]) when /^3....$/n then @dcd['dw1dw1'] = Tac.atoi(group[1, 2]) @dcd['dw2dw2'] = Tac.atoi(group[3, 2]) when /^4....$/n then @dcd['Pw1Pw1'] = Tac.atoi(group[1, 2]) @dcd['Hw1Hw1'] = Tac.atoi(group[3, 2]) when /^5....$/n then @dcd['Pw2Pw2'] = Tac.atoi(group[1, 2]) @dcd['Hw2Hw2'] = Tac.atoi(group[3, 2]) when /^6....$/n then @dcd['Is'] = Tac.atoi(group[1, 1]) @dcd['EsEs'] = Tac.atoi(group[2, 2]) @dcd['Rs'] = Tac.atoi(group[4, 1]) when /^70...$/n then @dcd['Hwa.3'] = Tac.atoi(group[2, 3]) when /^8(.)(...)$/n then @dcd['Tb.3'] = Tac.atosi($1, $2) when /^\/{5}$/n then :ignore when /^ICING$/n then @dcd['ICING'] = [] @state = :sec2icing when /^ICE[0-9\/]{5}$/n then @dcd['ICE'] = [group[3, 5]] when /^ICE$/n then @dcd['ICE'] = [] @state = :sec2ice else wputs("sec2 - bad group (#{group})") end elsif :sec2icing === @state then @dcd['ICING'] << group elsif :sec2ice === @state then @dcd['ICE'] << group elsif :sec3 === @state then case group when /^0..../n then :ignore end elsif :sec4 === @state then elsif :sec5 === @state then end end # correcion of coordinate sign and post-decode correction def dcd_fixup case @dcd['Qc'] when 1 then :ignore when 3 then @dcd['La.3'] *= -1 if Numeric === @dcd['La.3'] when 5 then @dcd['La.3'] *= -1 if Numeric === @dcd['La.3'] @dcd['Lo.4'] *= -1 if Numeric === @dcd['Lo.4'] when 7 then @dcd['Lo.4'] *= -1 if Numeric === @dcd['Lo.4'] end for kw in %w(ICING ICE) @dcd[kw] = @dcd[kw].join(' ') if @dcd.include?(kw) end end def decode @state = self.class::State[nil] puts "@ state := #{@state}" if $DEBUG @hold.size.times {|i| group = @hold[i] hit = dcd_preempt(group) next if hit # section 0 and 1 hit = dcd_bypos(i, group) puts "@ #{group} -> #{@state}" if hit and $DEBUG next if hit # section leader group hit = dcd_secleader(group) next if hit # section contents dcd_main(group) } dcd_fixup end end # SYNOP MOBILE class OOXX < BBXX State = BBXX::State.dup State[:QLLLL] = :MMMUU end # SYNOP class AAXX < BBXX def heritage case @hold.first when /^HSSS$/n then @hold[1] else @hold.first end end def heritage= val @hold.push val if val end State = BBXX::State.dup State[nil] = :YYGGi State[:YYGGi] = :IIiii State[:IIiii] = :iihVV end # BUOY class ZZYY < BBXX State = BBXX::State.dup State[nil] = :StationID def dcd_bypos i, group hit = case @state when :StationID @dcd['@ID'] = group @state = :YYMMJ when :YYMMJ @dcd['YY'] = Tac.atoi(group[0, 2]) @dcd['MM'] = Tac.atoi(group[2, 2]) @dcd['J'] = Tac.atoi(group[4, 1]) @state = :GGggi when :GGggi @dcd['GG'] = Tac.atoi(group[0, 2]) @dcd['gg'] = Tac.atoi(group[2, 2]) @dcd['iw'] = Tac.atoi(group[4, 1]) @state = :QLLLLLa when :QLLLLLa @dcd['Qc'] = Tac.atoi(group[0, 1]) @dcd['La.5'] = Tac.atoi(group[1, 5]) @state = :LLLLLL when :LLLLLL @dcd['Lo.6'] = Tac.atoi(group) @state = :_6QQQ_ when :_6QQQ_ if /^6...\//n === group then @dcd['Ql'] = Tac.atoi(group[1, 1]) @dcd['Qt'] = Tac.atoi(group[2, 1]) @dcd['Qa'] = Tac.atoi(group[3, 1]) true else nil end end hit end def dcd_secleader group hit = case group when /^111[0-9\/][0-9\/]$/n then @dcd['Qd_1'] = Tac.atoi(group[3, 1]) @dcd['Qx_1'] = Tac.atoi(group[4, 1]) @state = :sec1 when /^222[0-9\/][0-9\/]$/n then @dcd['Qd_2'] = Tac.atoi(group[3, 1]) @dcd['Qx_2'] = Tac.atoi(group[4, 1]) @state = :sec2 when /^333[0-9\/][0-9\/]$/n then @dcd['Qd1'] = Tac.atoi(group[3, 1]) @dcd['Qx2'] = Tac.atoi(group[4, 1]) @state = :sec3 when /^444$/n then @state = :sec4 when /^555$/n then @state = :sec5 end hit end def dcd_fixup case @dcd['Qc'] when 1 then :ignore when 3 then @dcd['La.5'] *= -1 when 5 then @dcd['La.5'] *= -1; @dcd['Lo.6'] *= -1 when 7 then @dcd['Lo.6'] *= -1 end end def dcd_main(group) if :sec1 === @state case group when /^0..../n then @dcd['dd'] = Tac.atoi(group[1, 2]) @dcd['ff'] = Tac.atoi(group[3, 2]) when /^1([01\/])(...)/n then @dcd['TTT'] = Tac.atosi($1, $2) when /^29(...)/n then @dcd['UUU'] = Tac.atoi($1) when /^2([01\/])(...)/n then @dcd['Td.3'] = Tac.atosi($1, $2) when /^3(....)/n then v = Tac.atoi($1) case v when String then :ignore when 0..799 then v += 10_000 when 800..900 then v += 9000 end @dcd['P0.4'] = v when /^4([09\/]...)/n then v = Tac.atoi($1) case v when String then :ignore when 0..799 then v += 10_000 when 800..900 then v += 9000 end @dcd['P.4'] = v when /^5(.)(...)/n then a, ppp = $1, $2 @dcd['a'] = Tac.atoi(a) @dcd['ppp'] = Tac.atoi(ppp) if /^[5-8]/n === a and Numeric === @dcd['ppp'] then @dcd['ppp'] = -@dcd['ppp'] end end elsif :sec2 === @state case group when /^0([01\/])(...)$/n then @dcd['Tw.3'] = Tac.atosi($1, $2) when /^1....$/n then @dcd['PwaPwa'] = Tac.atoi(group[1, 2]) @dcd['HwaHwa'] = Tac.atoi(group[3, 2]) when /^20...$/n then @dcd['Pwa.3'] = Tac.atoi(group[2, 3]) when /^21...$/n then @dcd['Hwa.3'] = Tac.atoi(group[2, 3]) end elsif :sec3 === @state case group when /^8887(.)/n then @dcd['k2'] = Tac.atoi($1) z = nil @state = :sec3TS when /^66.9./n then @dcd['k6'] = Tac.atoi(group[2, 1]) @dcd['k3'] = Tac.atoi(group[4, 1]) z = nil @state = :sec3dc1 else eputs("bad (#{group}) in Sec3") end elsif :sec3TS === @state case group when /^2..../n then z = Tac.atoi(group[1, 4]) when /^3..../n then @dcd['T0@%u' % z] = Tac.atoi(group[1, 4]) when /^4..../n then @dcd['S0@%u' % z] = Tac.atoi(group[1, 4]) else eputs("sec3TS (#{group})") end elsif :sec3dc1 === @state case group when /^2..../n then z = Tac.atoi(group[1, 4]) @state = :sec3dc2 else eputs("sec3dc1 (#{group})") end elsif :sec3dc2 === @state @dcd['d0@%u' % z] = Tac.atoi(group[0, 2]) @dcd['c0@%u' % z] = Tac.atoi(group[2, 3]) @state = :sec3dc1 end end end # TEMP SHIP Part A class UUAA < BBXX State = { nil => :D_D, :YYGGi => :_99LLL, :QLLLL => :MMMUU, :MMMUU => :temp2ini, } TPrefix = 't' WPrefix = 'w' Idname = 'Id' PFactor = 10 LevelBase = '00' # PPP in TEMP Parts A and B def self.atoPPP str case str when /\A0[0-9][0-9]\z/n then 1000 + str.to_i when /\A[1-9][0-9][0-9]\z/n then str.to_i else '/' end end # "i" is "id" that can be any digit or solidus # YY is shifted +50 if wind is reported in knots # id is omitted in RUHB REG_YYGGi = /^([012567][0-9]|[38][01])([01][0-9]|2[0-3])[0-9\/]?/n def dcd_bypos(i, group) hit = case @state when :D_D @dcd['@ID'] = group @state = :YYGGi when :YYGGi if !(@dcd['@ID']) and /^NIL$/n === @hold[i+1] and /^5/n === group then @dcd['@ID'] = group wputs("missing YYGGi") @state = :YYGGi elsif REG_YYGGi === group then @dcd['YY'] = Tac.atoi(group[0, 2]) @dcd['GG'] = Tac.atoi(group[2, 2]) @dcd[self.class::Idname] = Tac.atoi(group[4, 1]) wputs("Id omitted") if $DEBUG and group.length == 4 @state = self.class::State[@state] elsif '/////' == group then @dcd['YY'] = @dcd['GG'] = '/' @dcd[self.class::Idname] = '/' @state = self.class::State[@state] else eputs("stop word (#{group}) where YYGGi expected") @state = :err end @state when :IIiii @dcd['@ID'] = group @state = self.class::State[@state] when :_99LLL @dcd['La.3'] = Tac.atoi(group[2, 3]) @state = :QLLLL when :QLLLL @dcd['Qc'] = Tac.atoi(group[0, 1]) @dcd['Lo.4'] = Tac.atoi(group[1, 4]) @state = self.class::State[@state] when :MMMUU @dcd['MMM'] = Tac.atoi(group[0, 3]) @dcd['ULa'] = Tac.atoi(group[3, 1]) @dcd['ULo'] = Tac.atoi(group[4, 1]) @state = self.class::State[@state] when :hhhhi h = Tac.atoi(group[0, 4]) @dcd['im'] = im = Tac.atoi(group[4, 1]) if Integer === h case im when 5..8 then h = (h * 0.3048 + 0.5).floor end end @dcd['h0.4'] = h @state = self.class::State[@state] else nil end hit end # tokens causing state change - recognised only after "bypos" is done def dcd_secleader(group) hit = case group when /^21212$/n then :temp6ini when /^31313$/n then :temp7 when /^41414$/n then :temp8 when /^51515$/n then :temp91 when /^61616$/n then :temp101 when /^62626$/n then :temp102 end @state = hit if hit hit end def atohhh(str, p) i = Tac.atoi(str) return i if String === i case p when 10 then (i < 500) ? (30000 + 10 * i) : (20000 + 10 * i) when 10...50 then (20000 + 10 * i) when 50 then (i < 500) ? (20000 + 10 * i) : (10000 + 10 * i) when 50...225 then (20000 + 10 * i) when 225...350 then (i < 500) ? (10000 + 10 * i) : (10 * i) when 350..500 then 10 * i when 500...650 then (i < 500) ? (4000 + i) : (3000 + i) when 650...750 then (i < 500) ? (3000 + i) : (2000 + i) when 750...825 then (i < 500) ? (2000 + i) : (1000 + i) when 825...890 then 1000 + i when 890...990 then i when 990...1010 then (i < 500) ? i : (500 - i) else 30000 + 10 * i end end # FM 36 TEMP SHIP Sections 2--10 def dcd_main(group) istate = @state case @state when :temp2ini, :temp2skip, :temp3ini case group # 7 hPa is reported at least by Canada and U.S. when /^(00|92|85|70|50|40|30|25|20|15|10|07)/n then z = Tac.atoi(group[0,2]) case z when 0 then z = 1000 when 92 then z = 925 when Numeric then z *= self.class::PFactor end @z = "p#{z}" @dcd['hhh@' + @z] = atohhh(group[2,3], z) @state = :temp2TTTDD when /^99/n then @z = 'SURF' @dcd['PPP@' + @z] = self.class::atoPPP(group[2,3]) @state = :temp2TTTDD when /\A88999\z/n then @z = 'TROP' while @dcd["PPP@#{@z}"] @z = @z.succ end @dcd["PPP@#{@z}"] = '/' @state = :temp3ini when /\A88/n then @z = 'TROP' while @dcd["PPP@#{@z}"] @z = @z.succ end h = self.class::atoPPP(group[2,3]) @dcd["PPP@#{@z}"] = h @state = :temp3TTTDD when /\A77999\z/n then @dcd["PPP@MAXW"] = '/' @state = :temp2ini when /^(66|77)/n then @z = 'MAXW' @dcd['TOP@' + @z] = 1 if /^66/n === group h = self.class::atoPPP(group[2,3]) @dcd['PPP@' + @z] = h @state = :temp4 #when /\A(44|55)/n then when /\A\/{5}\z/n then :ignore else if not :temp3ini === @state eputs("stop word (#{group}) in :temp2ini") @state = :err end end when :temp2TTTDD @dcd['TTTa@' + @z] = Tac.atoia(group[0,3]) @dcd['DD@' + @z] = Tac.atoDD(group[3,2]) @state = (@dcd['%noWind'] ? :temp2skip : :temp2ddfff) case @dcd['Id'] when 5 then @dcd['%noWind'] = @z if /^p50/n === @z when 4 then @dcd['%noWind'] = @z if /^p400/n === @z when 3 then @dcd['%noWind'] = @z if /^p30/n === @z when 2 then @dcd['%noWind'] = @z if /^p20/n === @z end when :temp3TTTDD @dcd['TTTa@' + @z] = Tac.atoia(group[0,3]) @dcd['DD@' + @z] = Tac.atoDD(group[3,2]) @state = :temp3ddfff when :temp2ddfff dd = Tac.atoi(group[0,2]) if Integer === dd and dd > 36 then if dd == 88 then @z = 'TROP' else @z = format('p%u', dd * PFactor) @dcd['%noWind'] = @z unless @dcd['%noWind'] end h = Tac.atoi(group[2,3]) h = '/' if h == 999 @dcd["PP@#{@z}"] = h @state = :temp2TTTDD else @dcd["dd@#{@z}"], @dcd["fff@#{@z}"] = Tac.atoddfff(group) @state = :temp2ini end when :temp3ddfff @dcd["dd@#{@z}"], @dcd["fff@#{@z}"] = Tac.atoddfff(group) when :temp4 case group when /^[0123]/n then @dcd["dd@#{@z}"], @dcd["fff@#{@z}"] = Tac.atoddfff(group) when /^4/n then @dcd['vbvb@' + @z] = Tac.atoi(group[1,2]) @dcd['vava@' + @z] = Tac.atoi(group[3,3]) end when :temp5ini if /\A([0-9])\1/n === group irep = self.class::LevelBase begin @z = [self.class::TPrefix, irep, group[0,1]].join irep = irep.succ end while @dcd["PPP@#{@z}"] @dcd["PPP@#{@z}"] = self.class::atoPPP(group[2,3]) @state = :temp5TTTDD else eputs("unknown (#{group}) in TEMP Sec5") end when :temp5TTTDD t = Tac.atoia(group[0,3]) @dcd['TTTa@' + @z] = t @dcd['DD@' + @z] = Tac.atoDD(group[3,2]) @state = :temp5ini when :temp6ini if /\A([0-9])\1/n === group irep = self.class::LevelBase begin @z = [self.class::WPrefix, irep, group[0,1]].join irep = irep.succ end while @dcd["PPP@#{@z}"] @dcd["PPP@#{@z}"] = self.class::atoPPP(group[2,3]) @state = :temp6ddfff else eputs("unknown (#{group}) in TEMP Sec6") end when :temp6ddfff @dcd["dd@#{@z}"], @dcd["fff@#{@z}"] = Tac.atoddfff(group) @state = :temp6ini when :temp7 case group when /^[0-7]/n then @dcd['sr'] = Tac.atoi(group[0,1]) @dcd['rara'] = Tac.atoi(group[1,2]) @dcd['SaSa'] = Tac.atoi(group[3,2]) when /^8/n then @dcd['GG/7'] = Tac.atoi(group[1,2]) @dcd['gg/7'] = Tac.atoi(group[3,2]) when /^9/n then @dcd['TwTwTw'] = Tac.atosi(group[1,1], group[2,3]) end when :temp8 @dcd['Nh'] = Tac.atoi(group[0,1]) @dcd['CL'] = Tac.atoi(group[1,1]) @dcd['h'] = Tac.atoi(group[2,1]) @dcd['CM'] = Tac.atoi(group[3,1]) @dcd['CH'] = Tac.atoi(group[4,1]) when :temp91 case group when /\A101([45][0-9])\z/n then @dcd['Err/4'] = Tac.atoi($1) when /\A1018([012])\z/n then @dcd['Corr/4'] = Tac.atoi($1) when /\A10164\z/n then @state = :temp91na64 when /\A10190\z/n then @state = :temp91na90 when /\A10194\z/n then @state = :temp91na94 when /\A10196\z/n then @state = :temp91na96 when /\A11[0-9]{3}\z/n then @dcd['PPP@h900'] = self.class::atoPPP(group[2,3]) @state = :temp91hk1 when /\A77[0-9]{3}\z/n then @dcd['hhh@p775'] = atohhh(group[2,3], 775) @state = :temp91af1 when /\A92[0-9\/]{3}\z/n then @dcd['hhh@p92x'] = atohhh(group[2,3], 925) @state = :temp91af11 else eputs("unknown (#{group}) in Sec9") end when :temp91na64 case group when /\A000(?:50|[0-4]\d)\z/n then @dcd['LI/4'] = group.to_i when /\A\d+\z/n then @dcd['LI/4'] = 50 - group.to_i else eputs("unknown (#{group}) in Sec9 RA4 64") end @state = :temp91 when :temp91na90 z = Tac.atoi(group[0,2]) @dcd["hhh@xp#{z}"] = atohhh(group[2,3], z) @state = :temp91 when :temp91na94 z = 'h..1500' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91na94b when :temp91na94b z = 'h..3000' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91 when :temp91na96 @z = Tac.atoi(group[0,2]) * 10 @dcd["hhh@ep#{@z}"] = atohhh(group[2,3], @z) @state = :temp91na96TTTDD when :temp91na96TTTDD @dcd["TTTa@ep#{@z}"] = Tac.atoia(group[0,3]) @dcd["DD@ep#{@z}"] = Tac.atoDD(group[3,2]) @state = :temp91na96ddfff when :temp91na96ddfff @dcd["dd@ep#{@z}"], @dcd["fff@ep#{@z}"] = Tac.atoddfff(group) @z = nil @state = :temp91na96 when :temp91hk1 z = 'h900' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91hk2 when :temp91hk2 case group when '22800' then @state = :temp91hk3 when '33600' then @state = :temp91hk5 else eputs("unexpected (#{group}) in TEMP Sec9 11.2") @state = :err end when :temp91hk3 z = 'p800' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91hk4 when :temp91hk4 if group == '33600' then @state = :temp91hk5 else eputs("unexpected (#{group}) in TEMP Sec9 11.4") @state = :err end when :temp91hk5 z = 'p600' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91 when :temp91af11 @dcd['TTTa@p92x'] = Tac.atoia(group[0,3]) @dcd['DD@p92x'] = Tac.atoDD(group[3,2]) @state = :temp91af12 when :temp91af12 z = 'p92x' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91af13 when :temp91af13 if /^77/n === group @dcd['hhh@p775'] = atohhh(group[2,3], 775) @state = :temp91af1 else eputs("unexpected (#{group}) in TEMP Sec9 92.3") @state = :err end when :temp91af1 @dcd['TTTa@p775'] = Tac.atoia(group[0,3]) @dcd['DD@p775'] = Tac.atoDD(group[3,2]) @state = :temp91af2 when :temp91af2 z = 'p775' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91af3 when :temp91af3 if /^60/n === group @dcd['hhh@p600'] = atohhh(group[2,3], 600) @state = :temp91af4 else eputs("unexpected (#{group}) in TEMP Sec9 77.3") @state = :err end when :temp91af4 @dcd['TTTa@p600'] = Tac.atoia(group[0,3]) @dcd['DD@p600'] = Tac.atoDD(group[3,2]) @state = :temp91af5 when :temp91af5 z = 'p600' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp91 when :temp101 case group when /\A11999\z/n then @state = :temp101jp1 when /\A11[0-9\/]{3}\z/n then @dcd['PPP@s10v1'] = Tac.atoi(group[2,3]) @state = :temp101fr1 else @dcd['_sec10'] = [] unless @dcd['_sec10'] @dcd['_sec10'].push group end when :temp101fr1 z = 's10v1' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp101fr2 when :temp101jp1 z = 'p900' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp101jp2 when :temp101jp2, :temp101fr2 case group when /\A22800\z/n then @state = :temp101jp3 when /\A22[0-9\/]{3}\z/n then @dcd['PPP@s10v2'] = Tac.atoi(group[2,3]) @state = :temp101fr3 else eputs("unexpeted (#{group}) in TEMP Sec10 11.2") @state = :temp101 end when :temp101jp3 z = 'p800' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp101jp4 when :temp101jp4 if group == '33600' then @state = :temp101jp5 else eputs("unexpeted (#{group}) in TEMP Sec10 11.4") @state = :temp101 end when :temp101jp5 z = 'p600' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp101 when :temp101fr3 z = 's10v2' @dcd["dd@#{z}"], @dcd["fff@#{z}"] = Tac.atoddfff(group) @state = :temp101 end puts "@ #{group}: #{istate} -> #{@state}" if $DEBUG end # correcion of coordinate sign and post-decode correction def dcd_fixup case @dcd['Qc'] when 1 then :ignore when 3 then @dcd['La.3'] *= -1 if Numeric === @dcd['La.3'] when 5 then @dcd['La.3'] *= -1 if Numeric === @dcd['La.3'] @dcd['Lo.4'] *= -1 if Numeric === @dcd['Lo.4'] when 7 then @dcd['Lo.4'] *= -1 if Numeric === @dcd['Lo.4'] end for k in @dcd.keys.grep(/^%/n) @dcd.delete(k) end if @dcd['_sec10'] then @dcd['_sec10'] = @dcd['_sec10'].join(',') end if (51..81).include? @dcd['YY'].to_i @dcd['YY'] -= 50 # knot to m.s-1 conversion for k in @dcd.keys.grep(/\Afff@/n) case @dcd[k] when Numeric @dcd[k] = 0.1 * (5.14444 * @dcd[k] + 0.5).floor end end end end end # end of UUAA class UUBB < UUAA State = UUAA::State.dup State[:MMMUU] = :temp5ini Idname = 'a4' end class UUCC < UUAA TPrefix = 't' WPrefix = 'w' PFactor = 1 LevelBase = '10' def self.atoPPP str str.to_i * 0.1 end end class UUDD < UUBB TPrefix = 't' WPrefix = 'w' Idname = 'a4' PFactor = 1 LevelBase = '10' def self.atoPPP str str.to_i * 0.1 end end class TTAA < UUAA State = UUAA::State.dup State[nil] = :YYGGi State[:YYGGi] = :IIiii State[:IIiii] = :temp2ini end class TTBB < UUBB State = UUBB::State.dup State[nil] = :YYGGi State[:YYGGi] = :IIiii State[:IIiii] = :temp5ini end class TTCC < UUCC State = UUCC::State.dup State[nil] = :YYGGi State[:YYGGi] = :IIiii State[:IIiii] = :temp2ini end class TTDD < UUDD State = UUDD::State.dup State[nil] = :YYGGi State[:YYGGi] = :IIiii State[:IIiii] = :temp5ini end module FM32PILOT def pilot4leader(group) case group when /\A77999\z/n then return when /\A(77|66)/n then @z = 'MAXW0' @z = @z.succ while @dcd["dd@#{@z}"] @dcd["PPP@#{@z}"] = self.class::atoPPP(group[2,3]) @state = :pilot3 return when /\A(7|6)/n then @z = 'MAXW0' @z = @z.succ while @dcd["dd@#{@z}"] @dcd["HHHH@#{@z}"] = self.class::atoi(group[2,3]) @state = :pilot3 return when /\A9/n then @dcd['%zUnit'], @dcd['%zOfs'] = 300, '0' when /\A1/n then @dcd['%zUnit'], @dcd['%zOfs'] = 300, '1' when /\A8/n then @dcd['%zUnit'], @dcd['%zOfs'] = 500, '0' end @dcd['%tn'] = group[1,1] @dcd['%u1'] = group[2,1] @dcd['%u2'] = group[3,1] @dcd['%u3'] = group[4,1] @state = :pilot4h1 end def pilot4follower(group, digit) z = Tac.atoi(@dcd.values_at('%zOfs', '%tn', digit).join) z = format('%05u', z * @dcd['%zUnit']) if Numeric === z @dcd["dd@h#{z}"], @dcd["fff@h#{z}"] = Tac.atoddfff(group) end PLEVS1 = { '92' => [925, 850, 700], '85' => [850, 700, 500], '70' => [700, 500, 400], '50' => [500, 400, 300], '40' => [400, 300, 250], '30' => [300, 250, 200], '25' => [250, 200, 150], '20' => [200, 150, 100], '15' => [150, 100], '10' => [100], } PLEVS2 = { '70' => [70, 50, 30], '50' => [50, 30, 20], '30' => [30, 20, 10], '20' => [20, 10], '10' => [10], } def dcd_main(group) istate = @state case @state when :pilot4ini case group when /\A00000/n then :ignore when /\A[16789]/n then pilot4leader(group) when /\A(?:44|55)[123]/n then @dcd['Ind'] = group[0,2] @dcd['%pNum'] = Tac.atoi(group[2,1]) @dcd['%pRefs'] = self.class::PLEVS[group[3,2]] @state = :pilot2p1 when /\A(?:44|55)[\/0](?:\/\/|85)/n then :ignore when /\A(?:OBSCURED|RAIN)\z/n then wputs("ignored (#{group}) in PILOT Sec4") when /\ANO\z/n then @state = :no else eputs("unexpected (#{group}) in PILOT Sec4") @state = :err end when :no case group when /\A(?:ASCNT|ASENT|ACENT)\z/n then wputs("(NO ASCENT) in PILOT Sec4") else eputs("unexpected (NO #{group}) in PILOT Sec4") end @state = :err when :pilot2p1 z = @dcd['%pRefs'].to_a[0] @dcd["dd@p#{z}"], @dcd["fff@p#{z}"] = Tac.atoddfff(group) @state = (1 == @dcd['%pNum']) ? :pilot4ini : :pilot2p2 when :pilot2p2 z = @dcd['%pRefs'].to_a[1] @dcd["dd@p#{z}"], @dcd["fff@p#{z}"] = Tac.atoddfff(group) @state = (2 == @dcd['%pNum']) ? :pilot4ini : :pilot2p3 when :pilot2p3 z = @dcd['%pRefs'].to_a[2] @dcd["dd@p#{z}"], @dcd["fff@p#{z}"] = Tac.atoddfff(group) @state = :pilot4ini when :pilot3 case group when /^[0123]/n then @dcd["dd@#{@z}"], @dcd["fff@#{@z}"] = Tac.atoddfff(group) when /^4/n then @dcd['vbvb@' + @z] = Tac.atoi(group[1,2]) @dcd['vava@' + @z] = Tac.atoi(group[3,3]) @state = :pilot4ini end when :pilot4h1 pilot4follower(group, '%u1') @state = :pilot4h2 when :pilot4h2 if /\A[9876]/n === group or (/\A1/n === group and @dcd['%u2'] == '/') then pilot4leader(group) else pilot4follower(group, '%u2') @state = :pilot4h3 end when :pilot4h3 if /\A[9876]/n === group or (/\A1/n === group and @dcd['%u3'] == '/') then pilot4leader(group) else pilot4follower(group, '%u3') @state = :pilot4ini end when :pilot4p1, :temp6ini @dcd['%pPfx'] = 's00' unless @dcd['%pPfx'] case group when /^00/n then @z = 'SURF' else nn = group[0, 2] if nn < (@dcd['%pPrev'] or '00') @dcd['%pPfx'] = @dcd['%pPfx'].succ end @dcd['%pPrev'] = nn @z = @dcd.values_at('%pPfx', '%pPrev').join end @dcd["PPP@#{@z}"] = self.class::atoPPP(group[2,3]) @state = :pilot4pddfff when :pilot4pddfff @dcd["dd@#{@z}"], @dcd["fff@#{@z}"] = Tac.atoddfff(group) @state = :pilot4p1 end puts "@ #{group}: #{istate} -> #{@state}" if $DEBUG end end class PPAA < TTAA include FM32PILOT PLEVS = PLEVS1 State = TTAA::State.dup State[:IIiii] = :pilot4ini end class PPBB < TTBB include FM32PILOT PLEVS = PLEVS1 State = TTBB::State.dup State[:IIiii] = :pilot4ini end class PPCC < TTCC include FM32PILOT PLEVS = PLEVS2 State = TTCC::State.dup State[:IIiii] = :pilot4ini end class PPDD < TTDD include FM32PILOT PLEVS = PLEVS2 State = TTDD::State.dup State[:IIiii] = :pilot4ini end class CLIMAT < self def dcd_preempt group case group when /\A111\z/n then if @dcd['P.4'] then wputs("duplicated Sec1 in CLIMAT") @state = :err else @state = :climat1 end when /\A222\z/n then @state = :climat2 when /\A333\z/n then @state = :climat3 when /\A444\z/n then @state = :climat4 when /\A555\z/n then @state = :end when /\A(?:SEC|SEKSI)\z/n then @state = :hack_SEC when /\ANIL\z/n then @state = :nil else nil end end def sec1_init for k in %w(P0.4 P.4 TTT st.3 eee R1.4 nrnr S1.3 ps.3 mPmP mTmT mTx mTn meme mRmR mSmS) @dcd[k] = '/' end end def dcd_main group, group2 @state = :IIiii if /\A111\z/n === group2 case @state when :MMJJJ if /\ANIL\z/n =~ group2 then @dcd['@ID'] = group @state = :nil elsif /\A[0-9\/]{5}\z/n =~ group @dcd['MM'] = Tac.atoi(group[0,2]) @dcd['JJJ'] = Tac.atoi('2' + group[2,3]) @state = :IIiii elsif /\A[0-9\/]{3}\z/n =~ group @dcd['MM'] = Tac.atoi(group[0,2]) wputs("short (#{group}) for MMJJJ") @state = :IIiii elsif /\A(?:Administrator|\/\/END|TEST\.\.)\z/n =~ group wputs("ignored after (#{group})") @state = :err elsif /\ALa\z/n =~ group and /\Ainformaci.*n\z/n =~ group2 then wputs("ignored after (#{group} #{group2})") @state = :err elsif /\A(?:DE|MOIS)\z/n =~ group wputs("DIAP hack - one line ignored") @state = :hack_DIAP elsif /\ATEMP\z/n =~ group and /CSJD\d\d OJAM/n =~ @dcd['AHL'] wputs("OJAM hack - word (TEMP) ignored") else eputs("unknown (#{group}) at :MMJJJ") @state = :err end when :IIiii @dcd['@ID'] = group @state = :climat1 sec1_init when :hack_SEC wputs("(SEC) instead of section indicator") @state = case group when /^1$/n then sec1_init :climat1 when /^2$/n then :climat2 when /^3$/n then :climat3 when /^4$/n then :climat4 else eputs("isolated (SEC) word") :err end when :hack_DIAP case group when /^STOP$/n then @state = :MMJJJ end when :climat1 msg = catch(:BadToken) { emsg = nil case group when /^1[0-9\/]{4}/n then @dcd['P0.4'] = Tac.atoPPPP(group[1,4]) when /^2[0-9\/]{4}/n then @dcd['P.4'] = Tac.atoPPPP(group[1,4]) when /^3[0-9\/]{7}$/n then @dcd['TTT'] = Tac.atosi(group[1,1], group[2,3]) @dcd['st.3'] = Tac.atoi(group[5,3]) when /^4[0-9\/]{8}$/n then @dcd['Tx.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['Tn.3'] = Tac.atosi(group[5,1], group[6,3]) when /^4[0-9\/]{7}$/n then wputs("missing 2nd sn (#{group}) in CLIMAT Sec1") @dcd['Tx.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['Tn.3'] = Tac.atosi(group[1,1], group[5,3]) when /^4[01]\d{3}$/n then unless /^[01]\d{3}/ =~ group2 throw(:BadToken, "CLIMAT Sec1 (#{group2})") end @dcd['%prev'] = group @state = :climat1frag when /^5[0-9\/]{3}/n then @dcd['eee'] = Tac.atoi(group[1,3]) when /^6[0-9\/]{6}/n then @dcd['R1.4'] = Tac.atoi(group[1,4]) @dcd['nrnr'] = Tac.atoi(group[5,2]) when /^7[0-9\/]{6}/n then @dcd['S1.3'] = Tac.atoi(group[1,3]) @dcd['ps.3'] = Tac.atoi(group[4,3]) when /^8[0-9\/]{6}/n then @dcd['mPmP'] = Tac.atoi(group[1,2]) @dcd['mTmT'] = Tac.atoi(group[3,2]) @dcd['mTx'] = Tac.atoi(group[5,1]) @dcd['mTn'] = Tac.atoi(group[6,1]) when /^9[0-9\/]{6}/n then @dcd['meme'] = Tac.atoi(group[1,2]) @dcd['mRmR'] = Tac.atoi(group[3,2]) @dcd['mSmS'] = Tac.atoi(group[5,2]) else throw(:BadToken, "CLIMAT Sec1") end nil } eputs("unknown (#{group}) in #{msg}") if msg when :climat1frag prev = @dcd['%prev'] case join = [prev, group].join when /^4[01]\d{3}[01]\d{3}/ then @dcd['Tx.3'] = Tac.atosi(join[1,1], join[2,3]) @dcd['Tn.3'] = Tac.atosi(join[5,1], join[6,3]) else eputs("unknown (#{prev} #{group}) in CLIMAT Sec1") end @state = :climat1 when :climat2 case group when /^0[0-9\/]{4}/n then @dcd['YbYb'] = Tac.atoi(group[1,2]) @dcd['YcYc'] = Tac.atoi(group[3,2]) when /^1[0-9\/]{4}/n then @dcd['P0.4/C'] = Tac.atoPPPP(group[1,4]) when /^2[0-9\/]{4}/n then @dcd['P.4/C'] = Tac.atoPPPP(group[1,4]) when /^3[0-9\/]{7}/n then @dcd['TTT/C'] = Tac.atosi(group[1,1], group[2,3]) @dcd['st.3/C'] = Tac.atoi(group[5,3]) when /^4[0-9\/]{8}/n then @dcd['Tx.3/C'] = Tac.atosi(group[1,1], group[2,3]) @dcd['Tn.3/C'] = Tac.atosi(group[5,1], group[6,3]) when /^4[0-9\/]{7}$/n then wputs("missing 2nd sn (#{group}) in CLIMAT Sec2") @dcd['Tx.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['Tn.3'] = Tac.atosi(group[1,1], group[5,3]) when /^5[0-9\/]{3}/n then @dcd['eee/C'] = Tac.atoi(group[1,3]) when /^6[0-9\/]{6}/n then @dcd['R1.4/C'] = Tac.atoi(group[1,4]) @dcd['nrnr/C'] = Tac.atoi(group[5,2]) when /^7[0-9\/]{3}/n then @dcd['S1.3/C'] = Tac.atoi(group[1,3]) when /^8[0-9\/]{6}/n then @dcd['yPyP'] = Tac.atoi(group[1,2]) @dcd['yTyT'] = Tac.atoi(group[3,2]) @dcd['yTx.2'] = Tac.atoi(group[5,2]) when /^9[0-9\/]{6}/n then @dcd['yeye'] = Tac.atoi(group[1,2]) @dcd['yRyR'] = Tac.atoi(group[3,2]) @dcd['ySyS'] = Tac.atoi(group[5,2]) else eputs("unknown (#{group}) in CLIMAT Sec2") end when :climat3 case group when /^0[0-9\/]{4}/n then @dcd['T25.2'] = Tac.atoi(group[1,2]) @dcd['T30.2'] = Tac.atoi(group[3,2]) when /^1[0-9\/]{4}/n then @dcd['T35.2'] = Tac.atoi(group[1,2]) @dcd['T40.2'] = Tac.atoi(group[3,2]) when /^2[0-9\/]{4}/n then @dcd['Tn0.2'] = Tac.atoi(group[1,2]) @dcd['Tx0.2'] = Tac.atoi(group[3,2]) when /^3[0-9\/]{4}/n then @dcd['R01.2'] = Tac.atoi(group[1,2]) @dcd['R05.2'] = Tac.atoi(group[3,2]) when /^4[0-9\/]{4}/n then @dcd['R10.2'] = Tac.atoi(group[1,2]) @dcd['R50.2'] = Tac.atoi(group[3,2]) when /^5[0-9\/]{4}/n then @dcd['R100.2'] = Tac.atoi(group[1,2]) @dcd['R150.2'] = Tac.atoi(group[3,2]) when /^6[0-9\/]{4}/n then @dcd['s00.2'] = Tac.atoi(group[1,2]) @dcd['s01.2'] = Tac.atoi(group[3,2]) when /^7[0-9\/]{4}/n then @dcd['s10.2'] = Tac.atoi(group[1,2]) @dcd['s50.2'] = Tac.atoi(group[3,2]) when /^8[0-9\/]{6}/n then @dcd['f10.2'] = Tac.atoi(group[1,2]) @dcd['f20.2'] = Tac.atoi(group[3,2]) when /^9[0-9\/]{6}/n then @dcd['V1V1'] = Tac.atoi(group[1,2]) @dcd['V2V2'] = Tac.atoi(group[3,2]) @dcd['V3V3'] = Tac.atoi(group[5,2]) else eputs("unknown (#{group}) in CLIMAT Sec3") end when :climat4 case group when /^0[0-9\/]{6}/n then @dcd['Txd.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['yxyx'] = Tac.atoi(group[5,1]) when /^1[0-9\/]{6}/n then @dcd['Tnd.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['ynyn'] = Tac.atoi(group[5,1]) when /^2[0-9\/]{6}/n then @dcd['Tax.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['yax.2'] = Tac.atoi(group[5,1]) when /^3[0-9\/]{6}/n then @dcd['Tan.3'] = Tac.atosi(group[1,1], group[2,3]) @dcd['yan.2'] = Tac.atoi(group[5,1]) when /^4[0-9\/]{6}/n then @dcd['Rx.4'] = Tac.atoi(group[1,4]) @dcd['yryr'] = Tac.atoi(group[5,2]) when /^5[0-9\/]{6}/n then @dcd['iw'] = Tac.atoi(group[1,1]) @dcd['fx.3'] = Tac.atoi(group[2,3]) @dcd['yfx.2'] = Tac.atoi(group[5,2]) when /^6[0-9\/]{4}/n then @dcd['Dts.2'] = Tac.atoi(group[1,2]) @dcd['Dgr.2'] = Tac.atoi(group[3,2]) when /^7[0-9\/]{5}/n then @dcd['iy'] = Tac.atoi(group[1,1]) @dcd['GxGx'] = Tac.atoi(group[2,2]) @dcd['GnGn'] = Tac.atoi(group[4,2]) else eputs("unknown (#{group}) in CLIMAT Sec4") end end end def dcd_fixup end def decode @state = :MMJJJ @hold.size.times {|i| group = @hold[i] group2 = @hold[i + 1] next if dcd_preempt(group) dcd_main(group, group2) } dcd_fixup end end class AMDAR < self def heritage case @hold.first when /^HSSS$/n then @hold[1] else @hold.first end end def heritage= val @hold.push val if val end def dcd_preempt group case group when /\A333\z/n then @state = :amdar_sec3 when /\ANIL\z/n then @state = :nil else nil end end def dcd_main group, group2 msg = catch(:UNKN) { case @state when :YYGG @dcd['YY'] = Tac.atoi(group[0,2]) @dcd['GG'] = Tac.atoi(group[2,2]) @state = :ipipip when :ipipip @dcd['ip.3'] = group @state = :IA_IA when :IA_IA @dcd['@ID'] = group @state = :amdar_sec2 when :amdar_sec2 case group when /\A[0-9]{4}N\z/ then @dcd['La.4'] = Tac.atoi(group[0,4]) when /\A[0-9]{4}S\z/ then @dcd['La.4'] = Tac.atosi('1', group[0,4]) when /\A[0-9]{5}E\z/ then @dcd['Lo.5'] = Tac.atoi(group[0,5]) when /\A[0-9]{5}W\z/ then @dcd['Lo.5'] = Tac.atosi('1', group[0,5]) when /\A[0-9]{6}\z/ then @dcd['YY/obs'] = Tac.atoi(group[0,2]) @dcd['GG/obs'] = Tac.atoi(group[2,2]) @dcd['gg/obs'] = Tac.atoi(group[4,2]) when /\AF\d{3}\z/ then @dcd['hl.3'] = Tac.atoi(group[1,3]) when /\AA\d{3}\z/ then @dcd['hl.3'] = Tac.atosi('1', group[1,3]) when /\APS\d{3}\z/ then @dcd['TA.3'] = Tac.atoi(group[2,3]) when /\AMS\d{3}\z/ then @dcd['TA.3'] = Tac.atosi('1', group[2,3]) when /\A\d{3}\z/ then @dcd['UUU'] = Tac.atoi(group[0,3]) when /\A\/+\z/ then @dcd['Td.3'] = '/' when /\APS\d{3}\z/ then @dcd['Td.3'] = Tac.atoi(group[2,3]) when /\AMS\d{3}\z/ then @dcd['Td.3'] = Tac.atosi('1', group[2,3]) when /\A\d{3}\/\d{3}\z/ then @dcd['ddd'] = Tac.atoi(group[0,3]) @dcd['fff'] = Tac.atoi(group[3,3]) when /\ATB[\/\d]\z/ then @dcd['Ba'] = Tac.atoi(group[2,3]) when /\AS[\d\/]{3}\z/ then @dcd['s1'] = Tac.atoi(group[1,1]) @dcd['s2'] = Tac.atoi(group[2,1]) @dcd['s3'] = Tac.atoi(group[3,1]) else throw(:UNKN, true) end when :amdar_sec3 case group when /\AF[\/\d]{3}\z/ then @dcd['hd.3'] = Tac.atoi(group[1,3]) when /\AVG[\d\/]{3}\z/ then @dcd['fg.3'] = Tac.atoi(group[1,3]) else throw(:UNKN, true) end end nil } eputs("uknown (#{group}) at AMDAR #{@state}") if msg end def dcd_fixup end def decode @state = :YYGG @hold.size.times {|i| group = @hold[i] group2 = @hold[i + 1] next if dcd_preempt(group) dcd_main(group, group2) } dcd_fixup end end def self.start ahl, mimj, heritage = nil if mimj and const_defined?(mimj) klass = const_get(mimj) else klass = self end klass.new(ahl, mimj, heritage) end end class DeTac def initialize fnam @fnam = fnam if fnam == '-' then STDIN.binmode @s = StringScanner.new(STDIN.read) else File.open(fnam, 'rb') { |fp| @s = StringScanner.new(fp.read) } end @hold = nil @callback = nil end def each hack_climat = false hack_amdar = false while true # 0xA0 is used in place of space in some reports (ex. HAAB Ethiopia) @s.skip(/[\s\xA0]+/n) if @s.scan(/=/n) then yield(:EQ, @s.matched) # preceding \w* removes garbage found in CWAO message elsif @s.scan(/\w*([A-Z]{4}[0-9]{0,2} [A-Z]{4} [0-9]{6}(?: [A-Z]{3})?)/n) then yield(:AHL, @s[1]) elsif @s.scan(/(\003|NNNN)/n) then yield(:EOF, $1) elsif @s.scan(/ZCZC/n) then :ignore elsif @s.scan(/([A-Z])\1([ABCDVXY])\2/n) then hack_climat = false hack_amdar = false yield(:MiMj, @s.matched) elsif @s.scan(/(?:METAR|SPECI|AMDAR)/n) then hack_climat = false hack_amdar = true yield(:MiMj, @s.matched) elsif @s.scan(/(?:CLIMAT)/n) then hack_climat = true hack_amdar = false yield(:MiMj, @s.matched) elsif @s.scan(/NIL/n) then yield(:NIL, @s.matched) elsif @s.scan(/NULL/n) then yield(:NULL, @s.matched) elsif hack_climat and @s.scan(%r!0[ \r\n]+[0-9/]{6}\b|(?:0[0-9/]{6}|9[0-9/]{5})[ \r\n]+[0-9/]\b!n) then yield(:WORD, @s.matched.gsub(/ /n, '')) elsif hack_climat and @s.scan(%r!6\d{4}X\d\d\b!n) then yield(:WORD, @s.matched.gsub(/X/n, '/')) elsif hack_climat and @s.scan(%r!2 3 4 5!n) then yield(:WORD, '2////') yield(:WORD, '3///////') yield(:WORD, '4////////') yield(:WORD, '5///') elsif hack_climat and @s.scan(%r![07][0-9/]{7}|3[0-9]{9}|6[0-9/]{7,8}|[89][0-9/]{8}!n) then yield(:XWORD, @s.matched) elsif hack_climat and @s.scan(%r![012][0-9/]{6}|[34][0-9/]{6,8}|[57][0-9/]{3,6}|6[0-9/]{6}|[89][0-9/]{6,7}!n) then yield(:WORD, @s.matched) elsif hack_amdar and @s.scan(/(?:[0-9]{5}[EW]|\d{3}\/\d{3})/n) then yield(:WORD, @s.matched) elsif @s.scan(/[0-9\/]{6} ?[0-9\/]{4}\b/n) then x = @s.matched.sub(/ /n, '') yield(:WORD, x[0, 5]) yield(:WORD, x[5, 5]) elsif @s.scan(/[0-9\/]{5}(222|333|444|555)\b/n) then x = @s.matched yield(:WORD, x[0, 5]) yield(:WORD, x[5, 3]) elsif @s.scan(/[0-3][0-9][012][0-9][0-5][0-9]Z/n) then yield(:WORD, @s.matched) elsif @s.scan(/[0-9\/]{5,6}/n) then yield(:WORD, @s.matched) elsif !hack_amdar and @s.scan(/111|222|333|444|555\b/n) then yield(:WORD, @s.matched) elsif @s.scan(/[0-9] [0-9\/]{4}\b/n) then yield(:WORD, @s.matched.sub(/ /n, '')) elsif @s.scan(/[0-9\/\&]{5}/n) then yield(:WORD, @s.matched.sub(/\&/n, '6')) elsif @s.scan(/[12]O[0-9\/]{3}/n) then yield(:WORD, @s.matched.sub(/O/n, '0')) elsif @s.scan(/A\s/n) then :ignore # IRAQ elsif @s.scan(/Archive: \S+/n) then :ignore elsif @s.scan(/(extract|inflat)ing: (\S+)/n) then yield(:FNAM, $2) elsif @s.scan(/>+/n) then :ignore elsif @s.scan(/(?:\xC2\xA0|\xC2 )+/n) then :ignore elsif @s.scan(/[^\s=]+/n) then yield(:XWORD, @s.matched) elsif @s.eos? then break else throw(:BUG, [@s.pos, @s.peek(5)]) end end end def start(ahl, mimj, heritage) return if @hold @hold = Tac.start(ahl, mimj) @hold.heritage = heritage @hold.fnam = @fnam end def stop return unless @hold @hold.flush @callback @hold = nil end def decode &callback @callback = callback || lambda {|mimj, dcd| puts dcd.to_ltsv } @hold = nil ahl = mimj = heritage = nil each {|ttype, token| puts "& #{ttype} #{token.inspect}" if $DEBUG case ttype when :WORD, :XWORD, :NIL start(ahl, mimj, heritage) @hold.push(ttype, token) when :EQ heritage = @hold ? @hold.heritage : nil stop when :NULL start(ahl, mimj, heritage) @hold.push(ttype, 'NIL') heritage = @hold ? @hold.heritage : nil stop when :AHL stop heritage = nil ahl = token mimj = nil when :MiMj stop mimj = token start(ahl, mimj, heritage) when :EOF stop @fnam = nil when :FNAM stop @fnam = token else throw(:BUG, ["unknown token type", ttype]) end } stop end end if __FILE__ == $0 then tval = catch(:BUG) { fmt = :to_ltsv begin for file in ARGV case file when /^-yaml/ then fmt = :to_yaml else DeTac.new(file).decode {|mimj, dcd| puts dcd.send(fmt) } end end rescue Errno::EPIPE end nil } $stderr.puts tval.inspect if tval end