257 lines
9.6 KiB
Plaintext
257 lines
9.6 KiB
Plaintext
#------------------------------------------------------------------------------
|
|
# 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
|