Updated 2007-10-24 22:13:12 by jdc

jdc 27-jun-2007

World locations package edit

A list of 13000 waypoints can be found as a spreadsheet here: http://www.tapr.org/~kh2z/Waypoint/WaypointFiles.htm. To use this file, export the locations tab as a CSV file with ; or , used as field separator and " as string delimiter. (TODO: use csv package)

The following code uses this file to get information about locations based on name or coordinates:
package provide worldlocations 0.1

namespace eval ::worldlocations {
    variable locations_file WorldLocations.csv
    variable location_keys     {major_location name abbrev state country country_code info airport_id icao latitude longitude}
    variable location_csv_keys {major_location name abbrev state country country_code info airport_id icao latitude ns longitude ew}
}

proc ::worldlocations::make_num { n } {
    set n [string trimleft [string trim $n] "0"]
    if { [string length $n] == 0 } {
        set n 0
    }
    return $n
}

proc ::worldlocations::make_num { n } {
    set n [string trimleft [string trim $n] "0"]
    if { [string length $n] == 0 } {
        set n 0
    }
    return $n
}

proc ::worldlocations::parse_locations { arnm {metar_only 1} } {
    upvar $arnm ar
    variable location_keys
    variable location_csv_keys
    variable locations_file

    set f [open $locations_file]
    set ll [split [read $f] "\n"]
    close $f

    foreach l $ll {
        foreach $location_csv_keys [split $l ";,"] break
        set kname [string trim $name " \""]
        if { [info exists ar($kname,name)] } {
            incr locid($kname)
            append kname "-#$locid($kname)"
        } else {
            set locid($kname) 0
        }
        set ar($kname,name) [string trim $name " \""]
        set ar($kname,major_location) [make_num [string trim $major_location " \""]]
        set ar($kname,abbrev) [string trim $abbrev " \""]
        set ar($kname,state) [string trim $state " \""]
        set ar($kname,country) [string trim $country " \""]
        set ar($kname,country_code) [string trim $country_code " \""]
        set ar($kname,info) [string trim $info " \""]
        set ar($kname,airport_id) [string trim $airport_id " \""]
        set ar($kname,icao) [string trim $icao " \""]
        set ar($kname,latitude) [make_num [string trim $latitude " \""]]
        set ar($kname,longitude) [make_num [string trim $longitude " \""]]
        if { [string trim $ns " \""] eq "South" } {
            set ar($kname,latitude) -$ar($kname,latitude)
        }
        if { [string trim $ew " \""] eq "East" } {
            set ar($kname,longitude) -$ar($kname,longitude)
        }
    }
}

proc ::worldlocations::get_location_by_key_match { key match {n 1} } {
    variable statar
    variable location_keys
    if { ![info exists statar] } {
        ::worldlocations::parse_locations statar
    }
    set rl {}
    foreach k [array names statar "*,$key"] {
        if { [string match -nocase $match $statar($k)] } {
            set tl {}
            set location [lindex [split $k ","] 0]
            foreach sk $location_keys {
                lappend tl $sk $statar($location,$sk)
            }
            lappend rl $tl
            if { [llength $rl] >= $n } break
        }
    }
    return $rl
}

proc ::worldlocations::get_location_by_coordinates { lat lon {n 1} } {
    variable statar
    variable location_keys
    if { ![info exists statar] } {
        ::worldlocations::parse_locations statar
    }
    set dl {}
    foreach k [array names statar "*,latitude"] {
        foreach {location nm} [split $k ","] break
        set d [expr {pow($lat - $statar($location,latitude), 2) + pow($lon - $statar($location,longitude), 2)}]
        lappend dl [list $d $location]
    }
    set dl [lsort -real -index 0 $dl]
    set rl {}
    foreach sl $dl {
        set location [lindex $sl 1]
        set tl {}
        foreach sk $location_keys {
            lappend tl $sk $statar($location,$sk)
        }
        lappend rl $tl
        if { [llength $rl] >= $n } break
    }
    return $rl
}

Save the code in a file with name worldlocations.tcl and add a pkgIndex.tcl file with this contents:
package ifneeded worldlocations 0.1 [list source [file join $dir worldlocations.tcl]]

Initialisation edit

After downloading the locations file and converting it to CSV format, do a:
package require worldlocations

and set the path of the locations file:
set ::worldlocations::locations_file /tmp/WorldLocations10.csv

Available data edit

name
Location name
major_location
Indication if location is a major location (capital, large city) or not
abbrev
Abbreviation of the location, unique in country of for USA unique in state
state
State or province of the location
country
Country of the location
country_code
Country abbreviation for the location's country
info
Additional info for the location
airport_id
Airport identifier
icoa
METAR code of the location
latitude
Location latitude in degrees north
longitude
Location longitude in degrees west

API edit

::worldlocations::get_location_by_key_match

