or [broken www.nps.gov/prwi/readutm.htm]: try [1].Below are two routines to convert from UTM into lat/lon and vice versa. It uses the WGS-84 datum but a table is provided if you want to use other ones. As usual a simple demo is also provided (warning, absolutely no error checking is done.)##+############################################################# #############
#
# ll2utm -- Converts 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)}]
    if {$latitude > 84.0 || $latitude < -80.0} {
        set letter "Z"
    } else {
        set l [expr {int(($latitude + 80) / 8.0)}]
        set letter [string index "CDEFGHJKLMNPQRSTUVWXX" $l]
    }
    
    return [list $northing $easting $zone $letter]
}
##+##########################################################################
#
# utm2ll -- Converts Universal Transverse Mercator (UTM) into
# latitude and longitude coordinates. More fun math which I got off
# the web.
#
proc utm2ll {northing easting zone {letter S}} {
    set PI [expr {atan(1) * 4}]
    set K0 0.9996
    
    # WGS-84
    set er 6378137                              ;# EquatorialRadius
    set es2 0.00669438                          ;# EccentricitySquared
    set es2x [expr {1.0 - $es2}]
    
    set x [expr {$easting - 500000.0}]
    set northernHemisphere [expr {$letter >= "N"}]
    set y [expr {$northing - ($northernHemisphere ? 0.0 : 10000000.0)}]
    set long_origin [expr {($zone - 1) * 6 - 180 + 3}] ;# +3 puts in middle
    set ep2 [expr {$es2 / $es2x}]
    set e1 [expr {(1.0 - sqrt($es2x)) / (1.0 + sqrt($es2x))}]
    set M [expr {$y / $K0}]
    set mu [expr {$M / ($er * (1.0 - $es2 /4.0 - 3 * $es2 * $es2 /64.0
                               - 5 * $es2 * $es2 * $es2 /256.0))}]
    set phi [expr {$mu + (3 * $e1 / 2 - 27 * $e1 * $e1 * $e1 / 32 ) * sin(2*$mu)
               + (21 * $e1 * $e1 / 16 - 55 * $e1 * $e1 * $e1 * $e1 / 32) *
                   sin(4*$mu ) + (151 * $e1 * $e1 * $e1 / 96 ) * sin(6*$mu)}]
    set N1 [expr {$er / sqrt(1.0 - $es2 * sin($phi) * sin($phi))}]
    set T1 [expr {tan($phi) * tan($phi)}]
    set C1 [expr {$ep2 * cos($phi) * cos($phi)}]
    set R1 [expr {$er * $es2x / pow(1.0 - $es2 * sin($phi) * sin($phi), 1.5)}]
    set D [expr {$x / ($N1 * $K0)}]
    set latitude [expr {$phi - ($N1 * tan($phi) / $R1) * ($D * $D / 2
           - (5 + 3 * $T1 + 10 * $C1 - 4 * $C1 * $C1 - 9 * $ep2) *
           $D * $D * $D * $D / 24
           + (61 + 90 * $T1 + 298 * $C1 + 45 * $T1 * $T1
           - 252 * $ep2 - 3 * $C1 * $C1 ) * $D * $D * $D * $D * $D * $D / 720)}]
    set latitude [expr {$latitude * 180.0 / $PI}]
    set longitude [expr {($D - (1 + 2 * $T1 + $C1) * $D * $D * $D / 6
           + (5 - 2 * $C1 + 28 * $T1 - 3 * $C1 * $C1
           + 8 * $ep2 + 24 * $T1 * $T1) * $D * $D * $D * $D * $D / 120)
           / cos($phi)}]
    set longitude [expr {$long_origin + $longitude * 180.0 / $PI}]
 
    return [list $latitude $longitude]
}
 
## Other datums which could be used here instead:
##
## Name                         EquatorialRadius        EccentricitySquared
## ----                         ----------------        -------------------
## Airy                         6377563                 0.00667054
## Australian National          6378160                 0.006694542
## Bessel 1841                  6377397                 0.006674372
## Bessel 1841 (Nambia)         6377484                 0.006674372
## Clarke 1866                  6378206                 0.006768658
## Clarke 1880                  6378249                 0.006803511
## Everest                      6377276                 0.006637847
## Fischer 1960 (Mercury)       6378166                 0.006693422
## Fischer 1968                 6378150                 0.006693422
## GRS 1967                     6378160                 0.006694605
## GRS 1980                     6378137                 0.00669438
## Helmert 1906                 6378200                 0.006693422
## Hough                        6378270                 0.00672267
## International                6378388                 0.00672267
## Krassovsky                   6378245                 0.006693422
## Modified Airy                6377340                 0.00667054
## Modified Everest             6377304                 0.006637847
## Modified Fischer 1960        6378155                 0.006693422
## South American 1969          6378160                 0.006694542
## WGS 60                       6378165                 0.006693422
## WGS 66                       6378145                 0.006694542
## WGS-72                       6378135                 0.006694318
## WGS-84                       6378137                 0.00669438
 
 
#
# DEMO code
#
package require Tk
 
