jdc 22-jun-2007
METAR data can be obtained from http://www.noaa.org/ using ftp. This package can query data from the NOAA server and parse METAR data (obtained with this package or otherwise). Use the code below or try it with a GUI like tclMetarGui
KPV See also TclWeather and A Very Simple Weather App both of which download and parse METAR data.
package provide tclmetar 0.1 package require ftp namespace eval ::tclmetar { variable ftpsock variable metar_keys {abbrev raw year month day hour minute corrected winddir winddirstr windspeed windgust windvarfrom windvarfromstr windvarto windvartostr temperature dewpoint airpressure cloudstype cloudsheight toweringcumulus cumulonimbus weather visibility} } proc ::tclmetar::get_winddirstr { winddir } { if { [string is integer -strict $winddir] } { set wsi [expr {int(($winddir+11.25)/22.5)}] return [lindex {N NNE NE ENE E ESE SE SSE S SSW SW WSW W WNW NW NNW N} $wsi] } return $winddir } proc ::tclmetar::parse_wind { f wd winddirnm winddirstrnm ws windspeednm wg windgustnm wu } { upvar $winddirnm winddir upvar $winddirstrnm winddirstr upvar $windspeednm windspeed upvar $windgustnm windgust set winddir [string trimleft $wd "0"] if { [string length $winddir] == 0 } { set winddir 0 } set windspeed [string trimleft $ws "0"] if { [string length $windspeed] == 0 } { set windspeed 0 } set windgust [string trimleft [string range $wg 1 end] "0"] if { [string length $windgust] == 0 } { set windgust 0 } switch -exact -- $wu { "KT" { set windspeed [expr {round($windspeed * 1.852)}] set windgust [expr {round($windgust * 1.852)}] } "MPS" { set windspeed [expr {round($windspeed * 3.6)}] set windgust [expr {round($windgust * 3.6)}] } "KMH" { } } set winddirstr [get_winddirstr $winddir] return } proc ::tclmetar::parse_wind_variability { wvf windvarfromnm windvarfromstrnm wvt windvartonm windvartostrnm } { upvar $windvarfromnm windvarfrom upvar $windvartonm windvarto upvar $windvarfromstrnm windvarfromstr upvar $windvartostrnm windvartostr set windvarfrom [string trimleft $wvf "0"] if { [string length $windvarfrom] == 0 } { set windvarfrom 0 } set windvarfromstr [get_winddirstr $windvarfrom] set windvarto [string trimleft $wvt "0"] if { [string length $windvarto] == 0 } { set windvarto 0 } set windvartostr [get_winddirstr $windvarto] return } proc ::tclmetar::parse_temperature { f tempnm } { upvar $tempnm temp if { [string match "M*" $f] } { set temp -[string trimleft [string range $f 1 end] "0"] if { [string equal $temp "-"] } { set temp "0" } } else { set temp [string trimleft $f "0"] if { [string length $temp] == 0 } { set temp "0" } } } proc ::tclmetar::parse_air_pressure { pv airpressurenm pu } { upvar $airpressurenm airpressure switch -exact -- $pu { "A" { set airpressure [string trimleft [string range $pv 0 1] "0"].[string range $pv 2 3] set airpressure [expr {$airpressure * 0.033864}] } "Q" { set airpressure [expr {[string trimleft $pv "0"] / 1000.0}] } } return } proc ::tclmetar::parse_clouds { ct cloudstypenm ch cloudsheightnm cbtcu toweringcumulusnm cumulonimbusnm } { upvar $cloudstypenm cloudstype upvar $cloudsheightnm cloudsheight upvar $toweringcumulusnm toweringcumulus upvar $cumulonimbusnm cumulonimbus switch -exact -- $ct { SKC - CLR - NSC { if { [string equal $cloudstype "<unknown>"] } { set cloudstype SKC } else { lappend cloudstype SKC } } FEW - SCT - BKN - OVC { if { [string equal $cloudstype "<unknown>"] } { set cloudstype $ct } else { lappend cloudstype $ct } } } set ch [string trimleft $ch "0"] if { [string length $ch] == 0 } { set ch "0" } if { [string equal $cloudsheight "<unknown>"] } { set cloudsheight [expr {$ch * 30.48}] } else { lappend cloudsheight [expr {$ch * 30.48}] } switch -exact -- $cbtcu { CB { set toweringcumulus 0 set cumulonimbus 1 } TCU { set toweringcumulus 1 set cumulonimbus 0 } default { set toweringcumulus 0 set cumulonimbus 0 } } return } proc ::tclmetar::parse_weather { int vic desc wea weathernm } { upvar $weathernm weather set wl [list $int $vic $desc $wea] if { [string equal $weather "<unknown>"] } { set weather [list $wl] } else { lappend weather $wl } return } proc ::tclmetar::parse_international_visibility { m visibilitynm } { upvar $visibilitynm visibility set visibility [string trimleft $m "0"] if { [string length $visibility] == 0 } { set visibility 0 } return } proc ::tclmetar::parse_american_visibility { v1 v2 prevf visibilitynm } { upvar $visibilitynm visibility if { ([string length $v2] == 0) || [string equal $v2 "0"] } { set v2 1 } append v2 ".0" if { ![string is integer -strict $prevf] } { set prevf 0 } set visibility [expr {($prevf + $v1/$v2) * 1609.34}] return } proc ::tclmetar::parse { metarl mi_abbrev } { set ar(abbrev) $mi_abbrev set ar(raw) $metarl set ar(year) "<unknown>" set ar(month) "<unknown>" set ar(day) "<unknown>" set ar(hour) "<unknown>" set ar(minute) "<unknown>" set ar(corrected) "<unknown>" ;# bool set ar(winddir) "<unknown>" ;# 0 = north, 90 = east set ar(winddirstr) "<unknown>" set ar(windspeed) "<unknown>" ;# in km/h set ar(windgust) "<unknown>" ;# in km/h set ar(windvarfrom) "<unknown>" ;# 0 = north, 90 = east set ar(windvarfromstr) "<unknown>" ;# 0 = north, 90 = east set ar(windvarto) "<unknown>" ;# 0 = north, 90 = east set ar(windvartostr) "<unknown>" ;# 0 = north, 90 = east set ar(temperature) "<unknown>" ;# in degrees Celcius set ar(dewpoint) "<unknown>" ;# in degrees Celcius set ar(airpressure) "<unknown>" ;# in bar set ar(cloudstype) "<unknown>" ;# list of SKC (sky clear) | FEW | SCT (scattered) | BKN (broken) | OVC (overcast) | CAVOK set ar(cloudsheight) "<unknown>" ;# list of heights in meter | CAVOK set ar(toweringcumulus) "<unknown>" ;# bool set ar(cumulonimbus) "<unknown>" ;# bool set ar(weather) "<unknown>" ;# in metar abbreviations set ar(visibility) "<unknown>" ;# in meter set prevf "" set mi_abbrev_found 0 foreach f $metarl { ach f $metarl { if { [string equal $mi_abbrev $f] } { set mi_abbrev_found 1 continue } } if { !$mi_abbrev_found && [regexp {([0-9]{4})/([0-9]{2})/([0-9]{2})} $f m y m d] } { set ar(year) [string trimleft $y "0"] set ar(month) [string trimleft $m "0"] set ar(day) [string trimleft $d "0"] } elseif { !$mi_abbrev_found && [regexp {([0-9]{2}):([0-9]{2})} $f ma h m] } { set ar(hour) [string trimleft $h "0"] if { [string length $ar(hour)] == 0 } { set ar(hour) 0 } set ar(minute) [string trimleft $m "0"] if { [string length $ar(minute)] == 0 } { set ar(minute) 0 } } elseif { !$mi_abbrev_found } { continue } elseif { [string match "RMK*" $f] || [string match "TEMP*" $f] || [string match "NOSIG*" $f] } { break } elseif { [regexp {([0-9][0-9])([0-9][0-9])([0-9][0-9])Z} $f ma d h m] } { # Date/time field set ar(day) [string trimleft $d "0"] set ar(hour) [string trimleft $h "0"] if { [string length $ar(hour)] == 0 } { set ar(hour) 0 } set ar(minute) [string trimleft $m "0"] if { [string length $ar(minute)] == 0 } { set ar(minute) 0 } } elseif { [string equal "COR" $f] } { set ar(corrected) 1 } elseif { [string equal "AUT" $f] } { set ar(corrected) 0 } elseif { [string match {R[0-9][0-9][LCR]*} $f] } { # Runway visibility still to be added } elseif { [regexp {([0-9]{3}|VRB)([0-9]{2,3})(G[0-9]{2,3})?(KT|KMH|MPS)} $f m wd ws wg wu] } { # Wind field parse_wind $f $wd ar(winddir) ar(winddirstr) $ws ar(windspeed) $wg ar(windgust) $wu } elseif { [regexp {([0-9]{3})V([0-9]{3})} $f m wvf wvt] } { # Wind variability parse_wind_variability $wvf ar(windvarfrom) ar(windvarfromstr) $wvt ar(windvarto) ar(windvartostr) } elseif { [regexp {^(M?[0-9][0-9])/+(M?[0-9][0-9])$} $f m temp dewp] } { # Temperature field parse_temperature $temp ar(temperature) parse_temperature $dewp ar(dewpoint) } elseif { [regexp {(A|Q)([0-9]{4})} $f m pu pv] } { # Altimeter settings field parse_air_pressure $pv ar(airpressure) $pu } elseif { [regexp {(SKC|FEW|SCT|BKN|OVC|CLR|NSC)([0-9]{3})?(CB|TCU)?} $f m ct ch cbtcu] } { # Clouds field parse_clouds $ct ar(cloudstype) $ch ar(cloudsheight) $cbtcu ar(toweringcumulus) ar(cumulonimbus) } elseif { [string equal $f "CAVOK"] } { set ar(cloudstype) "CAVOK" set ar(cloudsheight) "CAVOK" set ar(cumulonimbus) 0 set ar(toweringcumulus) 0 set ar(visibility) 9999 } elseif { [regexp {([\+\-]?)(VC)?(MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PI|GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DU|PO|FC|\+FC)} $f m int vic desc wea] } { # Weather field parse_weather $int $vic $desc $wea ar(weather) while { 1 } { set f [string range $f [string length $m] end] if { ![regexp {([\+\-]?)(VC)?(MI|BC|PR|TS|BL|SH|DR|FZ)?(DZ|RA|SN|SG|IC|PI|GR|GS|UP|BR|FG|FU|VA|SA|HZ|PY|DU|SQ|SS|DU|PO|FC|\+FC)} $f m int vic desc wea] } { break } parse_weather $int $vic $desc $wea ar(weather) } } elseif { [regexp {[0-9]{4}} $f m ] } { # International visibility field parse_international_visibility $m ar(visibility) } elseif { [regexp {^([0-9]{1,2})/?([0-9]{0,2})SM$} $f m v1 v2] } { # American visibility field parse_american_visibility $v1 $v2 $prevf ar(visibility) } # Visibility directions still to be added set prevf $f } return [array get ar] } proc ::tclmetar::get_time { arnm } { upvar $arnm ar set y $ar(year) while { [string length $y] < 4 } { set y "0$y" } set M $ar(month) while { [string length $M] < 2 } { set M "0$M" } set d $ar(day) while { [string length $d] < 2 } { set d "0$d" } set h $ar(hour) while { [string length $h] < 2 } { set h "0$h" } set h "T$h" set m $ar(minute) while { [string length $m] < 2 } { set m "0$m" } set s "00" set mt [clock scan "$y$M$d$h$m$s" -gmt true] return $mt } proc ::tclmetar::ftpopen { ftpserver ftpuser ftppassword ftpdir } { variable ftpsock set ftpsock [ftp::Open $ftpserver $ftpuser $ftppassword] ftp::Cd $ftpsock $ftpdir return } proc ::tclmetar::ftpget { mfile } { variable ftpsock set mdata "" set rt [ftp::Get $ftpsock $mfile -variable mdata] if { [string length $rt] && $rt } { set mdata [string map {\n " " \r " " \t " "} [string trim $mdata]] return [eval list [split $mdata]] } else { return -code error "Could not download file '$mfile'" } } proc ::tclmetar::ftpclose { } { variable ftpsock ftp::Close $ftpsock } proc ::tclmetar::get_metar_data { mi_abbrev } { set ftpserver tgftp.nws.noaa.gov set ftpdir data/observations/metar/stations set ftpuser anonymous set ftppassword anonymous set tVerbose $ftp::VERBOSE set ::ftp::VERBOSE 0 ::log::lvSuppress error tclmetar::ftpopen $ftpserver $ftpuser $ftppassword $ftpdir set rt [catch {tclmetar::ftpget $mi_abbrev.TXT} mi_data] tclmetar::ftpclose set ::ftp::VERBOSE $tVerbose if { $rt } { return -code error $mi_data } if { [catch {tclmetar::parse $mi_data $mi_abbrev} rl] } { return -code error $rl } return $rl }
After a
package require tclmetar
the package is ready for use.
abbrev | METAR station abbreviation |
raw | Unparsed METAR data as obtained from the NOAA server |
year | Year part of date on which METAR data was collected |
month | Month part of date on which METAR data was collected |
day | Day part of date on which METAR data was collected |
hour | Hour part of date on which METAR data was collected |
minute | Minute part of date on which METAR data was collected |
corrected | Indication if METAR data was correct before upload to NOAA server by METAR Station of not |
winddir | Wind direction, 0 = north, 90 = east, ... |
winddirstr | String representation of wind direction (N, NNE, NE, ENE, E, ESE, SE, SSE, S, SSW, SW, WSW, W, WNW, NW, NNW, N) |
windspeed | Wind speed, in km/h |
windgust | Wind gusts, in km/h |
windvarfrom | Variable wind direction from this direction, 0 = north, 90 = east, ... |
windvarfromstr | String representation of wind variability from |
windvarto | Variable wind direction to this direction, 0 = north, 90 = east, ... |
windvartostr | String representation of wind variability to |
temperature | Temperature, in degrees Celcius |
dewpoint | Temperatur, in degrees Celcius |
airpressure | Air-pressure, in bar |
cloudstype | Clouds-type, list of SKC (sky clear), FEW (few), SCT (scattered), BKN (broken), OVC (overcast), CAVOK (ceiling and visibility OK) |
cloudsheight | List of clouds-heights in meter or CAVOK |
toweringcumulus | Boolean indicating if this type of clouds is present near the METAR station |
cumulonimbus | Boolean indicating if this type of clouds is present near the METAR station |
weather | Weather in METAR abbreviations |
visibility | Horizontal visibility at ground level, in meter |
Command:
::tclmetar::get_metar_data metarStationCode
This command will query the METAR data for the specified metarStationCode and return the parsed result as a list of key/value pairs suitable for use with array set.
The following script will query METAR data for all METAR stations specified on the command line:
package require tclmetar foreach m $argv { if { [catch {::tclmetar::get_metar_data $m} wl] } { puts "Could not get METAR info for '$m'" } else { puts "Weather for $m:" array set a $wl parray a unset a } }
> tclsh test.tcl EBBR KJFK Weather for EBBR: a(abbrev) = EBBR a(airpressure) = 1.008 a(cloudsheight) = 1158.24 a(cloudstype) = SCT a(corrected) = <unknown> a(cumulonimbus) = 0 a(day) = 22 a(dewpoint) = 10 a(hour) = 11 a(minute) = 20 a(month) = 6 a(raw) = 2007/06/22 11:20 EBBR 221120Z 20011KT 170V240 9999 SCT038 20/10 Q1008 TEMPO 5000 SHRA FEW020CB BKN030 a(temperature) = 20 a(toweringcumulus) = 0 a(visibility) = 9999 a(weather) = <unknown> a(winddir) = 200 a(winddirstr) = SSW a(windgust) = 0 a(windspeed) = 20 a(windvarfrom) = 170 a(windvarfromstr) = S a(windvarto) = 240 a(windvartostr) = WSW a(year) = 2007 Weather for KJFK: a(abbrev) = KJFK a(airpressure) = 1.01016312 a(cloudsheight) = 3657.6 a(cloudstype) = FEW a(corrected) = <unknown> a(cumulonimbus) = 0 a(day) = 22 a(dewpoint) = 11 a(hour) = 10 a(minute) = 51 a(month) = 6 a(raw) = 2007/06/22 10:51 KJFK 221051Z 30016KT 10SM FEW120 19/11 A2983 RMK AO2 SLP102 T01890111 {$} a(temperature) = 19 a(toweringcumulus) = 0 a(visibility) = 16093.4 a(weather) = <unknown> a(winddir) = 300 a(winddirstr) = WNW a(windgust) = 0 a(windspeed) = 30 a(windvarfrom) = <unknown> a(windvarfromstr) = <unknown> a(windvarto) = <unknown> a(windvartostr) = <unknown> a(year) = 2007
weather data