Command:
::worldlocations::get_location_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.
# Locations matching a given string
puts ""
puts "Latitude  Longitude Country/state, location"
puts "========= ========= ==============================="
foreach r [::worldlocations::get_location_by_key_match name *greenville* 100] {
    array set mLoc $r
    puts [format "%9.3f %9.3f %s, %s, %s, %s" $mLoc(latitude) $mLoc(longitude) $mLoc(country) $mLoc(state) $mLoc(name) $mLoc(info)]
    unset mLoc
}

The result of this script is:
Latitude  Longitude Country/state, location
========= ========= ===============================
   51.000    -5.067 BELGIUM, SCHAFFEN, 
   50.783    -4.950 BELGIUM, GOETSENHOVEN (MIL), 
   50.800    -5.200 BELGIUM, ST. TRUIDEN (BAFB), 
   50.750    -4.767 BELGIUM, BEAUVECHAIN (BAFB), 
   51.417    -5.000 BELGIUM, WEELDE (MIL), 
   51.167    -5.467 BELGIUM, KLEINE-BROGEL(BAFB), 
   50.900    -4.500 BELGIUM, BRUSSELS (BRUXELLES), NATIONAL/ZAVENTEM
   51.017    -5.517 BELGIUM, GENK, ZWARTBERG
   51.183    -4.467 BELGIUM, ANTWERPEN (ANTWERP), DEURNE
   50.650    -5.450 BELGIUM, BIERSET/LIEGE (MIL), 
   51.567    -4.917 NETHERLANDS, BREDA, MILITAIR VLIEGVELD GILZE-RIJEN
   50.633    -5.450 BELGIUM, LIEGE (LUTTICH), BIERSET
   51.450    -5.367 NETHERLANDS, EINDHOVEN, MILITAIR VLIEGVELD EINDHOVEN
   51.333    -4.500 BELGIUM, BRASSCHAAT (MIL), 
   51.250    -5.600 NETHERLANDS, WEERT, LUCHTVAARTTERREIN BUDEL
   50.467    -4.450 BELGIUM, CHARLEROI, GOSSELIES
   50.917    -5.783 NETHERLANDS, BEEK, MAASTRICHT/AACHEN AIRPORT
   51.450    -4.333 NETHERLANDS, WOENSDRECHT, MILITAIR VLIEGVELD WOENSDRECHT
   50.233    -4.650 BELGIUM, FLORENNES (BEL-AFB), 
   51.650    -5.700 NETHERLANDS, UDEN, MILITAIR VLIEGVELD VOLKEL

::worldlocations::get_location_by_coordinates

::worldlocations::get_location_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 locations close to given coordinates
# latitude: degrees north
# longitude: degrees west
puts "Latitude  Longitude Country/state, location"
puts "========= ========= ==============================="
foreach r [::worldlocations::get_location_by_coordinates 51 -5 20] {
    array set mLoc $r
    puts [format "%9.3f %9.3f %s, %s, %s" $mLoc(latitude) $mLoc(longitude) $mLoc(country) $mLoc(name) $mLoc(info)]
    unset mLoc
}

The result of this script is:
Latitude  Longitude Country/state, location
========= ========= ===============================
   51.000    -5.067 BELGIUM, SCHAFFEN, 
   50.783    -4.950 BELGIUM, GOETSENHOVEN (MIL), 
   50.800    -5.200 BELGIUM, ST. TRUIDEN (BAFB), 
   50.750    -4.767 BELGIUM, BEAUVECHAIN (BAFB), 
   51.417    -5.000 BELGIUM, WEELDE (MIL), 
   51.167    -5.467 BELGIUM, KLEINE-BROGEL(BAFB), 
   50.900    -4.500 BELGIUM, BRUSSELS (BRUXELLES), NATIONAL/ZAVENTEM
   51.017    -5.517 BELGIUM, GENK, ZWARTBERG
   51.183    -4.467 BELGIUM, ANTWERPEN (ANTWERP), DEURNE
   50.650    -5.450 BELGIUM, BIERSET/LIEGE (MIL), 
   51.567    -4.917 NETHERLANDS, BREDA, MILITAIR VLIEGVELD GILZE-RIJEN
   50.633    -5.450 BELGIUM, LIEGE (LUTTICH), BIERSET
   51.450    -5.367 NETHERLANDS, EINDHOVEN, MILITAIR VLIEGVELD EINDHOVEN
   51.333    -4.500 BELGIUM, BRASSCHAAT (MIL), 
   51.250    -5.600 NETHERLANDS, WEERT, LUCHTVAARTTERREIN BUDEL
   50.467    -4.450 BELGIUM, CHARLEROI, GOSSELIES
   50.917    -5.783 NETHERLANDS, BEEK, MAASTRICHT/AACHEN AIRPORT
   51.450    -4.333 NETHERLANDS, WOENSDRECHT, MILITAIR VLIEGVELD WOENSDRECHT
   50.233    -4.650 BELGIUM, FLORENNES (BEL-AFB), 
   51.650    -5.700 NETHERLANDS, UDEN, MILITAIR VLIEGVELD VOLKEL

