You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
9.6 KiB
256 lines
9.6 KiB
#------------------------------------------------------------------------------ |
|
# File: gps2utm.config |
|
# |
|
# Description: Generate UTM tags from GPS information |
|
# |
|
# Requires: ExifTool version 7.00 or later |
|
# |
|
# Notes: Uses GPSMapDatum, GPSLatitude and GPSLongitude to generate |
|
# UTMCoordinates, UTMZone, UTMEasting and UTMNorthing. If |
|
# GPSMapDatum is not available then "WGS84" is assumed. |
|
# |
|
# Example: > exiftool -config gps2utm.config "-utm*" t/images/GPS.jpg |
|
# UTM Coordinates : 30U 569475.596m E 6094180.754m N |
|
# UTM Easting : 569475.595558165 |
|
# UTM Northing : 6094180.75443061 |
|
# UTM Zone : 30U |
|
# |
|
# Caveats: When used to convert EXIF GPS coordinates, the reference |
|
# direction tags (GPSLatitudeRef/GPSLongitudeRef) must exist or |
|
# the calculated UTM coordinates may be in the wrong hemisphere |
|
# |
|
# Revisions: 2016/03/08 - Bryan K. Williams (aka StarGeek) Created |
|
# 2016/03/09 - PH removed library dependency and re-organized |
|
#------------------------------------------------------------------------------ |
|
|
|
my $deg2rad = 3.14159265358979 / 180; |
|
|
|
sub tan($) |
|
{ |
|
return sin($_[0]) / cos($_[0]); |
|
} |
|
|
|
#=============================================================================== |
|
# the following code is by Graham Crookham: |
|
# http://search.cpan.org/~grahamc/Geo-Coordinates-UTM/ (version 0.11) |
|
|
|
# remove all markup from an ellipsoid name, to increase the chance |
|
# that a match is found. |
|
sub _cleanup_name($) |
|
{ my $copy = lc(shift); |
|
for($copy) |
|
{ s/\([^)]+\)//g; # remove text between parentheses |
|
s/[\s-]//g; # no blanks or dashes |
|
} |
|
$copy; |
|
} |
|
|
|
# Ellipsoid array (name,equatorial radius,square of eccentricity) |
|
# Same data also as hash with key eq name (in variations) |
|
|
|
my (@Ellipsoid, %Ellipsoid); |
|
|
|
BEGIN { # Initialize this before other modules get a chance |
|
@Ellipsoid = |
|
( [ "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 1830 India", 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 ] |
|
, [ "Everest 1830 Malaysia", 6377299, 0.006637847] |
|
, [ "Everest 1956 India", 6377301, 0.006637847] |
|
, [ "Everest 1964 Malaysia and Singapore", 6377304, 0.006637847] |
|
, [ "Everest 1969 Malaysia", 6377296, 0.006637847] |
|
, [ "Everest Pakistan", 6377296, 0.006637534] |
|
, [ "Indonesian 1974", 6378160, 0.006694609] |
|
, [ "Arc 1950", 6378249.145,0.006803481] |
|
, [ "NAD 27",6378206.4,0.006768658] |
|
, [ "NAD 83",6378137,0.006694384] |
|
); |
|
|
|
# calc ecc as |
|
# a = semi major axis |
|
# b = semi minor axis |
|
# e^2 = (a^2-b^2)/a^2 |
|
# For clarke 1880 (Arc1950) a=6378249.145 b=6356514.966398753 |
|
# e^2 (40682062155693.23 - 40405282518051.34) / 40682062155693.23 |
|
# e^2 = 0.0068034810178165 |
|
|
|
foreach my $el (@Ellipsoid) |
|
{ my ($name, $eqrad, $eccsq) = @$el; |
|
$Ellipsoid{$name} = $el; |
|
$Ellipsoid{_cleanup_name $name} = $el; |
|
} |
|
} |
|
|
|
# Returns "official" name, equator radius and square eccentricity |
|
# The specified name can be numeric (for compatibility reasons) or |
|
# a more-or-less exact name |
|
# Examples: my($name, $r, $sqecc) = ellipsoid_info 'wgs84'; |
|
# my($name, $r, $sqecc) = ellipsoid_info 'WGS 84'; |
|
# my($name, $r, $sqecc) = ellipsoid_info 'WGS-84'; |
|
# my($name, $r, $sqecc) = ellipsoid_info 'WGS-84 (new specs)'; |
|
# my($name, $r, $sqecc) = ellipsoid_info 22; |
|
|
|
sub ellipsoid_info($) |
|
{ my $id = shift; |
|
|
|
my $el = $id !~ m/\D/ |
|
? $Ellipsoid[$id-1] # old system counted from 1 |
|
: $Ellipsoid{$id} || $Ellipsoid{_cleanup_name $id}; |
|
|
|
defined $el ? @$el : (); |
|
} |
|
|
|
# Expects Ellipsoid Number or name, Latitude, Longitude |
|
# (Latitude and Longitude in decimal degrees) |
|
# Returns UTM Zone, UTM Easting, UTM Northing |
|
|
|
sub latlon_to_utm(@) |
|
{ my ($ellips, $latitude, $longitude) = @_; |
|
|
|
die("Longitude value ($longitude) invalid\n") |
|
if $longitude < -180 || $longitude > 180; |
|
|
|
my $long2 = $longitude - int(($longitude + 180)/360) * 360; |
|
my $zone = _latlon_zone_number($latitude, $long2); |
|
|
|
_latlon_to_utm($ellips || 'WGS84', $zone, $latitude, $long2); |
|
} |
|
|
|
sub _latlon_zone_number |
|
{ my ($latitude, $long2) = @_; |
|
|
|
my $zone = int( ($long2 + 180)/6) + 1; |
|
if($latitude >= 56.0 && $latitude < 64.0 && $long2 >= 3.0 && $long2 < 12.0) |
|
{ $zone = 32; |
|
} |
|
if($latitude >= 72.0 && $latitude < 84.0) { |
|
$zone = ($long2 >= 0.0 && $long2 < 9.0) ? 31 |
|
: ($long2 >= 9.0 && $long2 < 21.0) ? 33 |
|
: ($long2 >= 21.0 && $long2 < 33.0) ? 35 |
|
: ($long2 >= 33.0 && $long2 < 42.0) ? 37 |
|
: $zone; |
|
} |
|
return $zone; |
|
} |
|
|
|
sub _latlon_to_utm |
|
{ my ($ellips, $zone, $latitude, $long2) = @_; |
|
|
|
my ($name, $radius, $eccentricity) = ellipsoid_info $ellips |
|
or die("Ellipsoid value ($ellips) invalid\n"); |
|
|
|
my $lat_radian = $deg2rad * $latitude; |
|
my $long_radian = $deg2rad * $long2; |
|
|
|
my $k0 = 0.9996; # scale |
|
|
|
my $longorigin = ($zone - 1)*6 - 180 + 3; |
|
my $longoriginradian = $deg2rad * $longorigin; |
|
my $eccentprime = $eccentricity/(1-$eccentricity); |
|
|
|
my $N = $radius / sqrt(1-$eccentricity * sin($lat_radian)*sin($lat_radian)); |
|
my $T = tan($lat_radian) * tan($lat_radian); |
|
my $C = $eccentprime * cos($lat_radian)*cos($lat_radian); |
|
my $A = cos($lat_radian) * ($long_radian - $longoriginradian); |
|
my $M = $radius |
|
* ( ( 1 - $eccentricity/4 - 3 * $eccentricity * $eccentricity/64 |
|
- 5 * $eccentricity * $eccentricity * $eccentricity/256 |
|
) * $lat_radian |
|
- ( 3 * $eccentricity/8 + 3 * $eccentricity * $eccentricity/32 |
|
+ 45 * $eccentricity * $eccentricity * $eccentricity/1024 |
|
) * sin(2 * $lat_radian) |
|
+ ( 15 * $eccentricity * $eccentricity/256 + |
|
45 * $eccentricity * $eccentricity * $eccentricity/1024 |
|
) * sin(4 * $lat_radian) |
|
- ( 35 * $eccentricity * $eccentricity * $eccentricity/3072 |
|
) * sin(6 * $lat_radian) |
|
); |
|
|
|
my $utm_easting = $k0*$N*($A+(1-$T+$C)*$A*$A*$A/6 |
|
+ (5-18*$T+$T*$T+72*$C-58*$eccentprime)*$A*$A*$A*$A*$A/120) |
|
+ 500000.0; |
|
|
|
my $utm_northing= $k0 * ( $M + $N*tan($lat_radian) * ( $A*$A/2+(5-$T+9*$C+4*$C*$C)*$A*$A*$A*$A/24 + (61-58*$T+$T*$T+600*$C-330*$eccentprime) * $A*$A*$A*$A*$A*$A/720)); |
|
|
|
$utm_northing += 10000000.0 if $latitude < 0; |
|
|
|
my $utm_letter |
|
= ( 84 >= $latitude && $latitude >= 72) ? 'X' |
|
: ( 72 > $latitude && $latitude >= 64) ? 'W' |
|
: ( 64 > $latitude && $latitude >= 56) ? 'V' |
|
: ( 56 > $latitude && $latitude >= 48) ? 'U' |
|
: ( 48 > $latitude && $latitude >= 40) ? 'T' |
|
: ( 40 > $latitude && $latitude >= 32) ? 'S' |
|
: ( 32 > $latitude && $latitude >= 24) ? 'R' |
|
: ( 24 > $latitude && $latitude >= 16) ? 'Q' |
|
: ( 16 > $latitude && $latitude >= 8) ? 'P' |
|
: ( 8 > $latitude && $latitude >= 0) ? 'N' |
|
: ( 0 > $latitude && $latitude >= -8) ? 'M' |
|
: ( -8 > $latitude && $latitude >= -16) ? 'L' |
|
: (-16 > $latitude && $latitude >= -24) ? 'K' |
|
: (-24 > $latitude && $latitude >= -32) ? 'J' |
|
: (-32 > $latitude && $latitude >= -40) ? 'H' |
|
: (-40 > $latitude && $latitude >= -48) ? 'G' |
|
: (-48 > $latitude && $latitude >= -56) ? 'F' |
|
: (-56 > $latitude && $latitude >= -64) ? 'E' |
|
: (-64 > $latitude && $latitude >= -72) ? 'D' |
|
: (-72 > $latitude && $latitude >= -80) ? 'C' |
|
: die("Latitude ($latitude) out of UTM range\n"); |
|
|
|
$zone .= $utm_letter; |
|
|
|
($zone, $utm_easting, $utm_northing); |
|
} |
|
# End Graham Crookham code |
|
#=============================================================================== |
|
|
|
%Image::ExifTool::UserDefined = ( |
|
'Image::ExifTool::Composite' => { |
|
UTMCoordinates => { |
|
Desire => { |
|
0 => 'GPSMapDatum', |
|
}, |
|
Require => { |
|
1 => 'GPSLatitude', |
|
2 => 'GPSLongitude', |
|
}, |
|
ValueConv => 'join " ", latlon_to_utm(@val)', |
|
PrintConv => 'sprintf("%s %.3fm E %.3fm N", split(" ", $val))', |
|
}, |
|
UTMZone => { |
|
Require => 'UTMCoordinates', |
|
ValueConv => 'my @a=split(" ",$val); $a[0]', |
|
}, |
|
UTMEasting => { |
|
Require => 'UTMCoordinates', |
|
ValueConv => 'my @a=split(" ",$val); $a[1]', |
|
}, |
|
UTMNorthing => { |
|
Require => 'UTMCoordinates', |
|
ValueConv => 'my @a=split(" ",$val); $a[2]', |
|
}, |
|
}, |
|
); |
|
|
|
#------------------------------------------------------------------------------ |
|
1; #end
|
|
|