metar: adding METAR and TAF weather parsers.

db4
John Benediktsson 2015-04-01 14:07:40 -07:00
parent 59ec7f599e
commit 96ea327702
5 changed files with 2231 additions and 0 deletions

1
extra/metar/authors.txt Normal file
View File

@ -0,0 +1 @@
John Benediktsson

1491
extra/metar/glossary.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
USING: metar.private tools.test ;
IN: metar
{ { "RAB05" "E30" "SNB20" "E55" } }
[ "RAB05E30SNB20E55" split-recent-weather ] unit-test

731
extra/metar/metar.factor Normal file
View File

@ -0,0 +1,731 @@
! Copyright (C) 2013 John Benediktsson
! See http://factorcode.org/license.txt for BSD license
USING: accessors arrays ascii assocs calendar calendar.format
combinators continuations csv formatting fry grouping
http.client io io.encodings.ascii io.files io.styles kernel math
math.extras math.parser memoize regexp sequences sorting.human
splitting strings urls wrap.strings ;
IN: metar
TUPLE: station cccc name state country latitude longitude ;
C: <station> station
<PRIVATE
ERROR: bad-location str ;
: parse-location ( str -- n )
"-" split dup length {
{ 3 [ first3 [ string>number ] tri@ 60.0 / + 60.0 / + ] }
{ 2 [ first2 [ string>number ] bi@ 60.0 / + ] }
{ 1 [ first string>number ] }
[ drop bad-location ]
} case ;
: string>longitude ( str -- lon/f )
dup R/ \d+-\d+(-\d+(\.\d+)?)?[WE]/ matches? [
unclip-last
[ parse-location ]
[ CHAR: W = [ neg ] when ] bi*
] [ drop f ] if ;
: string>latitude ( str -- lat/f )
dup R/ \d+-\d+(-\d+(\.\d+)?)?[NS]/ matches? [
unclip-last
[ parse-location ]
[ CHAR: S = [ neg ] when ] bi*
] [ drop f ] if ;
: stations-data ( -- seq )
URL" http://weather.noaa.gov/data/nsd_cccc.txt"
http-get nip CHAR: ; [ string>csv ] with-delimiter ;
PRIVATE>
MEMO: all-stations ( -- seq )
stations-data [
{
[ 0 swap nth ]
[ 3 swap nth ]
[ 4 swap nth ]
[ 5 swap nth ]
[ 7 swap nth string>latitude ]
[ 8 swap nth string>longitude ]
} cleave <station>
] map ;
: find-by-cccc ( cccc -- station )
all-stations swap '[ cccc>> _ = ] find nip ;
: find-by-country ( country -- stations )
all-stations swap '[ country>> _ = ] filter ;
: find-by-state ( state -- stations )
all-stations swap '[ state>> _ = ] filter ;
<PRIVATE
TUPLE: metar-report type station timestamp modifier wind
visibility rvr weather sky-condition temperature dew-point
altimeter remarks raw ;
CONSTANT: pressure-tendency H{
{ "0" "increasing then decreasing" }
{ "1" "increasing more slowly" }
{ "2" "increasing" }
{ "3" "increasing more quickly" }
{ "4" "steady" }
{ "5" "decreasing then increasing" }
{ "6" "decreasing more slowly" }
{ "7" "decreasing" }
{ "8" "decreasing more quickly" }
}
CONSTANT: lightning H{
{ "CA" "cloud-air lightning" }
{ "CC" "cloud-cloud lightning" }
{ "CG" "cloud-ground lightning" }
{ "IC" "in-cloud lightning" }
}
CONSTANT: weather H{
{ "BC" "patches" }
{ "BL" "blowing" }
{ "BR" "mist" }
{ "DR" "low drifting" }
{ "DS" "duststorm" }
{ "DU" "widespread dust" }
{ "DZ" "drizzle" }
{ "FC" "funnel clouds" }
{ "FG" "fog" }
{ "FU" "smoke" }
{ "FZ" "freezing" }
{ "GR" "hail" }
{ "GS" "small hail and/or snow pellets" }
{ "HZ" "haze" }
{ "IC" "ice crystals" }
{ "MI" "shallow" }
{ "PL" "ice pellets" }
{ "PO" "well-developed dust/sand whirls" }
{ "PR" "partial" }
{ "PY" "spray" }
{ "RA" "rain" }
{ "RE" "recent" }
{ "SA" "sand" }
{ "SG" "snow grains" }
{ "SH" "showers" }
{ "SN" "snow" }
{ "SQ" "squalls" }
{ "SS" "sandstorm" }
{ "TS" "thuderstorm" }
{ "UP" "unknown" }
{ "VA" "volcanic ash" }
}
MEMO: glossary ( -- assoc )
"vocab:metar/glossary.txt" ascii file-lines
[ "," split1 ] H{ } map>assoc ;
: parse-glossary ( str -- str' )
"/" split [
find-numbers [
dup number?
[ number>string ]
[ glossary ?at drop ] if
] map " " join
] map "/" join ;
: parse-timestamp ( str -- str' )
[ now [ year>> ] [ month>> ] bi ] dip
2 cut 2 cut 2 cut drop [ string>number ] tri@
0 instant <timestamp> timestamp>rfc822 ;
CONSTANT: compass-directions H{
{ 0.0 "N" }
{ 22.5 "NNE" }
{ 45.0 "NE" }
{ 67.5 "ENE" }
{ 90.0 "E" }
{ 112.5 "ESE" }
{ 135.0 "SE" }
{ 157.5 "SSE" }
{ 180.0 "S" }
{ 202.5 "SSW" }
{ 225.0 "SW" }
{ 247.5 "WSW" }
{ 270.0 "W" }
{ 292.5 "WNW" }
{ 315.0 "NW" }
{ 337.5 "NNW" }
{ 360.0 "N" }
}
: direction>compass ( direction -- compass )
22.5 round-to-step compass-directions at ;
: parse-compass ( str -- str' )
string>number [ direction>compass ] keep "%s (%s°)" sprintf ;
: parse-direction ( str -- str' )
dup "VRB" = [ drop "variable" ] [
parse-compass "from %s" sprintf
] if ;
: kt>mph ( kt -- mph ) 1.15077945 * ;
: mph>kt ( mph -- kt ) 1.15077945 / ;
: parse-speed ( str -- str'/f )
string>number [
dup kt>mph "%s knots (%.1f mph)" sprintf
] [ f ] if* ;
: parse-wind ( str -- str' )
dup "00000KT" = [ drop "calm" ] [
3 cut "KT" ?tail drop "G" split1
[ parse-direction ] [ parse-speed ] [ parse-speed ] tri*
[ "%s at %s with gusts to %s " sprintf ]
[ "%s at %s" sprintf ] if*
] if ;
: parse-wind-variable ( str -- str' )
"V" split1 [ parse-compass ] bi@
", variable from %s to %s" sprintf ;
: parse-visibility ( str -- str' )
dup first {
{ CHAR: M [ rest "less than " ] }
{ CHAR: P [ rest "more than " ] }
[ drop "" ]
} case swap "SM" ?tail drop
CHAR: / over index [ 1 > [ 1 cut "+" glue ] when ] when*
string>number "%s%s statute miles" sprintf ;
: parse-rvr ( str -- str' )
"R" ?head drop "/" split1 "FT" ?tail drop
"V" split1 [
[ string>number ] bi@
"varying between %s and %s" sprintf
] [
string>number "of %s" sprintf
] if* "runway %s visibility %s ft" sprintf ;
: (parse-weather) ( str -- str' )
dup "+FC" = [ drop "tornadoes or waterspouts" ] [
dup first {
{ CHAR: + [ rest "heavy " ] }
{ CHAR: - [ rest "light " ] }
[ drop f ]
} case [
2 group dup [ weather key? ] all?
[ [ weather at ] map " " join ]
[ concat parse-glossary ] if
] dip prepend
] if ;
: parse-weather ( str -- str' )
"VC" over subseq? [ "VC" "" replace t ] [ f ] if
[ (parse-weather) ]
[ [ " in the vicinity" append ] when ] bi* ;
: parse-altitude ( str -- str' )
string>number " at %s00 ft" sprintf ;
CONSTANT: sky H{
{ "BKN" "broken" }
{ "FEW" "few" }
{ "OVC" "overcast" }
{ "SCT" "scattered" }
{ "SKC" "clear sky" }
{ "CLR" "clear sky" }
{ "NSC" "clear sky" }
{ "ACC" "altocumulus castellanus" }
{ "ACSL" "standing lenticular altocumulus" }
{ "CCSL" "cirrocumulus standing lenticular cloud" }
{ "CU" "cumulus" }
{ "SC" "stratocumulus" }
{ "SCSL" "stratocumulus standing lenticular cloud" }
{ "TCU" "towering cumulus" }
}
: parse-sky-condition ( str -- str' )
sky ?at [
3 cut 3 cut
[ sky at ]
[ parse-altitude ]
[ sky at [ " (%s)" sprintf ] [ f ] if* ]
tri* 3append
] unless ;
: F>C ( F -- C ) 32 - 5/9 * ;
: C>F ( C -- F ) 9/5 * 32 + ;
: parse-temperature ( str -- temp dew-point )
"/" split1 [
[ f ] [
"M" ?head [ string>number ] [ [ neg ] when ] bi*
dup C>F "%d °C (%.1f °F)" sprintf
] if-empty
] bi@ ;
: parse-altimeter ( str -- str' )
unclip [ string>number ] [ CHAR: A = ] bi*
[ 100 /f "%.2f Hg" sprintf ] [ "%s hPa" sprintf ] if ;
CONSTANT: re-timestamp R! \d{6}Z!
CONSTANT: re-station R! \w{4}!
CONSTANT: re-temperature R! [M]?\d{2}/([M]?\d{2})?!
CONSTANT: re-wind R! (VRB|\d{3})\d{2,3}(G\d{2,3})?KT!
CONSTANT: re-wind-variable R! \d{3}V\d{3}!
CONSTANT: re-visibility R! [MP]?\d+(/\d+)?SM!
CONSTANT: re-rvr R! R\d{2}[RLC]?/\d{4}(V\d{4})?FT!
CONSTANT: re-weather R! [+-]?(VC)?(\w{2}|\w{4})!
CONSTANT: re-sky-condition R! (\w{2,3}\d{3}(\w+)?|\w{3}|CAVOK)!
CONSTANT: re-altimeter R! [AQ]\d{4}!
: find-one ( seq quot: ( elt -- ? ) -- seq elt/f )
dupd find drop [ tail unclip ] [ f ] if* ; inline
: find-all ( seq quot: ( elt -- ? ) -- seq elts )
[ find-one swap ] keep '[
dup [ f ] [ first @ ] if-empty
] [ unclip ] produce rot [ prefix ] when* ; inline
: metar-body ( report seq -- report )
[ { "METAR" "SPECI" } member? ] find-one
[ pick type<< ] when*
[ re-station matches? ] find-one
[ pick station<< ] when*
[ re-timestamp matches? ] find-one
[ parse-timestamp pick timestamp<< ] when*
[ { "AUTO" "COR" } member? ] find-one
[ pick modifier<< ] when*
[ re-wind matches? ] find-one
[ parse-wind pick wind<< ] when*
[ re-wind-variable matches? ] find-one
[ parse-wind-variable pick wind>> prepend pick wind<< ] when*
[ re-visibility matches? ] find-one
[ parse-visibility pick visibility<< ] when*
[ re-rvr matches? ] find-all " " join
[ parse-rvr ] map ", " join pick rvr<<
[ re-weather matches? ] find-all
[ parse-weather ] map ", " join pick weather<<
[ re-sky-condition matches? ] find-all
[ parse-sky-condition ] map ", " join pick sky-condition<<
[ re-temperature matches? ] find-one
[
parse-temperature
[ pick temperature<< ]
[ pick dew-point<< ] bi*
] when*
[ re-altimeter matches? ] find-one
[ parse-altimeter pick altimeter<< ] when*
drop ;
: signed-number ( sign value -- n )
[ string>number ] bi@ swap zero? [ neg ] unless 10.0 / ;
: single-value ( str -- str' )
1 cut signed-number ;
: double-value ( str -- m n )
1 cut 3 cut [ signed-number ] dip 1 cut signed-number ;
: parse-1hr-temp ( str -- str' )
"T" ?head drop dup length 4 > [
double-value
[ dup C>F "%.1f °C (%.1f °F)" sprintf ] bi@
"hourly temperature %s and dew point %s" sprintf
] [
single-value dup C>F
"hourly temperature %.1f °C (%.1f °F)" sprintf
] if ;
: parse-6hr-max-temp ( str -- str' )
"1" ?head drop single-value dup C>F
"6-hour maximum temperature %.1f °C (%.1f °F)" sprintf ;
: parse-6hr-min-temp ( str -- str' )
"2" ?head drop single-value dup C>F
"6-hour minimum temperature %.1f °C (%.1f °F)" sprintf ;
: parse-24hr-temp ( str -- str' )
"4" ?head drop double-value
[ dup C>F "%.1f °C (%.1f °F)" sprintf ] bi@
"24-hour maximum temperature %s minimum temperature %s"
sprintf ;
: parse-1hr-pressure ( str -- str' )
"5" ?head drop 1 cut single-value [ pressure-tendency at ] dip
"hourly pressure %s %s hPa" sprintf ;
: parse-snow-depth ( str -- str' )
"4/" ?head drop string>number "snow depth %s inches" sprintf ;
CONSTANT: low-clouds H{
{ 1 "cumulus (fair weather)" }
{ 2 "cumulus (towering)" }
{ 3 "cumulonimbus (no anvil)" }
{ 4 "stratocumulus (from cumulus)" }
{ 5 "stratocumuls (not cumulus)" }
{ 6 "stratus or Fractostratus (fair)" }
{ 7 "fractocumulus / fractostratus (bad weather)" }
{ 8 "cumulus and stratocumulus" }
{ 9 "cumulonimbus (thunderstorm)" }
{ -1 "not valid" }
}
CONSTANT: mid-clouds H{
{ 1 "altostratus (thin)" }
{ 2 "altostratus (thick)" }
{ 3 "altocumulus (thin)" }
{ 4 "altocumulus (patchy)" }
{ 5 "altocumulus (thickening)" }
{ 6 "altocumulus (from cumulus)" }
{ 7 "altocumulus (with altocumulus, altostratus, nimbostratus)" }
{ 8 "altocumulus (with turrets)" }
{ 9 "altocumulus (chaotic)" }
{ -1 "above overcast" }
}
CONSTANT: high-clouds H{
{ 1 "cirrus (filaments)" }
{ 2 "cirrus (dense)" }
{ 3 "cirrus (often with cumulonimbus)" }
{ 4 "cirrus (thickening)" }
{ 5 "cirrus / cirrostratus (low in sky)" }
{ 6 "cirrus / cirrostratus (hi in sky)" }
{ 7 "cirrostratus (entire sky)" }
{ 8 "cirrostratus (partial)" }
{ 9 "cirrocumulus or cirrocumulus / cirrus / cirrostratus" }
{ -1 "above overcast" }
}
: parse-cloud-cover ( str -- str' )
"8/" ?head drop first3 [ CHAR: 0 - ] tri@
[ [ f ] [ low-clouds at "low clouds are %s" sprintf ] if-zero ]
[ [ f ] [ mid-clouds at "middle clouds are %s" sprintf ] if-zero ]
[ [ f ] [ high-clouds at "high clouds are %s" sprintf ] if-zero ]
tri* 3array " " join ;
: parse-inches ( str -- str' )
dup [ CHAR: / = ] all? [ drop "unknown" ] [
string>number
[ "trace" ] [ 100 /f "%.2f inches" sprintf ] if-zero
] if ;
: parse-1hr-precipitation ( str -- str' )
"P" ?head drop parse-inches
"%s precipitation in last hour" sprintf ;
: parse-6hr-precipitation ( str -- str' )
"6" ?head drop parse-inches
"%s precipitation in last 6 hours" sprintf ;
: parse-24hr-precipitation ( str -- str' )
"7" ?head drop parse-inches
"%s precipitation in last 24 hours" sprintf ;
! XXX: "on the hour" instead of "00 minutes past the hour" ?
: parse-recent-time ( str -- str' )
dup length 2 >
[ 2 cut ":" glue ]
[ " minutes past the hour" append ] if ;
: parse-peak-wind ( str -- str' )
"/" split1 [ parse-wind ] [ parse-recent-time ] bi*
"%s occuring at %s" sprintf ;
: parse-sea-level-pressure ( str -- str' )
"SLP" ?head drop string>number 10.0 /f 1000 +
"sea-level pressure is %s hPa" sprintf ;
: parse-lightning ( str -- str' )
"LTG" ?head drop 2 group [ lightning at ] map " " join ;
CONSTANT: re-recent-weather R! ((\w{2})?[BE]\d{2,4}((\w{2})?[BE]\d{2,4})?)+!
: parse-began/ended ( str -- str' )
unclip swap
[ CHAR: B = "began" "ended" ? ]
[ parse-recent-time ] bi* "%s at %s" sprintf ;
: split-recent-weather ( str -- seq )
[ dup empty? not ] [
dup [ digit? ] find drop
over [ digit? not ] find-from drop
[ cut ] [ f ] if* swap
] produce nip ;
: (parse-recent-weather) ( str -- str' )
dup [ digit? ] find drop 2 > [
2 cut [ weather at " " append ] dip
] [ f swap ] if parse-began/ended "" append-as ;
: parse-recent-weather ( str -- str' )
split-recent-weather
[ (parse-recent-weather) ] map " " join ;
: parse-varying ( str -- str' )
"V" split1 [ string>number ] bi@
"varying between %s00 and %s00 ft" sprintf ;
: parse-from-to ( str -- str' )
"-" split [ parse-glossary ] map " to " join ;
: parse-water-equivalent-snow ( str -- str' )
"933" ?head drop parse-inches
"%s water equivalent of snow on ground" sprintf ;
: parse-duration-of-sunshine ( str -- str' )
"98" ?head drop string>number
[ "no" ] [ "%s minutes of" sprintf ] if-zero
"%s sunshine" sprintf ;
: parse-6hr-snowfall ( str -- str' )
"931" ?head drop parse-inches
"%s snowfall in last 6 hours" sprintf ;
: parse-probability ( str -- str' )
"PROB" ?head drop string>number
"probability of %d%%" sprintf ;
: parse-remark ( str -- str' )
{
{ [ dup glossary key? ] [ glossary at ] }
{ [ dup R! 1\d{4}! matches? ] [ parse-6hr-max-temp ] }
{ [ dup R! 2\d{4}! matches? ] [ parse-6hr-min-temp ] }
{ [ dup R! 4\d{8}! matches? ] [ parse-24hr-temp ] }
{ [ dup R! 4/\d{3}! matches? ] [ parse-snow-depth ] }
{ [ dup R! 5\d{4}! matches? ] [ parse-1hr-pressure ] }
{ [ dup R! 6[\d/]{4}! matches? ] [ parse-6hr-precipitation ] }
{ [ dup R! 7\d{4}! matches? ] [ parse-24hr-precipitation ] }
{ [ dup R! 8/\d{3}! matches? ] [ parse-cloud-cover ] }
{ [ dup R! 931\d{3}! matches? ] [ parse-6hr-snowfall ] }
{ [ dup R! 933\d{3}! matches? ] [ parse-water-equivalent-snow ] }
{ [ dup R! 98\d{3}! matches? ] [ parse-duration-of-sunshine ] }
{ [ dup R! T\d{4,8}! matches? ] [ parse-1hr-temp ] }
{ [ dup R! \d{3}\d{2,3}/\d{2,4}! matches? ] [ parse-peak-wind ] }
{ [ dup R! P\d{4}! matches? ] [ parse-1hr-precipitation ] }
{ [ dup R! SLP\d{3}! matches? ] [ parse-sea-level-pressure ] }
{ [ dup R! LTG\w+! matches? ] [ parse-lightning ] }
{ [ dup R! PROB\d+! matches? ] [ parse-probability ] }
{ [ dup R! \d{3}V\d{3}! matches? ] [ parse-varying ] }
{ [ dup R! [^-]+(-[^-]+)+! matches? ] [ parse-from-to ] }
{ [ dup R! [^/]+(/[^/]+)+! matches? ] [ ] }
{ [ dup R! \d+.\d+! matches? ] [ ] }
{ [ dup re-recent-weather matches? ] [ parse-recent-weather ] }
{ [ dup re-weather matches? ] [ parse-weather ] }
{ [ dup re-sky-condition matches? ] [ parse-sky-condition ] }
[ parse-glossary ]
} cond ;
: metar-remarks ( report seq -- report )
[ parse-remark ] map " " join >>remarks ;
: <metar-report> ( metar -- report )
[ metar-report new ] dip [ >>raw ] keep
[ blank? ] split-when { "RMK" } split1
[ metar-body ] [ metar-remarks ] bi* ;
: row. ( name quot -- )
'[
[ _ write ] with-cell
[ @ [ 65 wrap-string write ] when* ] with-cell
] with-row ; inline
: metar-report. ( report -- )
standard-table-style [
{
[ "Station" [ station>> ] row. ]
[ "Timestamp" [ timestamp>> ] row. ]
[ "Wind" [ wind>> ] row. ]
[ "Visibility" [ visibility>> ] row. ]
[ "RVR" [ rvr>> ] row. ]
[ "Weather" [ weather>> ] row. ]
[ "Sky condition" [ sky-condition>> ] row. ]
[ "Temperature" [ temperature>> ] row. ]
[ "Dew point" [ dew-point>> ] row. ]
[ "Altimeter" [ altimeter>> ] row. ]
[ "Remarks" [ remarks>> ] row. ]
[ "Raw Text" [ raw>> ] row. ]
} cleave
] tabular-output nl ;
PRIVATE>
GENERIC: metar ( station -- metar )
M: station metar cccc>> metar ;
M: string metar
"http://weather.noaa.gov/pub/data/observations/metar/stations/%s.TXT"
sprintf http-get nip ;
GENERIC: metar. ( station -- )
M: station metar. cccc>> metar. ;
M: string metar.
[ metar <metar-report> metar-report. ]
[ drop "%s METAR not found\n" printf ] recover ;
<PRIVATE
: parse-wind-shear ( str -- str' )
"WS" ?head drop "/" split1
[ parse-altitude ] [ parse-wind ] bi* prepend
"wind shear " prepend ;
CONSTANT: re-from-timestamp R! FM\d{6}!
: parse-from-timestamp ( str -- str' )
"FM" ?head drop parse-timestamp ;
CONSTANT: re-valid-timestamp R! \d{4}\/\d{4}!
: parse-valid-timestamp ( str -- str' )
"/" split1 [ "00" append parse-timestamp ] bi@ " to " glue ;
TUPLE: taf-report station timestamp valid-timestamp wind
visibility rvr weather sky-condition partials raw ;
TUPLE: taf-partial from-timestamp wind visibility rvr weather
sky-condition raw ;
: taf-body ( report str -- report )
[ blank? ] split-when
[ { "AMD" "COR" "RTD" } member? ] find-one drop
[ re-station matches? ] find-one
[ pick station<< ] when*
[ re-timestamp matches? ] find-one
[ parse-timestamp pick timestamp<< ] when*
[ re-valid-timestamp matches? ] find-one
[ parse-valid-timestamp pick valid-timestamp<< ] when*
[ re-wind matches? ] find-one
[ parse-wind pick wind<< ] when*
[ re-wind-variable matches? ] find-one
[ parse-wind-variable pick wind>> prepend pick wind<< ] when*
[ re-visibility matches? ] find-one
[ parse-visibility pick visibility<< ] when*
[ re-rvr matches? ] find-all " " join
[ parse-rvr ] map ", " join pick rvr<<
[ re-weather matches? ] find-all
[ parse-weather ] map ", " join pick weather<<
[ re-sky-condition matches? ] find-all
[ parse-sky-condition ] map ", " join pick sky-condition<<
drop ;
: <taf-partial> ( str -- partial )
[ taf-partial new ] dip [ blank? ] split-when
[ re-from-timestamp matches? ] find-one
[ parse-from-timestamp pick from-timestamp<< ] when*
[ re-wind matches? ] find-one
[ parse-wind pick wind<< ] when*
[ re-wind-variable matches? ] find-one
[ parse-wind-variable pick wind>> prepend pick wind<< ] when*
[ re-visibility matches? ] find-one
[ parse-visibility pick visibility<< ] when*
[ re-rvr matches? ] find-all " " join
[ parse-rvr ] map ", " join pick rvr<<
[ re-weather matches? ] find-all
[ parse-weather ] map ", " join pick weather<<
[ re-sky-condition matches? ] find-all
[ parse-sky-condition ] map ", " join pick sky-condition<<
drop ;
: taf-partials ( report seq -- report )
[ <taf-partial> ] map >>partials ;
: <taf-report> ( taf -- report )
[ taf-report new ] dip [ >>raw ] keep
string-lines [ [ blank? ] trim ] map
rest dup first "TAF" = [ rest ] when
harvest unclip swap
[ taf-body ] [ taf-partials ] bi* ;
: taf-report. ( report -- )
[
standard-table-style [
{
[ "Station" [ station>> ] row. ]
[ "Timestamp" [ timestamp>> ] row. ]
[ "Valid From" [ valid-timestamp>> ] row. ]
[ "Wind" [ wind>> ] row. ]
[ "Visibility" [ visibility>> ] row. ]
[ "RVR" [ rvr>> ] row. ]
[ "Weather" [ weather>> ] row. ]
[ "Sky condition" [ sky-condition>> ] row. ]
[ "Raw Text" [ raw>> ] row. ]
} cleave
] tabular-output nl
] [
partials>> [
standard-table-style [
{
[ "From" [ from-timestamp>> ] row. ]
[ "Wind" [ wind>> ] row. ]
[ "Visibility" [ visibility>> ] row. ]
[ "RVR" [ rvr>> ] row. ]
[ "Weather" [ weather>> ] row. ]
[ "Sky condition" [ sky-condition>> ] row. ]
} cleave
] tabular-output nl
] each
] bi ;
PRIVATE>
GENERIC: taf ( station -- taf )
M: station taf cccc>> taf ;
M: string taf
"http://weather.noaa.gov/pub/data/forecasts/taf/stations/%s.TXT"
sprintf http-get nip ;
GENERIC: taf. ( station -- )
M: station taf. cccc>> taf. ;
M: string taf.
[ taf <taf-report> taf-report. ]
[ drop "%s TAF not found\n" printf ] recover ;

1
extra/metar/summary.txt Normal file
View File

@ -0,0 +1 @@
METAR and TAF weather parsers