Combined example edit

# Locations close to locations matching a given string
foreach r [::worldlocations::get_location_by_key_match name *paris* 100] {
    array set mLoc $r
    foreach m [::worldlocations::get_location_by_coordinates $mLoc(latitude) $mLoc(longitude) 5] {
        array set tLoc $m
        lappend ml [list $tLoc(latitude) $tLoc(longitude) $tLoc(country) $mLoc(state) $tLoc(name) $tLoc(info)]
    }
    unset mLoc
}
set ml [lsort -unique $ml]
puts ""
puts "Latitude  Longitude Country/state, location"
puts "========= ========= ==============================="
foreach r $ml {
    foreach {latitude longitude country state location info} $r break
    puts [format "%9.3f %9.3f %s, %s, %s, %s" $latitude $longitude $country $state $location $info]
}

The result of this script is:
Latitude  Longitude Country/state, location
========= ========= ===============================
   33.214    95.236 USA, TX, MOUNT VERNON, FRANKLIN COUNTY
   33.593    95.063 USA, TX, CLARKSVILLE, CLARKSVILLE-RED RIVER COUNTY
   33.636    95.451 USA, TX, PARIS, COX FLD
   34.033    95.550 USA, TX, HUGO, STAN STAMPER MUNICIPAL AIRPORT
   34.035    95.542 USA, TX, HUGO, STAN STAMPER MUNI
   35.087    93.429 USA, AR, DANVILLE, DANVILLE MUNI
   35.149    93.865 USA, AR, BOONEVILLE, BOONEVILLE MUNI
   35.300    93.683 USA, AR, PARIS /SUBIACO/, PARIS MUNI
   35.471    93.427 USA, AR, CLARKSVILLE, CLARKSVILLE MUNI
   35.511    93.839 USA, AR, OZARK, OZARK-FRANKLIN COUNTY
   36.000    88.467 USA, TN, HUNTINGDON, HUNTINGDON AIRPORT
   36.011    88.123 USA, TN, CAMDEN, BENTON COUNTY
   36.090    88.463 USA, TN, HUNTINGDON, CARROLL COUNTY
   36.338    88.383 USA, TN, PARIS, HENRY COUNTY
   36.666    88.371 USA, TN, MURRAY, KYLE-OAKLEY FIELD
   39.452    87.309 USA, IL, TERRE HAUTE, HULMAN REGIONAL
   39.548    87.377 USA, IL, TERRE HAUTE, SKY KING
   39.700    87.671 USA, IL, PARIS, EDGAR COUNTY
   39.712    87.401 USA, IL, CLINTON, CLINTON
   40.199    87.595 USA, IL, DANVILLE, VERMILION COUNTY
   42.046   110.966 USA, ID, COKEVILLE, COKEVILLE MUNI
   42.108   111.901 USA, ID, PRESTON, PRESTON
   42.247   111.338 USA, ID, PARIS, BEAR LAKE COUNTY
   42.317   111.300 USA, ID, MONTPELIER, MIAMI PUBLIC SEAPLANE BASE
   42.641   111.580 USA, ID, SODA SPRINGS, ALLEN H TIGERT
   48.600    -2.333 FRANCE, , BRETIGNY-SUR-ORGE, 
   48.717    -2.383 FRANCE, , PARIS, ORLY
   48.767    -2.200 FRANCE, , VILLACOUBLAY/VELIZY, 
   48.833    -2.333 FRANCE, , PARIS, METROPOLIAN AREA
   48.967    -2.450 FRANCE, , PARIS, LE BOURGET
   49.017    -2.550 FRANCE, , PARIS, CHARLES-DE-GAULLE
   49.250    -2.517 FRANCE, , CREIL (FAFB), 

Counting locations:
puts "Major cities: [llength [::worldlocations::get_location_by_key_match major_location 1 100000]]"
puts "ICOA: [llength [::worldlocations::get_location_by_key_match icoa \[a-z\]* 100000]]"
puts "All: [llength [::worldlocations::get_location_by_key_match name * 100000]]"

Result:
Major cities: 330
ICOA: 6716
All: 13451

RS 2007-06-27: A very rich source for geodata (cities and towns, down to very small places) is fallingrain http://www.fallingrain.com/world/

RS 2007-07-02: Yet another rich source of geodata (except for the US) is http://earth-info.nga.mil/gns/html/namefiles.htm - ordered by country, zipped text files, one line per item. For Belgium for instance, it has 28359 items.

jdc 2007-07-02: Geodata for the US can be found here: http://geonames.usgs.gov/domestic/download_data.htm