#!/usr/bin/ruby require 'digest/md5' require 'cgi' require 'rubygems' require 'sqlite3' require 'xml' class Mkmd PARAMS = { "gtshost" => "int.wmo.wis", "titletail" => "", "absthead" => "", } class MD def initialize row, app @row, @app = row, app @xml = XML::Document.new @root = @xml.root = XML::Node.new('metadata') @ns = XML::Namespace.new(@root, 'jmd', 'http://www.gisc.kishou.go.jp/xsd/jmd0.1') @root.namespaces.namespace = @ns @mdfid = add_element 'mdfid', nil @abstract = nil @mdfnam = nil end def add_element name = nil, content = nil, attrs = {} raise "give me name" if name.nil? @root << elem = XML::Node.new(name) elem.namespaces.namespace = @ns for aname, aval in attrs elem[aname] = aval if aval end elem << content if content elem end attr_reader :mdfnam def mdfid @mdfid.content end for key in %w(centre ttaaii cccc category content codeform country rth region centre remarks timegroup) eval("def #{key}; @row['#{key}']; end") end def warn str $deferr.puts "#{ttaaii} #{cccc}: #{str}" if $deferr.tty? end def warn! str $deferr.puts "#{ttaaii} #{cccc}: #{str}" end def iso_date dbdate = @row['datetime(date)'] case dbdate when nil Time.now.strftime('%Y-%m-%dT%H:%M:%SZ') when %r{^(\d\d)/(\d\d)/(\d\d\d\d)$} "#{$3}-#{$2}-#{$1}" when %r<^(\d{4}-\d\d-\d\d) 00:00:00> $1 when %r<^(\d{4}-\d\d-\d\d) (\d\d:\d\d:\d\d)> "#{$1}T#{$2}Z" else raise "malformatted date (#{dbdate})" end end def num_date case id = iso_date when %r<^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z$> "#{$1}#{$2}#{$3}#{$4}#{$5}#{$6}" when %r<^(\d{4})-(\d\d)-(\d\d)$> "#{$1}#{$2}#{$3}000000" else raise "malformatted iso_date #{id}" end end def contacts wisorg = case centre when 'TOKYO' then "Japan Meteorological Agency" else "Japan Meteorological Agency (on behalf of NC/DCPC #{centre} by interim arrangement in CBS/ET-GDDP for demonstration purpose)" end add_element 'wisorg', wisorg add_element 'wiscont', 'mailto:metadata-jma@ml.kishou.go.jp' add_element 'wiscont', 'fax:+81-3-3211-8404' orgorg = case ttaaii when /^[HY]/ "DCPC #{centre.capitalize}" else "NC/DCPC #{centre}" end add_element 'orgorg', orgorg if /\bTOKYO\b/i =~ centre add_element 'orgcont', 'mailto:wis-jma@met.kishou.go.jp' add_element 'orgcont', 'fax:+81-3-3211-8404' end add_element 'mddate', iso_date() add_element 'mdcycle', 'irregularly' end def topic add_element 'subjkey', 'GTS' # fake title = "GTS bulletin #{ttaaii} #{cccc}" unless PARAMS['titletail'].empty? title = "#{title} (#{PARAMS['titletail']})" end add_element 'title', title @abstract = add_element('abstract') unless PARAMS['absthead'].empty? @abstract << "Remark: #{PARAMS['absthead']}\n" end @app.datatype(ttaaii) do |text| @abstract << "Datatype: #{text};\n" end @app.datatype(ttaaii[0,1] + '??x01') do |text| add_element 'themekey', text end @app.fcsttime(ttaaii) do |fcsthour, text| add_element 'themekey', text end # fake end def stn ary n = -90 s = 90 w = 180 e = -180 a = [] for id in ary case id when /\*$/ then id = $` when /606691/ then id = '60691' when /012224/ then id = '01224' when /5642/ then id = '15642' end @app.station(id) { |lat, lon, name| s = [s, lat.to_f].min if lat n = [n, lat.to_f].max if lat w = [w, lon.to_f].min if lon e = [e, lon.to_f].max if lon a.push [name, id] } end {:southbc => s, :northbc => n, :westbc => w, :eastbc => e, :placekey => a} end NUM = '(?:\d+(?:\.\d+)?)' LAT = '(?:\d+(?:\.\d+)?(?:\xC2\xB0)? ?[NS]?)' LON = '(?:\d+(?:\.\d+)?(?:\xC2\xB0)? ?[EW]?)' DEG = "\xC2\xB0" def sign str if /[WS]\s*$/ =~ str -str.to_f else str.to_f end end def num str return str.to_f unless i = str.index(',') str[i] = '.' str.to_f end def box_desc kontent s = n = w = e = r = nil a = [[kontent]] case kontent when /^GLOBAL AREA/, /^XXX$/, /^Global$/ then s, n, w, e, r = -90, 90, -180, 180, nil when /^ *(#{LAT})-(#{LAT}) (#{LON})-(#{LON}); (#{NUM}) ?x ?(#{NUM}) GRIB/ # EGRR Style s, n, w, e = $~.values_at(1, 2, 3, 4) s, n = [sign(s), sign(n)].sort w, e = [sign(w), sign(e)].sort r = [num($5), num($6)].max when /^\((#{LON})-(#{LON})\) \((#{LAT})-(#{LAT})\) GRID \((\d+)/ # LFPW Style s, n, w, e = $~.values_at(3, 4, 1, 2) s, n = [sign(s), sign(n)].sort w, e = [sign(w), sign(e)].sort r = num($5) when /^LOWER LEFT CORNER: (#{LON}) +(#{LAT}), UPPER RIGHT CORNER: (#{LON}) +(#{LAT}), POLAR STEREOGRAPHIC/ # EDZW w, s, e, n = [$1, $2, $3, $4].map{|s| sign(s)} when /Global SST forecast anomalies\. Products encoded in GRIB-2 at (#{NUM}) degree lat\/lon resolution\./ # ECSW s, n, w, e, r = -90, 90, -180, 180, num($1) when /^(Nor|Sou)thern hemisphere products +from #{LAT} *- *#{LAT}/ # ECMF s, n = (/^N/ =~ $&) ? [0, 90] : [-90, 0] w, e = -180, 180 when /^ *(#{LON}) - +(#{LON}) +(#{LAT}) - (#{LAT}) \((#{NUM}) X (#{NUM})\)$/ # EDZW s, n, w, e, r1, r2 = $~.values_at(3, 4, 1, 2, 5, 6) s, n = [sign(s), sign(n)].sort w, e = [sign(w), sign(e)].sort r = [num(r1), num(r2)].max when /^\(180#{DEG}W-180#{DEG}E\) ?\(90#{DEG}N-20#{DEG}S\) AND \( ?20#{DEG}N-90#{DEG}S\) GRID \(2#{DEG}5x2#{DEG}5\)$/ then # LFPW - two overlapping bboxes go globe s, n, w, e, r = -90, 90, -180, 180, 2.5 when /^,? *(#{LON}) +- +(#{LON}) +((?:NOR|SOU)THERN HEMISPHERE|TROPICAL BELT) \((#{NUM}) X (#{NUM})\)$/ # EDZW w, e = [sign($1), sign($2)].sort r = [num($4), num($5)].max case $3 when /^S/ then s, n = -90, 0 when /^N/ then s, n = 0, 90 else s, n = -35, 35 end when /^(#{LON})-(#{LON}) (SOU|NOR)THERN HEMISPHERE$/ w, e = [sign($1), sign($2)].sort s, n = (/^N/ =~ $3) ? [0, 90] : [-90, 0] when /^(#{LON})-(#{LON}) TROPICAL BELT$/ w, e = [sign($1), sign($2)].sort s, n = [-35, 35] when /^\((#{LAT})-(#{LAT}) (#{LON})-(#{LON})\),(?#-- --) GRI[BD] \(([\d.,]+)[Xx]([\d.,]+)\)$/ s, n = [sign($1), sign($2)].sort w, e = [sign($3), sign($4)].sort r = [num($5), num($6)].max when /^\((NOR|SOU)THERN HEMISPHERE\), GRID \(([\d.]+)X([\d.]+)\)$/ w, e, r = -180, 180, [num($2), num($3)].max s, n = (/^N/ =~ $1) ? [0, 90] : [-90, 0] when /^(?:(?:MSL|MEAN SEA LEVEL) PRESSURE|\d+ ?HPA [-.A-Z ]+)(?#-- --) (?:\d+ ?HRS FORECAST|ANALYSIS(?:\/ANALYSE DE LA HAUTEUR A \d+ HPA)?)(?#-- --)(?: \((NOR|SOU)THERN ?HEMISPHERE?\)?)?(?#-- --)(?:, GRID[ ,]?\((#{NUM})X(#{NUM})\))?$/, /^(?:\d\d-\d\d ?HRS PRECIPITATION FORECAST)(?#-- --)(?: \((NOR|SOU)THERN HEMISPHERE\))?$/ # AMMC w, e, r = -180, 180, nil case $1 when /^N/ then s, n = [0, 90] when /^S/ then s, n = [-90, 0] else s, n = [-90, 90] end r = [num($2), num($3)].max if $2 and $3 when /^(?:SHIP: )?AREA BETWEEN (#{LAT})(?: *- *(#{LAT})(?:, (#{LON}) *- *(#{LON}))?)?$/ # AMMC if $2.nil? then s, n, w, e = -90, sign($1), -180, 180 elsif $3.nil? then s, n = [sign($1), sign($2)].sort w, e = -180, 180 else s, n = [sign($1), sign($2)].sort w, e = [sign($3), sign($4)].sort end when /^Tropical Belt products are from (#{LAT})-(#{LAT})$/ w, e = -180, 180 s, n = [sign($1), sign($2)].sort when /^BUFR REPORT ON TROPICAL DISTURBANCE$/ # ICXL20 FMEE w, e = -180, 180 s, n = -35, 35 when /^ASCAT scatterometer winds; (#{NUM})km resolution; (global area|N hemisphere)$/ w, e, r = -180, 180, num($1) / 110.0 case $2 when /^N/ then s, n = 0, 90 else s, n = -90, 90 end when /^(?:(?:PROG\.24 HS\.|ANALYSIS): \d+ hPa|(?#-- --)FORECAST OF WIND AND TEMPERATURE;)(?#-- --) FROM (#{LAT}) (#{LON}) TO (#{LAT}) (#{LON}) (?#-- --)-(?:LONGITUDE: )?DATA EVERY (\d+) DEGREES(?#-- --)(?:-|; LATITUDE: DATA EVERY (\d+) DEGREES)$/ s, n = [sign($2), sign($4)].sort w, e = [sign($1), sign($3)].sort r = num($5) when %r<^BULLETIN FOR SHIPPING, IN (?:ENGLISH|SPANISH)[;/] AREA: (?:SOUTH OF|BETWEEN (?#1)(#{LAT}) \&) (?#2)(#{LAT}), (?:BETWEEN )?(?#3)(#{LON}) \& (?#4)(#{LON})$> if $1 then s, n = [sign($1), sign($2)].sort else s, n = [-90, sign($2)].sort end w, e = [sign($3), sign($4)].sort when /^GOME-2 satellite vertical ozone profile; (global area|tropical belt|[SN] hemisphere)(?: (#{LON})-(#{LON}))?$/ w, e = [sign($2), sign($3)].sort case $1 when /^g/ then s, n = -90, 90 when /^t/ then s, n = -35, 35 when /^S/ then s, n = -90, 0 when /^N/ then s, n = 0, 90 else raise end when /^((?:\d+ *[NS] +\d+ *[EW], *)+)(?:SCALE 1: *[\d.]+ \(A[34]\) )?POLAR STEREOGRAPHIC PROJECTION$/ s, n, w, e = 90, -90, 180, -180 $1.split(/,/).each {|point| next unless /(#{LAT}) +(#{LON})/ =~ point lat, lon = sign($1), sign($2) s = [s, lat].min n = [n, lat].max w = [w, lon].min e = [e, lon].max } when /^SHIP[:.] AREA BETWEE?N (#{LAT}) *- *(#{LAT}), *(#{LON}) *- *(#{LON})$/i s, n = [sign($1), sign($2)].sort w, e = [sign($3), sign($4)].sort when /^GOME-2 satellite vertical ozone profile; (global area|tropical belt|[SN] hemisphere)(?: (#{LON})-(#{LON}))?$/ w, e = [sign($3), sign($4)].sort when /^HIGH SEAS WARNING FOR AREA 0-10 S, 90-125 E$/ s, n, w, e = -10, 0, 90, 125 when /^WARNING AREA IN (PERTH|DARWIN|BRISBANE) TROPICAL CYCLONE WARNING CENTRE$/ case $1 # soure: http://www.bom.gov.au/cyclone/about/warnings/ when /PERTH/ s, n, w, e = -36, -10, 90, 129 when /DARWIN/ s, n, w, e = -32, -10, 125, 141 when /BRISBANE/ s, n, w, e = -32, -5, 137, 160 else raise end when /^BULLETIN FOR SHIPPING, IN ENGLISH; ANTARCTIC OCEAN AREAS$/ s, n, w, e = -90, -60, -180, 180 when /^MOORED BUOY REPORTS, POS\.AT (#{LAT}) (#{LON})$/ n, e = s, w = sign($1), sign($2) when /^NORTH PACIFIC AREA/ s, n, w, e = 0, 90, 100, -75 when %r<^(SHIPS|NIL|SHIPPING FORECAST|GALE WARNING|ARGO FLOATS|(?#-- --)AIREP|ARFOR|All stations|(?#-- --)24 HOURS Sea weather forecast in French|(?#-- --)CAN INCLUDE GALE WARNINGS ASSOCIATED WITH TROPICAL CYCLONES|(?#-- --)2,210,122,102,221,030,000,000,000)$> warn "non-locational content=<#{kontent}>" return nil when /^AMDAR REPORTS OF THE SEOUL/ warn "guessed bbox for content=<#{kontent}>" s, n, w, e = 30, 45, 120, 135 a.push ["SEOUL"] a.push ["KOREA"] when %r<^(?:(?#-- --)(?:[A-Z]{4} \& )?[A-Z]{4} (?:\([FU]IR\)|FIR)|(?#-- --)TIDE STATION \d+|(?#-- --)WARNING AREA IN PERTH TROPICAL CYCLONE WARNING CENTRE|(?#-- --)AUSTRALIAN CITY FORECASTS|(?#-- --)NEW ZEALAND SEA LEVEL STATIONS|(?#-- --)(?:OSLO|FOR [A-Z]+) (?:FIR|AOR)(?#== ==)(?: (?:N|SOUTH) FOR 65 DEGREES NORTH)?, ISSUED BY (?:ENMI|ENV[VN])|(?#-- --)(?:SPECIAL AIREP )?ESAA FIR [SN])$> warn "ignored content=<#{kontent}>" return nil else warn! "unknown content=#{kontent.inspect} globe assumed" end # heuristic flip if (-90..-10).include?(w) and e == 180 then w, e = -180, w warn "bbox flip to [#{w}, #{e}]" elsif (-179..-91).include?(w) and (100..179).include?(e) then w, e = e, w warn "bbox flip to [#{w}, #{e}]" end @abstract << "Place: #{kontent};\n" {:southbc => s, :northbc => n, :westbc => w, :eastbc => e, :horizres => r, :placekey => a} end def ahl_bbox r = @app.aa(ttaaii) if r southbc, northbc, westbc, eastbc, text = r if r.all? then {:southbc => southbc, :northbc => northbc, :westbc => westbc, :eastbc => eastbc, :placekey => [[text]]} else warn! "ahl_bbox: A1A2 defined but unknown bbox value" {:placekey => [[text]]} end else warn! "ahl_bbox: unknown A1A2; content=<#{content}>" {} end end def horizontal_extent h = {} kontent = content() kontent = remarks if kontent == 'XXX' and /^\d{5} \d/ =~ remarks() case kontent when %r<^\s*(\d{5}(?:(?:,\s*|\s+)(?:\d{4,6}|[A-Z]{4}))*)(?:[, ]+(?:NIL|XXX)|"NOTE: [A-Z/. ]+)?\s*$> h.update(stn($1.split(/,\s*|\s+/))) @abstract << "Place: #{h[:placekey].map{|a,c| a or c}.join(', ')};\n" when /^\s*([A-Z]{4}\*?(?:(?:\s*,\s*|\s+)[A-Z]{4}\*?)*)(?: F)?$/i h.update(stn($1.upcase.split(/\s*,\s*|\s+/))) @abstract << "Place: #{h[:placekey].map{|a,c| a or c}.join(', ')};\n" when %r<^(?:(?=[A-Z]{5})[A-Z'/ ,]+|(?!Global)[A-Z][a-z]+)$> h.update(ahl_bbox) h[:placekey] = kontent.split(/\s*,\s*/) when /^$/, nil h.update(ahl_bbox) else h.update(box_desc(kontent) || ahl_bbox) end if country h[:placekey] = [] if h[:placekey].nil? unless h[:placekey].map{|a| a.first.to_s.upcase}.include?(country.upcase) h[:placekey].push [country] end @abstract << "Country: #{country};\n" end if h[:southbc] then for key in %w(southbc northbc westbc eastbc) add_element key, h[key.to_sym].to_s, 'units' => 'deg' end end if h[:horizres] then add_element 'horizres', h[:horizres].to_s, 'units' => 'deg' end if h[:placekey] then for key, code in h[:placekey] add_element 'placekey', (key or code), 'code' => code end end end def updcycle timegroup.gsub!(/(^[., ]+| +$)/, '') case timegroup when /^\d\d(?:\d\d)?(?:,\s*\d\d(?:\d\d)?)*$/ a = timegroup.split(/,\s*/) freq = case a.size when 1 then 'daily' when 2 then '12-hourly' when 3 then if a == %w(00 06 12) '6-hourly' else '8-hourly' end when 4 then '6-hourly' when 5..8 then '3-hourly' when 9..12 then '2-hourly' when 13..24 then 'hourly' else '30-minute' end add_element 'updcycle', freq for h in a case h when /^\d\d$/ then add_element 'updtime', "#{$&}:00:00Z" when /^(\d\d)(\d\d)$/ then add_element 'updtime', "#{$1}:#{$2}:00Z" else raise "malformatted time #{h}" end end when /^HOURLY\(([-\d,]+)\)/ add_element 'updcycle', 'hourly' for fragm in $1.split(/,/) case fragm when /^\d\d$/ then add_element 'updtime', sprintf("%02u:00:00Z", fragm.to_i) when /^(\d\d)-(\d\d)$/ then for h in ($1.to_i)..($2.to_i) add_element 'updtime', sprintf("%02u:00:00Z", h) end else raise "timegroup=<#{timegroup}>: dont-know(#{fragm})" end end when /^H(\d\d)-(\d\d)/ a, b = $1.to_i, $2.to_i if (a > b) then for h in a..23 add_element 'updtime', sprintf("%02u:00:00Z", h) end for h in 0..b add_element 'updtime', sprintf("%02u:00:00Z", h) end else for h in a..b add_element 'updtime', sprintf("%02u:00:00Z", h) end end when /^HOURLY( WHEN AVAILABLE)?/ add_element 'updcycle', 'hourly' for h in 0..23 add_element 'updtime', sprintf("%02u:00:00Z", h) end when /^H\+(\d\d),(H\+)?(\d\d)( \(S?00-24\))?$/ add_element 'updcycle', '30-minute' for h in 0..23 add_element 'updtime', sprintf("%02u:%02u:00Z", h, $1) add_element 'updtime', sprintf("%02u:%02u:00Z", h, $3) end when /^HALF-HLY . HLY$/ add_element 'updcycle', '30-minute' when /^HALF-HOURLY$/ add_element 'updcycle', '30-minute' # we don't include updtime because of no info on minutes. when /^(EVERY )?(TUESDAY|THURSDAY|FRIDAY)$/ add_element 'updcycle', 'weekly' when /^MONTHLY/i, /EACH MONTH$/i add_element 'updcycle', 'monthly' when /^(ONCE A DAY|DAILY)$/ add_element 'updcycle', 'daily' when /^EVERY 6 HOURS/, /^4 TIMES DAILY$/ add_element 'updcycle', '6-hourly' when /^3 HOURLY$/ add_element 'updcycle', '3-hourly' when /EVERY (?:THREE|3) MINUTES$/ add_element 'updcycle', 'continually', 'cycle' => '3 min' when /^EVERY (\d+) (?:MINS\.|MINUTES)$/i add_element 'updcycle', 'continually', 'cycle' => "#{$1} min" when /^varying$/, /^VARIOUS TIMES$/, /^NON[- ]STANDARD/, /^IRREGULAR$/ add_element 'updcycle', 'irregularly' when /^10 \(DURING SUMMERTIME 9\)$/ add_element 'updcycle', 'daily' add_element 'updtime', '10:00:00Z' when /^(AS|WHEN) (REQUIRED|AVAILABLE)/i add_element 'updcycle', 'asNeeded' when /^XXX/ # do nothing else warn! "timegroup=<#{timegroup}>" end end def spatiotemporal @abstract << "Originating-Centre: #{centre};\n" if rth @abstract << "WMO-Region: #{region};\n" if region @abstract << "GTS-RTH: #{rth};\n" if rth horizontal_extent # horizres @app.level(ttaaii) { |text| add_element 'stratkey', text @abstract << "Level: #{text};\n" } add_element 'avail', '24', 'units' => 'h' # begdate: N/A # enddate: N/A # revdate: N/A @app.fcsttime(ttaaii) { |fcsthour, text| add_element 'maxftime', fcsthour.to_s, 'units' => 'h' @abstract << "Forecast: #{text.sub(/\s*forecast\s*/i, '')};\n" } if timegroup.to_s.empty? add_element 'updcycle', 'irregularly' else @abstract << "TimeGroup: #{timegroup};\n" updcycle end end def codeform_pretty fmt = codeform() return nil if fmt.nil? fmt = fmt.sub(/\bEXT\./, 'Ext.').sub(/^FM(\d)/, 'FM \1') case fmt when /^FM 12/ then "#{fmt} SYNOP" when /^FM 13/ then "#{fmt} SHIP" when /^FM 14/ then "#{fmt} SYNOP MOBIL" when /^FM 15/ then "#{fmt} METAR" when /^FM 16/ then "#{fmt} SPECI" when /^FM 18/ then "#{fmt} BUOY" when /^FM 20/ then "#{fmt} RADOB" when /^FM 22/ then "#{fmt} RADREP" when /^FM 32/ then "#{fmt} PILOT" when /^FM 33/ then "#{fmt} PILOT SHIP" when /^FM 34/ then "#{fmt} PILOT MOBIL" when /^FM 35/ then "#{fmt} TEMP" when /^FM 36/ then "#{fmt} TEMP SHIP" when /^FM 37/ then "#{fmt} TEMP DROP" when /^FM 38/ then "#{fmt} TEMP MOBIL" when /^FM 39/ then "#{fmt} ROCOB" when /^FM 40/ then "#{fmt} ROCOB SHIP" when /^FM 41/ then "#{fmt} CODAR" when /^FM 42/ then "#{fmt} AMDAR" when /^FM 44/ then "#{fmt} ICEAN" when /^FM 45/ then "#{fmt} IAC" when /^FM 46/ then "#{fmt} IAC FLEET" when /^FM 47/ then "#{fmt} GRID" when /^FM 49/ then "#{fmt} GRAF" when /^FM 50/ then "#{fmt} WINTEM" when /^FM 51/ then "#{fmt} TAF" when /^FM 53/ then "#{fmt} ARDOR" when /^FM 54/ then "#{fmt} ROFOR" when /^FM 57/ then "#{fmt} RADOF" when /^FM 61/ then "#{fmt} MAFOR" when /^FM 62/ then "#{fmt} TRACKOB" when /^FM 63/ then "#{fmt} BATHY" when /^FM 64/ then "#{fmt} TESAC" when /^FM 65/ then "#{fmt} WAVEOB" when /^FM 67/ then "#{fmt} HYDRA" when /^FM 68/ then "#{fmt} HYFOR" when /^FM 71/ then "#{fmt} CLIMAT" when /^FM 72/ then "#{fmt} CLIMAT SHIP" when /^FM 75/ then "#{fmt} CLIMAT TEMP" when /^FM 76/ then "#{fmt} CLIMAT TEMP SHIP" when /^FM 82/ then "#{fmt} SFLOC" when /^FM 83/ then "#{fmt} SFAZU" when /^FM 85/ then "#{fmt} SAREP" when /^FM 86/ then "#{fmt} SATEM" when /^FM 87/ then "#{fmt} SARAD" when /^FM 88/ then "#{fmt} SATOB" when /^FM 92-XI[IV]/ then "#{fmt} GRIB Edition 2" when /^FM 92-XI?( Ext\.)?$/ then "#{fmt} GRIB Edition 1" when /^FM 92/ then "#{fmt} GRIB" when /^FM 94/ then "#{fmt} BUFR" when /^FM 95/ then "#{fmt} CREX" when /^BUFR/ then "FM 94 BUFR" when /^CREX/ then "FM 95 CREX" when /^plain language$/i then 'PLAIN LANGUAGE' else fmt end end def locator if fmt = codeform_pretty case fmt when /^(FM) (\d\d)/ then add_element 'format', fmt, 'code' => $1 + $2 when /^(AIREP|AIRMET|SIGMET|HDF5)$/ then add_element 'format', fmt, 'code' => $1 else add_element 'format', fmt end @abstract << "Format: #{fmt};\n" end instpat = case ttaaii when /^[HYOIJPQ]/ "^T_#{ttaaii}_C_#{cccc}_[0-9]{14}\\.bin$" else "^T_#{ttaaii}_C_#{cccc}_[0-9]{14}\\.txt$" end @mdfnam = "TM_#{ttaaii}_C_#{cccc}_#{num_date}.xml" add_element 'instpat', instpat @abstract << "GTS-AHL: #{ttaaii} #{cccc};\n" urn = "urn:x-wmo:md:#{PARAMS['gtshost']}::#{ttaaii}#{cccc}" @mdfid << urn url = "http://www.gisc.kishou.go.jp/cgi-bin/gisccache/#{urn}" add_element 'url', url urn end def constraints case category when 'E', nil add_element('audience', 'all WMO programmes and related international programmes') add_element 'rredist', 'no restriction' add_element 'ruse', 'WMO Essential' @abstract << "Res40: Essential;\n" when 'A' add_element 'raccess', 'registered user only' add_element 'audience', 'non-commercial purposes only' add_element 'rredist', 'commercial redistribution restricted' add_element 'ruse', 'WMO Additional' @abstract << "Res40: Additional;\n" when 'O' add_element 'raccess', 'please consult originating centre for conditions' add_element 'rredist', 'please consult originating centre for conditions' add_element 'ruse', 'Other data in WMO Cg-XII Res.40' @abstract << "Res40: Other;\n" else raise "unknown category #{category.inspect}" end @abstract << "Remarks: #{remarks};\n" if remarks end def compile contacts topic spatiotemporal locator constraints self end attr_reader :xml def to_s @xml.to_s end end def initialize @db = nil @dbfile = 'wmo9.sqlite' @xslt = nil @output = $stdout @write = :write_simple end def open @db = SQLite3::Database.new(@dbfile) end def dbfile=(fnam) @dbfile = fnam end def xslt=(fnam) require 'libxslt' xsltdoc = XML::Document.file(fnam) @xslt = LibXSLT::XSLT::Stylesheet.new(xsltdoc) end def output=(fnam) case fnam when /\.seq$/ then @output = File.open(fnam, 'wb') @write = :write_seq when /\.zip$/ then require 'zipruby' @output = Zip::Archive.open(fnam, Zip::CREATE | Zip::TRUNC) @write = :write_zip else @output = File.open(fnam, 'wb') end end def setparam key, val PARAMS[key] = val end def close case @write when :write_seq @output.close when :write_zip p :zip_close @output.close else @output.close unless @output == $defout end @db.close end def write_zip str, metadata fid = metadata.mdfnam $defout.puts fid if $defout.tty? @output.add_buffer(fid, str) end def write_seq str, metadata fid = metadata.mdfnam + Time.now.utc.strftime("\t%Y-%m-%dT%H:%M:%SZ") $defout.puts fid if $defout.tty? and not @output.tty? recl = [fid.length].pack('N') @output.write recl @output.write fid @output.write recl recl = [str.length].pack('N') @output.write recl @output.write str @output.write recl end def write_simple str, metadata @output.write str end def volc1entry row begin m = MD.new(row, self).compile s = @xslt ? @xslt.apply(m.xml) : m.xml self.send(@write, s.to_s(:indent => true), m) s = nil rescue Exception => e $deferr.puts row.inspect raise e end end def scan cmd case cmd when /^[A-Z]{4}\d\d$/ then cmd = "WHERE ttaaii = '#{cmd}'" when /^[A-Z]{4}$/ then cmd = "WHERE cccc = '#{cmd}'" when /^([A-Z]{4}\d\d) ([A-Z]{4})$/ then cmd = "WHERE ttaaii = '#{$1}' AND cccc = '#{$2}'" when /^([A-Z_]{4}[\d_]{2}) ([A-Z]{4})$/ then cmd = "WHERE ttaaii like '#{$1}' AND cccc = '#{$2}'" end sql = <<-ENDSQL SELECT region, rth, country, centre, datetime(date), category, ttaaii, cccc, codeform, timegroup, content, remarks FROM volc1 #{cmd} ENDSQL cols = nil n = 0 @db.execute2(sql) do |row| if cols.nil? then cols = row else volc1entry(Hash[*cols.zip(row).flatten]) n = n.succ GC.start if (n % 32).zero? end end end def datatype ttaaii sql = <<-ENDSQL SELECT text FROM datatype WHERE t1 = ? AND t2 = ? AND a1 = ? AND iimin <= ? AND iimax >= ? LIMIT 1 ENDSQL t1 = ttaaii[0,1] t2 = ttaaii[1,1] a1 = ttaaii[2,1] ii = ttaaii[4,2].to_i @db.execute(sql, t1, t2, a1, ii, ii) do |row| yield row.first end end def level ttaaii sql = <<-ENDSQL SELECT text FROM level WHERE t1 = ? AND ii = ? LIMIT 1 ENDSQL t1 = ttaaii[0,1] ii = ttaaii[4,2].to_i @db.execute(sql, t1, ii) do |row| yield row.first end end def fcsttime ttaaii sql = <<-ENDSQL SELECT fcsthour, text FROM fcsttime WHERE t1 = ? AND a2 = ? LIMIT 1 ENDSQL t1 = ttaaii[0,1] a2 = ttaaii[3,1] @db.execute(sql, t1, a2) do |row| yield row end end def aa ttaaii sql = <<-ENDSQL SELECT southbc, northbc, westbc, eastbc, text FROM place WHERE t1 = :t1 AND (a1 = :a1 OR a1 = "?") AND a2 = :a2 LIMIT 1 ENDSQL t1 = ttaaii[0,1] a1 = ttaaii[2,1] a2 = ttaaii[3,1] @db.execute(sql, :t1 => t1, :a1 => a1, :a2 => a2).first end def station id case id when /^\d\d\d\d\d$/ then # fake: THIS CAUSES UNDEFINED CHOICE IF UPPER/SURFACE STN SHARE AN ID # BUT THATS NOT TOO BAD SINCE WE ARE ONLY MAKING BOUNDING BOX sql = <<-ENDSQL SELECT lat, lon, stnname FROM vola WHERE idxnum = ? LIMIT 1 ENDSQL rows = @db.execute(sql, id) if rows.empty? yield [nil, nil, nil] else yield rows.first end when /^[A-Z]{4}$/ then sql = <<-ENDSQL SELECT lat, lon, stnname FROM cccc WHERE icaoid = ? LIMIT 1 ENDSQL rows = @db.execute(sql, id) if rows.empty? yield [nil, nil, nil] else yield rows.first end else raise "malformatted station <#{id}>" end end def run(cmd) open scan(cmd) ensure close end end app = Mkmd.new while /^-/ =~ ARGV.first case arg = ARGV.shift when /^-o/ then app.output = $' when /^-d/ then app.dbfile = $' when /^-x/ then app.xslt = $' when /^-p(\w+)=/ then app.setparam($1, $') else puts < specify database file -o specify output file -x specify XSLT file to filter each record -p= set parameters EOF for key, val in Mkmd::PARAMS puts " #{key}=#{val.inspect}" end exit 1 end end app.xslt = ARGV.shift if /\.xslt?$/ =~ ARGV.first app.run(ARGV.join(' '))