[jdc] 22-jun-2007 **Metar stations package** A list of [METAR] stations can be found here: http://www.rap.ucar.edu/weather/surface/stations.txt The following code uses this file to get information about locations based on name or coordinates: ====== package provide metarstations 0.1 namespace eval ::metarstations { variable stations_file stations.txt variable station_keys {state_or_country station icao latitude longitude elevation metar} variable station_pos { state_or_country 0 1 station 3 18 icao 20 23 latitude_degrees 38 40 latitude_minutes 42 43 latitude_orien 44 44 longitude_degrees 47 49 longitude_minutes 51 52 longitude_orien 53 53 elevation 55 58 metar 62 62 } } proc ::metarstations::make_num { n } { set n [string trimleft [string trim $n] "0"] if { [string length $n] == 0 } { set n 0 } return $n } proc ::metarstations::parse_stations { arnm {metar_only 1} } { upvar $arnm ar variable station_keys variable station_pos variable stations_file set f [open $stations_file] set ll [split [read $f] "\n"] close $f set state "" foreach l $ll { if { [string length [string trim $l]] == 0 } { continue } if { [string index $l 0] eq "!" } { continue } if { [string range $l 0 1] eq "CD" } { continue } if { [llength $l] < 10 } { set state [string trim [string range $l 0 18]] } else { set kstation [string trim [string range $l 3 18]] if { [info exists stationar($kstation)] } { incr stationar($kstation) set kstation "$kstation-#$stationar($kstation)" } else { set stationar($kstation) 1 } foreach {key from to} $station_pos { set ar($kstation,$key) [string trim [string range $l $from $to]] } set ar($kstation,state_or_country) $state set latd [make_num $ar($kstation,latitude_degrees)] set latm [make_num $ar($kstation,latitude_minutes)] set lond [make_num $ar($kstation,longitude_degrees)] set lonm [make_num $ar($kstation,longitude_minutes)] set lat [expr {$latd + $latm/60.0}] if { $ar($kstation,latitude_orien) eq "S" } { set lat [expr {-$lat}] } set lon [expr {$lond + $lonm/60.0}] if { $ar($kstation,longitude_orien) eq "E" } { set lon [expr {-$lon}] } set ar($kstation,latitude) $lat set ar($kstation,longitude) $lon unset ar($kstation,latitude_degrees) unset ar($kstation,latitude_minutes) unset ar($kstation,latitude_orien) unset ar($kstation,longitude_degrees) unset ar($kstation,longitude_minutes) unset ar($kstation,longitude_orien) if { $ar($kstation,metar) eq "X" } { set ar($kstation,metar) 1 } else { set ar($kstation,metar) 0 } set ar($kstation,elevation) [make_num $ar($kstation,elevation)] } } } proc ::metarstations::get_station_by_key_match { key match {n 1} } { variable statar variable station_keys if { ![info exists statar] } { ::metarstations::parse_stations statar } set rl {} foreach k [array names statar "*,$key"] { if { [string match -nocase $match $statar($k)] } { set tl {} set station [lindex [split $k ","] 0] foreach sk $station_keys { lappend tl $sk $statar($station,$sk) } lappend rl $tl if { [llength $rl] >= $n } break } } return $rl } proc ::metarstations::get_station_by_coordinates { lat lon {n 1} } { variable statar variable station_keys if { ![info exists statar] } { ::metarstations::parse_stations statar } set dl {} foreach k [array names statar "*,latitude"] { foreach {station nm} [split $k ","] break set d [expr {pow($lat - $statar($station,latitude), 2) + pow($lon - $statar($station,longitude), 2)}] lappend dl [list $d $station] } set dl [lsort -real -index 0 $dl] set rl {} foreach sl $dl { set station [lindex $sl 1] set tl {} foreach sk $station_keys { lappend tl $sk $statar($station,$sk) } lappend rl $tl if { [llength $rl] >= $n } break } return $rl } ====== Save the code in a file with name `metarstations.tcl` and add a `pkgIndex.tcl` file with this contents: ====== package ifneeded metarstations 0.1 [list source [file join $dir metarstations.tcl]] ====== **Initialisation** To use this package, first download the stations file from the location given above. Now do a ====== package require metarstations ====== and set the path to the stations file (e.g. when downloaded to `/tmp/stations.txt`): ====== set ::metarstations::stations_file /tmp/stations.txt ====== Now you can use the access functions. **Available data** +++ state_or_country Name of state (USA) or country station Name of METAR station (city or airport) icao METAR code of the station latitude Latitude of METAR station, in degrees '''north''' longitude Longitude of METAR station, in degrees '''west''' elevation Elevation of METAR station, in meters metar Indication if METAR data is available or not for +++ **API** ***::metarstations::get_station_by_key_match*** Command: === ::metarstations::get_station_by_key_match ''key'' ''match'' ''?n?'' === All the available data can be queried using the ''key'' names listed above. A ''match'' string is specified in the same format as for the [string match] command. Optionally the maximum number of matches ''n'' can be specified. A list-of-lists is returned. Each sub-list is a key/value list suitable for use with [array set]. ====== # Maximum 100 Metar stations matching a given string puts "" puts "Latitude Longitude Country/state, station" puts "========= ========= ===============================" foreach r [::metarstations::get_station_by_key_match station *paris* 100] { array set mLoc $r puts [format "%9.3f %9.3f %s, %s" $mLoc(latitude) $mLoc(longitude) $mLoc(state_or_country) $mLoc(station)] unset mLoc } ====== The result of this script is: ====== Latitude Longitude Country/state, station ========= ========= =============================== 39.700 87.667 ILLINOIS, PARIS 48.817 -2.317 FRANCE, PARIS MET CENTER 33.617 95.450 TEXAS, PARIS/COX FIELD 48.717 -2.383 FRANCE, PARIS/ORLY 30.383 103.683 TEXAS, ALPINE-CASPARIS 48.967 -2.450 FRANCE, PARIS/LE BOURGET 36.333 88.383 TENNESSEE, PARIS HENRY CTY ====== ***::metarstations::get_station_by_coordinates*** Command: === ::metarstations::get_station_by_coordinates ''lat'' ''lon'' ''?n?'' === Coordinates are given as decimal ''latitude'' and ''longitude''. The latitude must be in degrees '''north''', the longitude in degrees '''west'''. Optionally the maximum number of matches ''n'' can be specified. A list-of-lists is returned. Each sub-list is a key/value list suitable for use with [array set]. ''Closest to'' is calculated as the minimum of: === pow(longitude_station - longitude_query, 2) + pow(latitude_station - latitude_query, 2) === ====== # 20 Metar stations close to given coordinates # latitude: degrees north # longitude: degrees west puts "Latitude Longitude Country/state, station" puts "========= ========= ===============================" foreach r [::metarstations::get_station_by_coordinates 51 -5 20] { array set mLoc $r puts [format "%9.3f %9.3f %s, %s" $mLoc(latitude) $mLoc(longitude) $mLoc(state_or_country) $mLoc(station)] unset mLoc } ====== The result of this script is: ====== Latitude Longitude Country/state, station ========= ========= =============================== 51.000 -5.067 BELGIUM, SCHAFFEN 50.767 -4.950 BELGIUM, GOETSENHOVEN (MI 51.183 -5.217 BELGIUM, BALEN-KEIHEUVEL 50.783 -5.200 BELGIUM, ST. TRUIDEN (BAF 50.750 -4.767 BELGIUM, BEAUVECHAIN (BAF 51.417 -5.000 BELGIUM, WEELDE (MIL) 51.167 -5.467 BELGIUM, KLEINE-BROGEL(BA 50.883 -4.517 BELGIUM, BRUSSELS (MIL) 50.883 -4.517 BELGIUM, BRUSSELS NATIONA 50.917 -5.500 BELGIUM, GENK/ZWARTBERG 50.883 -4.500 BELGIUM, MELSBROEK (BEL-A 51.200 -4.467 BELGIUM, ANTWERP/DEURNE 51.567 -4.917 NETHERLANDS, GILZE-RIJEN RNLA 50.633 -5.450 BELGIUM, BIERSET/LIEGE (M 50.633 -5.450 BELGIUM, BIERSET/LIEGE (C 51.317 -4.500 BELGIUM, BRASSCHAAT (MIL) 50.950 -4.400 BELGIUM, GRIMBERGEN 51.450 -5.417 NETHERLANDS, EINDHOVEN RNLAFB 50.800 -4.350 BELGIUM, UCCLE/UKKLE 50.467 -4.450 BELGIUM, CHARLEROI/GOSSEL ====== **Combined example** ====== # Metar stations close to stations matching a given string foreach r [::metarstations::get_station_by_key_match station *paris* 100] { array set mLoc $r foreach m [::metarstations::get_station_by_coordinates $mLoc(latitude) $mLoc(longitude) 5] { array set tLoc $m lappend ml [list $tLoc(latitude) $tLoc(longitude) $tLoc(state_or_country) $tLoc(station)] } unset mLoc } set ml [lsort -unique $ml] puts "" puts "Latitude Longitude Country/state, station" puts "========= ========= ===============================" foreach r $ml { foreach {latitude longitude state_or_country station} $r break puts [format "%9.3f %9.3f %s, %s" $latitude $longitude $state_or_country $station] } ====== The result for this scripts is: ====== Latitude Longitude Country/state, station ========= ========= =============================== 30.167 102.417 TEXAS, SANDERSON (RAMOS 30.367 104.017 TEXAS, MARFA 30.383 103.683 TEXAS, ALPINE-CASPARIS 30.917 102.917 TEXAS, FORT STOCKTON 31.383 103.517 TEXAS, PECOS CITY 33.167 95.617 TEXAS, SULPHUR SPRINGS 33.600 95.067 TEXAS, CLARKSVILLE 33.617 95.450 TEXAS, PARIS/COX FIELD 33.917 94.867 OKLAHOMA, IDABEL 35.650 88.383 TENNESSEE, FRANKLIN WILKINS 36.083 88.467 TENNESSEE, HUNTINGDON 36.333 88.383 TENNESSEE, PARIS HENRY CTY 36.383 88.983 TENNESSEE, UNION CITY 37.067 88.767 KENTUCKY, PADUCAH 39.450 87.333 INDIANA, TERRE HAUTE 39.467 88.267 ILLINOIS, MATTOON/CHARLEST 39.483 87.250 INDIANA, TERRE HAUTE VOR 39.700 87.667 ILLINOIS, PARIS 40.200 87.600 ILLINOIS, DANVILLE 48.600 -2.317 FRANCE, BRETIGNY-SUR-ORG 48.717 -2.383 FRANCE, PARIS/ORLY 48.750 -2.117 FRANCE, TOUSSUS LE NOBLE 48.767 -2.200 FRANCE, VILLACOUBLAY/VEL 48.817 -2.317 FRANCE, PARIS MET CENTER 48.967 -2.450 FRANCE, PARIS/LE BOURGET 49.017 -2.517 FRANCE, CHARLES DE GAULL 49.250 -2.517 FRANCE, CREIL (FAFB) ====== ---- weather / meteorology ---- [Category Science]