Updated 2010-06-08 23:49:47 by zorro

jdc 22-jun-2007

Metar stations package edit

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 "<unknown>"
    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 edit

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 edit

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 edit

::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 edit

# 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)


zorro - 2010-06-08 19:49:47

I believe your coordinate conversion is incorrect: you should negate the longitude if the orientation is "W". (not "E")