) which is a vast, public domain, database of the USGS topo maps and aerial images in many different resolutions. The only complication in using TerraServer is that it works in UTM coordinates as opposed to latitude and longitude. Last weekend I finally got writing the script that does the conversion into UTM and grabs the images.As a side project, I also wrote this little utility to share with others the core technology of getting maps off of TerraServer. This is just a very basic front-end GUI: you specify latitude and longitude (or UTM if you know it), the resolution and type of image and hit the "Get Map" button and it will display that image.Notes about the images: each image is 200x200 pixels; the resolution is in meters per pixel; not all resolutions are available for all image types.For grins, try looking at latitude 32.15112, longitude -110.83032, resolution 4m/p and as an image.#!/bin/sh
# The next line is executed by /bin/sh, but not tcl \
exec wish $0 ${1+"$@"}
##+##########################################################################
#
# TkTopoMap
#
# Simple front end to Microsoft's terraserver which servers up topo
# maps and images. Has to convert from latitude and longitude into
# Universal Transverse Mercator coordinates. The conversion is performed
# using the WGS-84 datum.
# by Keith Vetter
#
# Revisions:
# KPV Aug 20, 2002 - initial revision
#
##+##########################################################################
package require Tk
if {[catch {package require Img}]} {
wm withdraw .
tk_messageBox -icon error -title TkTopoMap \
-message "TkTopoMap\n\nError: this program requires the Img package."
exit
}
package require http 2.0
proc Init {} {
global env state
set state(latitude) "" ;# Value we'll map
set state(longitude) "" ;# Value we'll map
set state(utm) "" ;# utm of lat/long
set state(vscale) "16 m/p" ;# Resolution of image
set state(vtheme) Topo ;# Type of image
set state(fetching) 0
# Terraserver chunks images so we must adjust utm by this value
array set state {
mult,10 200 mult,11 400 mult,12 800 mult,13 1600
mult,14 3200 mult,15 6400 mult,16 12800 mult,17 25600
mult,18 51200 mult,19 102400
}
# Map resolutions available from the server
set state(resolutions) [list "1 m/p" "2 m/p" "4 m/p" \
"8 m/p" "16 m/p" "32 m/p" \
"64 m/p" "128 m/p" \
"256 m/p" "512 m/p"]
set s 9
foreach r $state(resolutions) {
set state(res,$r) [incr s] ;# String => scale
set state(i,res,$s) $r ;# Scale => string
}
set state(themes) [list Topo Image] ;# Type of images available
set state(theme,Image) 1
set state(theme,Topo) 2
set state(url) "http://terraserver-usa.com/tile.ashx"
append state(url) "?T=\$TTHEME&S=\$SSCALE&X=\$XX&Y=\$YY&Z=\$ZZONE&W=0"
if {[info exists env(temp)]} { ;# We put image into here
set state(tempfile) [file join $env(temp) get_topo.temp]
} elseif {[info exists env(tmp)]} {
set state(tempfile) [file join $env(tmp) get_topo.temp]
} elseif {[file isdirectory /tmp]} {
set state(tempfile) [file join /tmp get_topo.temp]
} else {
set state(tempfile) get_topo.temp
}
trace variable state w DoTrace
}
##+##########################################################################
#
# Display
#
# Sets up our simple dialog
#
proc Display {} {
global state
wm title . TkTopoMap
label .l
frame .f0
frame .f1a -relief sunken -bd 2
frame .f1b -relief sunken -bd 2
frame .f2 -relief sunken -bd 2
frame .f2.a ; frame .f2.b
frame .f3 -relief sunken -bd 2
pack .f0 -side top -fill x -ipady 3 -ipadx 3
pack .f3 .f2 -side bottom -fill x -ipady 3 -ipadx 3
pack .f2.a .f2.b -side left -fill both -expand 1
pack .f1a .f1b -side left -fill both -ipady 3 -ipadx 3
label .title -text TkTopoMap \
-font [eval font create [font actual [.l cget -font]] -size 18]
label .title2 -text "by Keith Vetter" \
-font [eval font create [font actual [.l cget -font]] -size 10]
button .b -text "About TkTopoMap" -command about -font [.title2 cget -font]
pack .title -in .f0 -side top
pack .b -in .f0 -expand 1 -side left -pady 5
label .latlong -text "Latitude/Longitude" \
-font [eval font create [font actual [.l cget -font]] -size 10]
label .lat -text Latitude
label .long -text Longitude
entry .elat -width 15 -justify center -textvariable state(latitude)
entry .elong -width 15 -justify center -textvariable state(longitude)
label .spacer -text ""
button .go1a -text " GetMap " -command GoLatLong
grid .latlong - -in .f1a
grid .lat .elat -sticky ew -in .f1a
grid .long .elong -sticky ew -in .f1a
grid .spacer - -sticky ew -in .f1a
grid .go1a - -in .f1a -pady 10
label .utm -text "UTM" \
-font [eval font create [font actual [.l cget -font]] -size 10]
label .northing -text "Northing"
entry .enorthing -width 15 -justify center -textvariable state(northing)
label .easting -text Easting
entry .eeasting -width 15 -justify center -textvariable state(easting)
label .zone -text Zone
entry .ezone -width 15 -justify center -textvariable state(zone)
button .go1b -text " GetMap " -command GoUTM
grid .utm - -in .f1b
grid .northing .enorthing -sticky ew -in .f1b
grid .easting .eeasting -sticky ew -in .f1b
grid .zone .ezone -sticky ew -in .f1b
grid .go1b - -in .f1b -pady 10
label .sc -text "Resolution"
eval tk_optionMenu .esc state(vscale) $state(resolutions)
.esc config -width 8
label .theme -text Theme
eval tk_optionMenu .etheme state(vtheme) $state(themes)
.etheme config -width 8
pack .sc .esc -side left -in .f2.a
pack .theme .etheme -side left -in .f2.b
label .terra -text "TerraServer:" -justify right
label .eterra -textvariable state(terra) -width 30 -relief sunken
canvas .c -bd 1 -relief ridge -width 200 -height 200
.c create text 100 100 -anchor c -text "no image" -tag what
image create photo map
.c create image 0 0 -anchor nw -image map
grid .terra .eterra -sticky ew -in .f3
grid .c - -in .f3
}
##+##########################################################################
#
# DoTrace
#
# Traces the state variable so we know when to enable certain buttons.
#
proc DoTrace {arg1 arg2 op} {
global state
if {$state(fetching)} { ;# Always off while fetching
.go1a config -state disabled
.go1b config -state disabled
return
}
set new disabled ;# Check the lat/long button
if {$state(latitude) != "" && $state(longitude) != ""} {
set new normal
}
.go1a config -state $new
set new disabled ;# Check the utm button
if {$state(northing) != "" && $state(easting) != "" && $state(zone) != ""} {
set new normal
}
.go1b config -state $new
}
##+##########################################################################
#
# GoUTM
#
# Handles getting map from UTM coordinates.
#
proc GoUTM {} {
global state
set emsg ""
if {! [string is double $state(northing)]} {
set emsg "Can't figure out northing: '$state(northing)'"
} elseif {! [string is double $state(easting)]} {
set emsg "Can't figure out easting: '$state(easting)'"
} elseif {! [string is double $state(zone)]} {
set emsg "Can't figure out zone: '$state(zone)'"
}
if {$emsg != ""} {
tk_messageBox -icon error -message $emsg
return
}
set state(latitude) ""
set state(longitude) ""
GetMap
}
##+##########################################################################
#
# GoLatLong
#
# Handles getting map from latitude and longitude.
#
proc GoLatLong {} {
global state
# Extract the lat/long from the entry boxes
set state(latitude) [string trim $state(latitude)]
set lat $state(latitude)
regsub -nocase {N *(.*)} $lat {\1} lat
regsub -nocase {S *(.*)} $lat {-\1} lat
if {[regexp {^[\d.]+$} $lat]} {
;
} elseif {[regexp {^([\d.]+)\s+([\d.]+)\s+([\d.]+)$} $lat => a b c]} {
set lat [expr {$a + $b/60.0 + $c/60.0/60.0}]
} else {
set emsg "Can't figure out $state(latitude)"
tk_messageBox -icon error -message $emsg
return
}
set state(longitude) [string trim $state(longitude)]
set long $state(longitude)
regsub -nocase {W *(.*)} $long {-\1} long
regsub -nocase {E *(.*)} $long {\1} long
if {[regexp {^-?[\d.]+$} $long]} {
;
} elseif {[regexp {^(-?)([\d.]+)\s+([\d.]+)\s+([\d.]+)$} $long \
=> west a b c]} {
set long [expr {$a + $b/60.0 + $c/60.0/60.0}]
if {$west != ""} { set long [expr {-1 * $long}]}
} else {
set emsg "Can't figure out $state(longitude)"
tk_messageBox -icon error -message $emsg
return
}
# Convert first to UTM coordinates
set state(utm) [ll2utm $lat $long]
foreach {state(northing) state(easting) state(zone)} $state(utm) break
GetMap
}
##+##########################################################################
#
# GetMap
#
# Fetches and displays the map for this utm from the terraserver. We
# fetch the image straight into a temp file for efficiency.
#
proc GetMap {} {
global state
# Adjust scale for this image's theme
set state(scale) $state(res,$state(vscale))
set state(theme) $state(theme,$state(vtheme))
if {$state(theme) == 2 && $state(scale) == 10} { set state(scale) 11 }
if {$state(theme) == 1 && $state(scale) >= 17} { set state(scale) 16 }
set state(vscale) $state(i,res,$state(scale))
catch {image delete map}
.c itemconfig what -text "fetching image"
set state(fetching) 1
# Chunk utm for
set mult $state(mult,$state(scale))
set XX [expr {int($state(easting) / $mult)}]
set YY [expr {int($state(northing) / $mult)}]
set SSCALE $state(scale)
set TTHEME $state(theme)
set ZZONE $state(zone)
set state(xurl) [subst $state(url)]
set state(terra) "X=$XX Y=$YY Z=$ZZONE MULT=$mult"
after 1 GetMap2
}
##+##########################################################################
#
# GetMap2
#
# This gets the actual map. It's a separate procedure so we can call it
# as an after task.
#
proc GetMap2 {} {
global state
;# Fetch the image from terraserver into our tempfile
set f [open $state(tempfile) w]
set token [::http::geturl $state(xurl) -channel $f]
::http::wait $token
close $f
;# Display the image
image create photo map -file $state(tempfile)
file delete $state(tempfile)
set state(fetching) 0
}
##+##########################################################################
#
# ll2utm
#
# Convert latitude and longitude into Universal Transverse Mercator (UTM)
# coordinates. Lots of fun math which I got off the web.
#
proc ll2utm {latitude longitude} {
set PI [expr {atan(1) * 4}]
set K0 0.9996
# WGS-84
set er 6378137 ;# EquatorialRadius
set es2 0.00669438 ;# EccentricitySquared
set es4 [expr {$es2 * $es2}]
set es6 [expr {$es2 * $es2 * $es2}]
# Must be in the range -180 <= long < 180
while {$longitude < -180} { set longitude [expr {$longitude + 360}]}
while {$longitude >= 180} { set longitude [expr {$longitude - 360}]}
# Now convert
set lat_rad [expr {$latitude * $PI / 180.0}]
set long_rad [expr {$longitude * $PI / 180.0}]
set zone [expr {int(($longitude + 180) / 6) + 1}]
if {$latitude >= 56.0 && $latitude < 64.0 &&
$longitude >= 3.0 && $longitude < 12.0} {
$zone = 32
}
if { $latitude >= 72.0 && $latitude < 84.0 } {
if { $longitude >= 0.0 && $longitude < 9.0 } {$zone = 31;}
if { $longitude >= 9.0 && $longitude < 21.0 } {$zone = 33;}
if { $longitude >= 21.0 && $longitude < 33.0 } {$zone = 35;}
if { $longitude >= 33.0 && $longitude < 42.0 } {$zone = 37;}
}
# +3 puts origin in middle of zone
set long_origin [expr {( $zone - 1 ) * 6 - 180 + 3}]
set long_origin_rad [expr {$long_origin * $PI / 180.0}]
set eccPrimeSquared [expr {$es2 / ( 1.0 - $es2 )}]
set N [expr {$er / sqrt( 1.0 - $es2 * sin( $lat_rad ) * sin( $lat_rad ) )}]
set T [expr {tan( $lat_rad ) * tan( $lat_rad )}]
set C [expr {$eccPrimeSquared * cos( $lat_rad ) * cos( $lat_rad )}]
set A [expr {cos( $lat_rad ) * ( $long_rad - $long_origin_rad )}]
set M [expr { $er * ( \
(1.0 - $es2 / 4 - 3 * $es4 / 64 - 5 * $es6 / 256) * $lat_rad \
- (3 * $es2 / 8 + 3 * $es4 / 32 + 45 * $es6 / 1024) * sin(2 * $lat_rad) \
+ (15 * $es4 / 256 + 45 * $es6 / 1024 ) * sin(4 * $lat_rad) \
- (35 * $es6 / 3072 ) * sin(6 * $lat_rad) \
)}]
set easting [expr {$K0 * $N * ( $A + ( 1 - $T + $C ) * $A * $A * $A / 6 \
+ ( 5 - 18 * $T + $T * $T + 72 * $C - 58 * $eccPrimeSquared ) * \
$A * $A * $A * $A * $A / 120 ) + 500000.0}]
set northing [expr {$K0 * ( $M + $N * tan( $lat_rad ) * \
( $A * $A / 2 + ( 5 - $T + 9 * $C + 4 * $C * $C ) * \
$A * $A * $A * $A / 24 + ( 61 - 58 * $T + $T * $T + \
600 * $C - 330 * $eccPrimeSquared ) * \
$A * $A * $A * $A * $A * $A / 720 ) )}]
if {$latitude < 0} { ;# 1e7 meter offset for southern hemisphere
set northing [expr {$northing + 10000000.0}]
}
set northing [expr {int($northing)}]
set easting [expr {int($easting)}]
return [list $northing $easting $zone]
}
##+##########################################################################
#
# about
#
# Your basic about box.
#
proc about {} {
set msg ""
append msg "TkTopoMap\nby Keith Vetter\n\n"
append msg "TkTopoMap is a simple front end for TerraServer, Microsoft's\n"
append msg "database of USGS aerial images and topographic maps.\n"
append msg "This data is free to everyone.\n\n"
append msg "Fetching images from TerraServer is straightforward except\n"
append msg "for the conversion from latitude and longitude into\n"
append msg "Universal Transverse Mercator (UTM) coordinates. The\n"
append msg "conversion is done using the WGS-84 datum.\n\n"
append msg "For more details see http://terraserver.homeadvisor.msn.com."
tk_messageBox -message $msg -title TkTopoMap
}
Init
Display
set state(latitude) 40.069
set state(longitude) -82.517
set state(latitude) "37 48 9"
set state(longitude) "-122 13 29"
if {0} { ;# Beale Airforce base
set state(latitude) 32.15112
set state(longitude) -110.83032
set state(vscale) "4 m/p"
set state(vtheme) Image
}uniquename 2013aug19For readers who do not have the time/facilities/whatever to setup this code and execute it, here is an image that shows the GUI presented by this code.