proc DoDisplay {} {
 
    labelframe .ll -text "Lat/Lon" -padx 5
    label .ll.lat -text Latitude:
    entry .ll.elat -textvariable C(lat)
    label .ll.lon -text Longitude:
    entry .ll.elon -textvariable C(lon)
    grid .ll.lat .ll.elat -sticky w
    grid .ll.lon .ll.elon -sticky w
    grid rowconfigure .ll 1000 -weight 1
 
    labelframe .utm -text UTM -padx 5
    label .utm.north -text Northing:
    entry .utm.enorth -textvariable C(north)
    label .utm.east -text Easting:
    entry .utm.eeast -textvariable C(east)
    label .utm.zone -text Zone:
    entry .utm.ezone -textvariable C(zone)
    label .utm.letter -text Letter:
    entry .utm.eletter -textvariable C(letter)
    
    grid .utm.north .utm.enorth -sticky w
    grid .utm.east .utm.eeast -sticky w
    grid .utm.zone .utm.ezone -sticky w
    grid .utm.letter .utm.eletter -sticky w
 
    frame .2
    button .2.utm -text "==>" -command [list Convert 1]
    button .2.ll -text "<==" -command [list Convert 0]
    grid .2.utm -pady 5
    grid .2.ll
 
    grid .ll .2 .utm -sticky ns -pady {5 10} -padx 5
    grid configure .2 -padx 20
    
    image create photo ::img::blank -width 1 -height 1
    button .about -image ::img::blank -highlightthickness 0 -command \
        [list tk_messageBox -message "UTM Converter\nby Keith Vetter\nMarch, 2004"]
    place .about -in . -relx 1 -rely 1 -anchor se
    focus .ll.elat
}
proc lat2dec {value} {
    set cnt [scan "$value 0 0 0" "%g %g %g" l1 l2 l3]
    set dec [expr {abs($l1) + $l2 / 60.0 + $l3 / 3600.0}]
    if {$l1 < 0} {set dec [expr {-1 * $dec}]}
    return $dec
}    
proc Convert {toUTM} {
    global C
    
    if {$toUTM} {
        foreach var {north east zone letter} { set C($var) "" }
        foreach var {lat lon} {
            set C($var) [string trim $C($var)]
            if {$C($var) eq ""} return
            set $var [lat2dec $C($var)]
        }
        set utm [ll2utm $lat $lon]
        foreach {C(north) C(east) C(zone) C(letter)} $utm break
    } else {
        foreach var {lat lon} { set C($var) "" } 
        foreach var {north east zone letter} { 
            set C($var) [string trim $C($var)]
            if {$C($var) eq "" && $var ne "letter"} return
        }
        set ll [utm2ll $C(north) $C(east) $C(zone) $C(letter)]
        foreach {C(lat) C(lon)} $ll break
    }
}
DoDisplay
set C(lat) "40 4 4.5"
set C(lon) [lat2dec "-82 31 12.6"]PWE: The first line of ll2utm proc
 if {$longitude > 0} {set longitude [expr {-1 * $longitude}]}makes this code correct for only the western hemisphere. Probably more correct is to remove the first line and enter western hemisphere longitudes as negative numbers (as seems to be the normal convention). fixedPD: These formulae appear to be the same as in Snyder http://onlinepubs.er.usgs.gov/djvu/PP/PP_1395.pdf
 - see formulae 8-9 to 8-25. This is very slow.  A better solution would involve an extension to use the proj.4 library http://trac.osgeo.org/proj/wiki/ProjAPI
Screenshots
uniquename 2013aug18The screenshot above is on an 'external' site (flickr) and is likely to go dead. Also the image is too small to read the text. Here is another screenshot of the UTM GUI --- 'locally stored', on the disk drives of the server of this wiki --- a larger image.
Related topics: geodesy


