[jdc] 27-jun-2007 **World locations package** 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** 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** +++ 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** ***::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** ====== # 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 http://www.fallingrain.com/world/ ---- [Category Geography]