6 # miscellaneous useful functions
8 # read a time in string format, turn it into "seconds from now".
9 # example formats handled are "5 minutes", "2 days", "five hours",
10 # "11:30", "15:45:11", "one day", etc.
12 # Throws:: RunTimeError "invalid time string" on parse failure
13 def Utils.timestr_offset(timestr)
15 when (/^(\S+)\s+(\S+)$/)
18 if(mult =~ /^([\d.]+)$/)
20 raise "invalid time string" unless num
52 raise "invalid time string"
56 when (/^(s|sec(ond)?s?)$/)
58 when (/^(m|min(ute)?s?)$/)
60 when (/^(h|h(ou)?rs?)$/)
63 return num * 60 * 60 * 24
65 raise "invalid time string"
67 when (/^(\d+):(\d+):(\d+)$/)
72 later = Time.mktime(now.year, now.month, now.day, hour, min, sec)
74 when (/^(\d+):(\d+)$/)
78 later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
80 when (/^(\d+):(\d+)(am|pm)$/)
88 later = Time.mktime(now.year, now.month, now.day, hour, min, now.sec)
94 when (/^(s|sec(ond)?s?)$/)
96 when (/^(m|min(ute)?s?)$/)
98 when (/^(h|h(ou)?rs?)$/)
101 return num * 60 * 60 * 24
103 raise "invalid time string"
106 raise "invalid time string"
110 # turn a number of seconds into a human readable string, e.g
111 # 2 days, 3 hours, 18 minutes, 10 seconds
112 def Utils.secs_to_string(secs)
114 days = (secs / (60 * 60 * 24)).to_i
115 secs = secs % (60 * 60 * 24)
116 hours = (secs / (60 * 60)).to_i
117 secs = (secs % (60 * 60))
118 mins = (secs / 60).to_i
119 secs = (secs % 60).to_i
120 ret += "#{days} days, " if days > 0
121 ret += "#{hours} hours, " if hours > 0 || days > 0
122 ret += "#{mins} minutes and " if mins > 0 || hours > 0 || days > 0
123 ret += "#{secs} seconds"
127 def Utils.safe_exec(command, *args)
130 return p.readlines.join("\n")
135 rescue Exception => e
136 puts "exec of #{command} led to exception: #{e}"
139 puts "exec of #{command} failed"
145 # returns a string containing the result of an HTTP GET on the uri
146 def Utils.http_get(uristr, readtimeout=8, opentimeout=4)
148 # ruby 1.7 or better needed for this (or 1.6 and debian unstable)
149 Net::HTTP.version_1_2
150 # (so we support the 1_1 api anyway, avoids problems)
152 uri = URI.parse uristr
155 query += "?#{uri.query}"
160 if(ENV['http_proxy'] && proxy_uri = URI.parse(ENV['http_proxy']))
161 proxy_host = proxy_uri.host
162 proxy_port = proxy_uri.port
165 http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
166 http.open_timeout = opentimeout
167 http.read_timeout = readtimeout
171 resp = http.get(query)
172 if resp.code == "200"
177 $stderr.puts "Utils.http_get exception: #{e}, while trying to get #{uristr}"
183 # This is nasty-ass. I hate writing parsers.
189 def initialize(string)
192 string.each_line {|l|
194 # grab first line (date)
195 @date = l.chomp.strip
201 str += " " + l.chomp.strip
205 if @date && @date =~ /^(\d+)\/(\d+)\/(\d+) (\d+):(\d+)$/
207 @date = Time.gm($1, $2, $3, $4, $5, 0)
217 'FEW' => '1/8 - 2/8',
218 'SCT' => '3/8 - 4/8',
219 'BKN' => '5/8 - 7/8',
241 @wind_dir_texts_short = [
264 'DR' => 'Low Drifting ',
266 'SH' => 'Shower(s) ',
267 'TS' => 'Thunderstorm ',
272 'SG' => 'Snow Grains ',
273 'IC' => 'Ice Crystals ',
274 'PE' => 'Ice Pellets ',
276 'GS' => 'Small Hail and/or Snow Pellets ',
281 'VA' => 'Volcanic Ash ',
282 'DU' => 'Widespread Dust ',
286 'PO' => 'Well-Developed Dust/Sand Whirls ',
288 'FC' => 'Funnel Cloud Tornado Waterspout ',
289 'SS' => 'Sandstorm/Duststorm '
291 @cloud_condition_array = {
294 'VV' => 'vertical visibility',
296 'SCT' => 'scattered',
301 'mm_inches' => '%s mm (%s inches)',
302 'precip_a_trace' => 'a trace',
303 'precip_there_was' => 'There was %s of precipitation ',
304 'sky_str_format1' => 'There were %s at a height of %s meters (%s feet)',
305 'sky_str_clear' => 'The sky was clear',
306 'sky_str_format2' => ', %s at a height of %s meter (%s feet) and %s at a height of %s meters (%s feet)',
307 'sky_str_format3' => ' and %s at a height of %s meters (%s feet)',
308 'clouds' => ' clouds',
309 'clouds_cb' => ' cumulonimbus clouds',
310 'clouds_tcu' => ' towering cumulus clouds',
311 'visibility_format' => 'The visibility was %s kilometers (%s miles).',
312 'wind_str_format1' => 'blowing at a speed of %s meters per second (%s miles per hour)',
313 'wind_str_format2' => ', with gusts to %s meters per second (%s miles per hour),',
314 'wind_str_format3' => ' from the %s',
315 'wind_str_calm' => 'calm',
316 'precip_last_hour' => 'in the last hour. ',
317 'precip_last_6_hours' => 'in the last 3 to 6 hours. ',
318 'precip_last_24_hours' => 'in the last 24 hours. ',
319 'precip_snow' => 'There is %s mm (%s inches) of snow on the ground. ',
320 'temp_min_max_6_hours' => 'The maximum and minimum temperatures over the last 6 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit).',
321 'temp_max_6_hours' => 'The maximum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
322 'temp_min_6_hours' => 'The minimum temperature over the last 6 hours was %s degrees Celsius (%s degrees Fahrenheit). ',
323 'temp_min_max_24_hours' => 'The maximum and minimum temperatures over the last 24 hours were %s and %s degrees Celsius (%s and %s degrees Fahrenheit). ',
325 'moderate' => 'Moderate ',
328 'nearby' => 'Nearby ',
329 'current_weather' => 'Current weather is %s. ',
330 'pretty_print_metar' => '%s on %s, the wind was %s at %s. The temperature was %s degrees Celsius (%s degrees Fahrenheit), and the pressure was %s hPa (%s inHg). The relative humidity was %s%%. %s %s %s %s %s'
336 def store_speed(value, windunit, meterspersec, knots, milesperhour)
337 # Helper function to convert and store speed based on unit.
338 # &$meterspersec, &$knots and &$milesperhour are passed on
340 if (windunit == 'KT')
341 # The windspeed measured in knots:
342 @decoded[knots] = sprintf("%.2f", value)
343 # The windspeed measured in meters per second, rounded to one decimal place:
344 @decoded[meterspersec] = sprintf("%.2f", value.to_f * 0.51444)
345 # The windspeed measured in miles per hour, rounded to one decimal place: */
346 @decoded[milesperhour] = sprintf("%.2f", value.to_f * 1.1507695060844667)
347 elsif (windunit == 'MPS')
348 # The windspeed measured in meters per second:
349 @decoded[meterspersec] = sprintf("%.2f", value)
350 # The windspeed measured in knots, rounded to one decimal place:
351 @decoded[knots] = sprintf("%.2f", value.to_f / 0.51444)
352 #The windspeed measured in miles per hour, rounded to one decimal place:
353 @decoded[milesperhour] = sprintf("%.1f", value.to_f / 0.51444 * 1.1507695060844667)
354 elsif (windunit == 'KMH')
355 # The windspeed measured in kilometers per hour:
356 @decoded[meterspersec] = sprintf("%.1f", value.to_f * 1000 / 3600)
357 @decoded[knots] = sprintf("%.1f", value.to_f * 1000 / 3600 / 0.51444)
358 # The windspeed measured in miles per hour, rounded to one decimal place:
359 @decoded[milesperhour] = sprintf("%.1f", knots.to_f * 1.1507695060844667)
366 @input.split(" ").each {|part|
368 # Type of Report: METAR
369 @decoded['type'] = 'METAR'
370 elsif (part == 'SPECI')
371 # Type of Report: SPECI
372 @decoded['type'] = 'SPECI'
373 elsif (part == 'AUTO')
374 # Report Modifier: AUTO
375 @decoded['report_mod'] = 'AUTO'
376 elsif (part == 'NIL')
378 elsif (part =~ /^\S{4}$/ && ! (@decoded.has_key?('station')))
380 @decoded['station'] = part
381 elsif (part =~ /([0-9]{2})([0-9]{2})([0-9]{2})Z/)
382 # ignore this bit, it's useless without month/year. some of these
383 # things are hideously out of date.
385 # time = Time.gm(now.year, now.month, $1, $2, $3, 0)
386 # Date and Time of Report
387 # @decoded['time'] = time
388 elsif (part == 'COR')
389 # Report Modifier: COR
390 @decoded['report_mod'] = 'COR'
391 elsif (part =~ /([0-9]{3}|VRB)([0-9]{2,3}).*(KT|MPS|KMH)/)
394 # now do ereg to get the actual values
395 part =~ /([0-9]{3}|VRB)([0-9]{2,3})((G[0-9]{2,3})?#{windunit})/
397 @decoded['wind_deg'] = 'variable directions'
398 @decoded['wind_dir_text'] = 'variable directions'
399 @decoded['wind_dir_text_short'] = 'VAR'
401 @decoded['wind_deg'] = $1
402 @decoded['wind_dir_text'] = @wind_dir_texts[($1.to_i/22.5).round]
403 @decoded['wind_dir_text_short'] = @wind_dir_texts_short[($1.to_i/22.5).round]
405 store_speed($2, windunit,
406 'wind_meters_per_second',
408 'wind_miles_per_hour')
411 # We have a report with information about the gust.
412 # First we have the gust measured in knots
413 if ($4 =~ /G([0-9]{2,3})/)
414 store_speed($1,windunit,
415 'wind_gust_meters_per_second',
417 'wind_gust_miles_per_hour')
420 elsif (part =~ /([0-9]{3})V([0-9]{3})/)
421 # Variable wind-direction
422 @decoded['wind_var_beg'] = $1
423 @decoded['wind_var_end'] = $2
424 elsif (part == "9999")
425 # A strange value. When you look at other pages you see it
426 # interpreted like this (where I use > to signify 'Greater
428 @decoded['visibility_miles'] = '>7';
429 @decoded['visibility_km'] = '>11.3';
430 elsif (part =~ /^([0-9]{4})$/)
431 # Visibility in meters (4 digits only)
432 # The visibility measured in kilometers, rounded to one decimal place.
433 @decoded['visibility_km'] = sprintf("%.1f", $1.to_i / 1000)
434 # The visibility measured in miles, rounded to one decimal place.
435 @decoded['visibility_miles'] = sprintf("%.1f", $1.to_i / 1000 / 1.609344)
436 elsif (part =~ /^[0-9]$/)
437 # Temp Visibility Group, single digit followed by space
438 @decoded['temp_visibility_miles'] = part
439 elsif (@decoded['temp_visibility_miles'] && (@decoded['temp_visibility_miles']+' '+part) =~ /^M?(([0-9]?)[ ]?([0-9])(\/?)([0-9]*))SM$/)
442 vis_miles = $2.to_i + $3.to_i/$5.to_i
446 if (@decoded['temp_visibility_miles'][0] == 'M')
447 # The visibility measured in miles, prefixed with < to indicate 'Less than'
448 @decoded['visibility_miles'] = '<' + sprintf("%.1f", vis_miles)
449 # The visibility measured in kilometers. The value is rounded
450 # to one decimal place, prefixed with < to indicate 'Less than' */
451 @decoded['visibility_km'] = '<' . sprintf("%.1f", vis_miles * 1.609344)
453 # The visibility measured in mile.s */
454 @decoded['visibility_miles'] = sprintf("%.1f", vis_miles)
455 # The visibility measured in kilometers, rounded to one decimal place.
456 @decoded['visibility_km'] = sprintf("%.1f", vis_miles * 1.609344)
458 elsif (part =~ /^(-|\+|VC|MI)?(TS|SH|FZ|BL|DR|BC|PR|RA|DZ|SN|SG|GR|GS|PE|IC|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS)+$/)
459 # Current weather-group
460 @decoded['weather'] = '' unless @decoded.has_key?('weather')
461 if (part[0].chr == '-')
463 @decoded['weather'] += @strings['light']
464 part = part[1,part.length]
465 elsif (part[0].chr == '+')
467 @decoded['weather'] += @strings['heavy']
468 part = part[1,part.length]
469 elsif (part[0,2] == 'VC')
470 # Proximity Qualifier
471 @decoded['weather'] += @strings['nearby']
472 part = part[2,part.length]
473 elsif (part[0,2] == 'MI')
474 @decoded['weather'] += @strings['mild']
475 part = part[2,part.length]
477 # no intensity code => moderate phenomenon
478 @decoded['weather'] += @strings['moderate']
481 while (part && bite = part[0,2]) do
482 # Now we take the first two letters and determine what they
483 # mean. We append this to the variable so that we gradually
486 @decoded['weather'] += @weather_array[bite]
487 # Here we chop off the two first letters, so that we can take
488 # a new bite at top of the while-loop.
491 elsif (part =~ /(SKC|CLR)/)
493 # There can be up to three of these groups, so we store them as
494 # cloud_layer1, cloud_layer2 and cloud_layer3.
497 # Again we have to translate the code-characters to a
499 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] = @cloud_condition_array[$1]
500 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
501 elsif (part =~ /^(VV|FEW|SCT|BKN|OVC)([0-9]{3})(CB|TCU)?$/)
502 # We have found (another) a cloud-layer-group. There can be up
503 # to three of these groups, so we store them as cloud_layer1,
504 # cloud_layer2 and cloud_layer3.
506 # Again we have to translate the code-characters to a meaningful string.
508 # cumulonimbus (CB) clouds were observed. */
509 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
510 @cloud_condition_array[$1] + @strings['clouds_cb']
512 # towering cumulus (TCU) clouds were observed.
513 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
514 @cloud_condition_array[$1] + @strings['clouds_tcu']
516 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_condition'] =
517 @cloud_condition_array[$1] + @strings['clouds']
519 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_coverage'] = @cloud_coverage[$1]
520 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_ft'] = $2.to_i * 100
521 @decoded['cloud_layer'+ (@cloud_layers.to_s) +'_altitude_m'] = ($2.to_f * 30.48).round
522 elsif (part =~ /^T([0-9]{4})$/)
523 store_temp($1,'temp_c','temp_f')
524 elsif (part =~ /^T?(M?[0-9]{2})\/(M?[0-9\/]{1,2})?$/)
525 # Temperature/Dew Point Group
526 # The temperature and dew-point measured in Celsius.
527 @decoded['temp_c'] = sprintf("%d", $1.tr('M', '-'))
529 @decoded['dew_c'] = 0
531 @decoded['dew_c'] = sprintf("%.1f", $2.tr('M', '-'))
533 # The temperature and dew-point measured in Fahrenheit, rounded to
534 # the nearest degree.
535 @decoded['temp_f'] = ((@decoded['temp_c'].to_f * 9 / 5) + 32).round
536 @decoded['dew_f'] = ((@decoded['dew_c'].to_f * 9 / 5) + 32).round
537 elsif(part =~ /A([0-9]{4})/)
539 # The pressure measured in inHg
540 @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_i/100)
541 # The pressure measured in mmHg, hPa and atm
542 @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.254)
543 @decoded['altimeter_hpa'] = sprintf("%d", ($1.to_f * 0.33863881578947).to_i)
544 @decoded['altimeter_atm'] = sprintf("%.3f", $1.to_f * 3.3421052631579e-4)
545 elsif(part =~ /Q([0-9]{4})/)
547 # This is strange, the specification doesnt say anything about
548 # the Qxxxx-form, but it's in the METARs.
549 # The pressure measured in hPa
550 @decoded['altimeter_hpa'] = sprintf("%d", $1.to_i)
551 # The pressure measured in mmHg, inHg and atm
552 @decoded['altimeter_mmhg'] = sprintf("%.1f", $1.to_f * 0.7500616827)
553 @decoded['altimeter_inhg'] = sprintf("%.2f", $1.to_f * 0.0295299875)
554 @decoded['altimeter_atm'] = sprintf("%.3f", $1.to_f * 9.869232667e-4)
555 elsif (part =~ /^T([0-9]{4})([0-9]{4})/)
556 # Temperature/Dew Point Group, coded to tenth of degree.
557 # The temperature and dew-point measured in Celsius.
558 store_temp($1,'temp_c','temp_f')
559 store_temp($2,'dew_c','dew_f')
560 elsif (part =~ /^1([0-9]{4}$)/)
561 # 6 hour maximum temperature Celsius, coded to tenth of degree
562 store_temp($1,'temp_max6h_c','temp_max6h_f')
563 elsif (part =~ /^2([0-9]{4}$)/)
564 # 6 hour minimum temperature Celsius, coded to tenth of degree
565 store_temp($1,'temp_min6h_c','temp_min6h_f')
566 elsif (part =~ /^4([0-9]{4})([0-9]{4})$/)
567 # 24 hour maximum and minimum temperature Celsius, coded to
569 store_temp($1,'temp_max24h_c','temp_max24h_f')
570 store_temp($2,'temp_min24h_c','temp_min24h_f')
571 elsif (part =~ /^P([0-9]{4})/)
572 # Precipitation during last hour in hundredths of an inch
574 @decoded['precip_in'] = sprintf("%.2f", $1.to_f/100)
575 @decoded['precip_mm'] = sprintf("%.2f", $1.to_f * 0.254)
576 elsif (part =~ /^6([0-9]{4})/)
577 # Precipitation during last 3 or 6 hours in hundredths of an
578 # inch (store as inches)
579 @decoded['precip_6h_in'] = sprintf("%.2f", $1.to_f/100)
580 @decoded['precip_6h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
581 elsif (part =~ /^7([0-9]{4})/)
582 # Precipitation during last 24 hours in hundredths of an inch
584 @decoded['precip_24h_in'] = sprintf("%.2f", $1.to_f/100)
585 @decoded['precip_24h_mm'] = sprintf("%.2f", $1.to_f * 0.254)
586 elsif(part =~ /^4\/([0-9]{3})/)
587 # Snow depth in inches
588 @decoded['snow_in'] = sprintf("%.2f", $1);
589 @decoded['snow_mm'] = sprintf("%.2f", $1.to_f * 25.4)
591 # If we couldn't match the group, we assume that it was a
593 @decoded['remarks'] = '' unless @decoded.has_key?("remarks")
594 @decoded['remarks'] += ' ' + part;
599 # p @decoded['dew_c'] # 11.0
600 # p @decoded['temp_c'] # 21.0
602 @decoded['rel_humidity'] = sprintf("%.1f",100 *
603 (6.11 * (10.0**(7.5 * @decoded['dew_c'].to_f / (237.7 + @decoded['dew_c'].to_f)))) / (6.11 * (10.0 ** (7.5 * @decoded['temp_c'].to_f / (237.7 + @decoded['temp_c'].to_f))))) if @decoded.has_key?('dew_c')
606 def store_temp(temp,temp_cname,temp_fname)
607 # Given a numerical temperature temp in Celsius, coded to tenth of
608 # degree, store in @decoded[temp_cname], convert to Fahrenheit
609 # and store in @decoded[temp_fname]
610 # Note: temp is converted to negative if temp > 100.0 (See
611 # Federal Meteorological Handbook for groups T, 1, 2 and 4)
613 # Temperature measured in Celsius, coded to tenth of degree
616 # first digit = 1 means minus temperature
617 temp = -(temp - 100.0)
619 @decoded[temp_cname] = sprintf("%.1f", temp)
620 # The temperature in Fahrenheit.
621 @decoded[temp_fname] = sprintf("%.1f", (temp * 9 / 5) + 32)
624 def pretty_print_precip(precip_mm, precip_in)
625 # Returns amount if $precip_mm > 0, otherwise "trace" (see Federal
626 # Meteorological Handbook No. 1 for code groups P, 6 and 7) used in
627 # several places, so standardized in one function.
628 if (precip_mm.to_i > 0)
629 amount = sprintf(@strings['mm_inches'], precip_mm, precip_in)
631 amount = @strings['a_trace']
633 return sprintf(@strings['precip_there_was'], amount)
638 return "The weather stored for #{@decoded['station']} consists of the string 'NIL' :("
641 ["temp_c", "altimeter_hpa"].each {|key|
642 if !@decoded.has_key?(key)
643 return "The weather stored for #{@decoded['station']} could not be parsed (#{@input})"
647 mins_old = ((Time.now - @date.to_i).to_f/60).round
649 weather_age = mins_old.to_s + " minutes ago,"
650 elsif (mins_old <= 60 * 25)
651 weather_age = (mins_old / 60).to_s + " hours, "
652 weather_age += (mins_old % 60).to_s + " minutes ago,"
654 # return "The weather stored for #{@decoded['station']} is hideously out of date :( (Last update #{@date})"
655 weather_age = "The weather stored for #{@decoded['station']} is hideously out of date :( here it is anyway:"
658 if(@decoded.has_key?("cloud_layer1_altitude_ft"))
659 sky_str = sprintf(@strings['sky_str_format1'],
660 @decoded["cloud_layer1_condition"],
661 @decoded["cloud_layer1_altitude_m"],
662 @decoded["cloud_layer1_altitude_ft"])
664 sky_str = @strings['sky_str_clear']
667 if(@decoded.has_key?("cloud_layer2_altitude_ft"))
668 if(@decoded.has_key?("cloud_layer3_altitude_ft"))
669 sky_str += sprintf(@strings['sky_str_format2'],
670 @decoded["cloud_layer2_condition"],
671 @decoded["cloud_layer2_altitude_m"],
672 @decoded["cloud_layer2_altitude_ft"],
673 @decoded["cloud_layer3_condition"],
674 @decoded["cloud_layer3_altitude_m"],
675 @decoded["cloud_layer3_altitude_ft"])
677 sky_str += sprintf(@strings['sky_str_format3'],
678 @decoded["cloud_layer2_condition"],
679 @decoded["cloud_layer2_altitude_m"],
680 @decoded["cloud_layer2_altitude_ft"])
685 if(@decoded.has_key?("visibility_miles"))
686 visibility = sprintf(@strings['visibility_format'],
687 @decoded["visibility_km"],
688 @decoded["visibility_miles"])
693 if (@decoded.has_key?("wind_meters_per_second") && @decoded["wind_meters_per_second"].to_i > 0)
694 wind_str = sprintf(@strings['wind_str_format1'],
695 @decoded["wind_meters_per_second"],
696 @decoded["wind_miles_per_hour"])
697 if (@decoded.has_key?("wind_gust_meters_per_second") && @decoded["wind_gust_meters_per_second"].to_i > 0)
698 wind_str += sprintf(@strings['wind_str_format2'],
699 @decoded["wind_gust_meters_per_second"],
700 @decoded["wind_gust_miles_per_hour"])
702 wind_str += sprintf(@strings['wind_str_format3'],
703 @decoded["wind_dir_text"])
705 wind_str = @strings['wind_str_calm']
709 if (@decoded.has_key?("precip_in"))
710 prec_str += pretty_print_precip(@decoded["precip_mm"], @decoded["precip_in"]) + @strings['precip_last_hour']
712 if (@decoded.has_key?("precip_6h_in"))
713 prec_str += pretty_print_precip(@decoded["precip_6h_mm"], @decoded["precip_6h_in"]) + @strings['precip_last_6_hours']
715 if (@decoded.has_key?("precip_24h_in"))
716 prec_str += pretty_print_precip(@decoded["precip_24h_mm"], @decoded["precip_24h_in"]) + @strings['precip_last_24_hours']
718 if (@decoded.has_key?("snow_in"))
719 prec_str += sprintf(@strings['precip_snow'], @decoded["snow_mm"], @decoded["snow_in"])
723 if (@decoded.has_key?("temp_max6h_c") && @decoded.has_key?("temp_min6h_c"))
724 temp_str += sprintf(@strings['temp_min_max_6_hours'],
725 @decoded["temp_max6h_c"],
726 @decoded["temp_min6h_c"],
727 @decoded["temp_max6h_f"],
728 @decoded["temp_min6h_f"])
730 if (@decoded.has_key?("temp_max6h_c"))
731 temp_str += sprintf(@strings['temp_max_6_hours'],
732 @decoded["temp_max6h_c"],
733 @decoded["temp_max6h_f"])
735 if (@decoded.has_key?("temp_min6h_c"))
736 temp_str += sprintf(@strings['temp_max_6_hours'],
737 @decoded["temp_min6h_c"],
738 @decoded["temp_min6h_f"])
741 if (@decoded.has_key?("temp_max24h_c"))
742 temp_str += sprintf(@strings['temp_min_max_24_hours'],
743 @decoded["temp_max24h_c"],
744 @decoded["temp_min24h_c"],
745 @decoded["temp_max24h_f"],
746 @decoded["temp_min24h_f"])
749 if (@decoded.has_key?("weather"))
750 weather_str = sprintf(@strings['current_weather'], @decoded["weather"])
755 return sprintf(@strings['pretty_print_metar'],
758 wind_str, @decoded["station"], @decoded["temp_c"],
759 @decoded["temp_f"], @decoded["altimeter_hpa"],
760 @decoded["altimeter_inhg"],
761 @decoded["rel_humidity"], sky_str,
762 visibility, weather_str, prec_str, temp_str).strip
770 def Utils.get_metar(station)
773 result = Utils.http_get("http://weather.noaa.gov/pub/data/observations/metar/stations/#{station}.TXT")
774 return nil unless result
775 return Metar.new(result)