line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
3
|
|
|
3
|
|
69556
|
use strict; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
123
|
|
2
|
3
|
|
|
3
|
|
16
|
use warnings; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
122
|
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Geo::Coordinates::UTM; |
5
|
|
|
|
|
|
|
|
6
|
3
|
|
|
3
|
|
17
|
use Carp; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
367
|
|
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
16
|
use base 'Exporter'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
477
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our @EXPORT = qw( latlon_to_utm latlon_to_utm_force_zone |
11
|
|
|
|
|
|
|
utm_to_latlon utm_to_mgrs |
12
|
|
|
|
|
|
|
latlon_to_mgrs mgrs_to_utm mgrs_to_latlon |
13
|
|
|
|
|
|
|
ellipsoid_info ellipsoid_names ); |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
our $VERSION = '0.11'; |
16
|
|
|
|
|
|
|
|
17
|
3
|
|
|
3
|
|
2974
|
use Math::Trig; |
|
3
|
|
|
|
|
85207
|
|
|
3
|
|
|
|
|
1552
|
|
18
|
|
|
|
|
|
|
my $deg2rad = pi / 180; |
19
|
|
|
|
|
|
|
my $rad2deg = 180 / pi; |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# remove all markup from an ellipsoid name, to increase the chance |
22
|
|
|
|
|
|
|
# that a match is found. |
23
|
|
|
|
|
|
|
sub _cleanup_name($) |
24
|
355
|
|
|
355
|
|
934
|
{ my $copy = lc(shift); |
25
|
355
|
|
|
|
|
802
|
for($copy) |
26
|
355
|
|
|
|
|
789
|
{ s/\([^)]+\)//g; # remove text between parantheses |
27
|
355
|
|
|
|
|
2568
|
s/[\s-]//g; # no blanks or dashes |
28
|
|
|
|
|
|
|
} |
29
|
355
|
|
|
|
|
19168
|
$copy; |
30
|
|
|
|
|
|
|
} |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# Ellipsoid array (name,equatorial radius,square of eccentricity) |
33
|
|
|
|
|
|
|
# Same data also as hash with key eq name (in variations) |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
my (@Ellipsoid, %Ellipsoid); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
BEGIN { # Initialize this before other modules get a chance |
39
|
3
|
|
|
3
|
|
86
|
@Ellipsoid = |
40
|
|
|
|
|
|
|
( [ "Airy", 6377563, 0.00667054] |
41
|
|
|
|
|
|
|
, [ "Australian National", 6378160, 0.006694542] |
42
|
|
|
|
|
|
|
, [ "Bessel 1841", 6377397, 0.006674372] |
43
|
|
|
|
|
|
|
, [ "Bessel 1841 Nambia", 6377484, 0.006674372] |
44
|
|
|
|
|
|
|
, [ "Clarke 1866", 6378206, 0.006768658] |
45
|
|
|
|
|
|
|
, [ "Clarke 1880", 6378249, 0.006803511] |
46
|
|
|
|
|
|
|
, [ "Everest 1830 India", 6377276, 0.006637847] |
47
|
|
|
|
|
|
|
, [ "Fischer 1960 Mercury", 6378166, 0.006693422] |
48
|
|
|
|
|
|
|
, [ "Fischer 1968", 6378150, 0.006693422] |
49
|
|
|
|
|
|
|
, [ "GRS 1967", 6378160, 0.006694605] |
50
|
|
|
|
|
|
|
, [ "GRS 1980", 6378137, 0.00669438] |
51
|
|
|
|
|
|
|
, [ "Helmert 1906", 6378200, 0.006693422] |
52
|
|
|
|
|
|
|
, [ "Hough", 6378270, 0.00672267] |
53
|
|
|
|
|
|
|
, [ "International", 6378388, 0.00672267] |
54
|
|
|
|
|
|
|
, [ "Krassovsky", 6378245, 0.006693422] |
55
|
|
|
|
|
|
|
, [ "Modified Airy", 6377340, 0.00667054] |
56
|
|
|
|
|
|
|
, [ "Modified Everest", 6377304, 0.006637847] |
57
|
|
|
|
|
|
|
, [ "Modified Fischer 1960", 6378155, 0.006693422] |
58
|
|
|
|
|
|
|
, [ "South American 1969", 6378160, 0.006694542] |
59
|
|
|
|
|
|
|
, [ "WGS 60", 6378165, 0.006693422] |
60
|
|
|
|
|
|
|
, [ "WGS 66", 6378145, 0.006694542] |
61
|
|
|
|
|
|
|
, [ "WGS-72", 6378135, 0.006694318] |
62
|
|
|
|
|
|
|
, [ "WGS-84", 6378137, 0.00669438 ] |
63
|
|
|
|
|
|
|
, [ "Everest 1830 Malaysia", 6377299, 0.006637847] |
64
|
|
|
|
|
|
|
, [ "Everest 1956 India", 6377301, 0.006637847] |
65
|
|
|
|
|
|
|
, [ "Everest 1964 Malaysia and Singapore", 6377304, 0.006637847] |
66
|
|
|
|
|
|
|
, [ "Everest 1969 Malaysia", 6377296, 0.006637847] |
67
|
|
|
|
|
|
|
, [ "Everest Pakistan", 6377296, 0.006637534] |
68
|
|
|
|
|
|
|
, [ "Indonesian 1974", 6378160, 0.006694609] |
69
|
|
|
|
|
|
|
, [ "Arc 1950", 6378249.145,0.006803481] |
70
|
|
|
|
|
|
|
, [ "NAD 27",6378206.4,0.006768658] |
71
|
|
|
|
|
|
|
, [ "NAD 83",6378137,0.006694384] |
72
|
|
|
|
|
|
|
); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# calc ecc as |
75
|
|
|
|
|
|
|
# a = semi major axis |
76
|
|
|
|
|
|
|
# b = semi minor axis |
77
|
|
|
|
|
|
|
# e^2 = (a^2-b^2)/a^2 |
78
|
|
|
|
|
|
|
# For clarke 1880 (Arc1950) a=6378249.145 b=6356514.966398753 |
79
|
|
|
|
|
|
|
# e^2 (40682062155693.23 - 40405282518051.34) / 40682062155693.23 |
80
|
|
|
|
|
|
|
# e^2 = 0.0068034810178165 |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
|
83
|
3
|
|
|
|
|
12
|
foreach my $el (@Ellipsoid) |
84
|
96
|
|
|
|
|
133
|
{ my ($name, $eqrad, $eccsq) = @$el; |
85
|
96
|
|
|
|
|
195
|
$Ellipsoid{$name} = $el; |
86
|
96
|
|
|
|
|
144
|
$Ellipsoid{_cleanup_name $name} = $el; |
87
|
|
|
|
|
|
|
} |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub _valid_utm_zone($) |
91
|
1117
|
|
|
1117
|
|
2686
|
{ my $char = shift; |
92
|
1117
|
|
|
|
|
4781
|
index("CDEFGHJKLMNPQRSTUVWX", $char) >= 0; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# Returns all pre-defined ellipsoid names, sorted alphabetically |
96
|
|
|
|
|
|
|
sub ellipsoid_names() |
97
|
0
|
|
|
0
|
1
|
0
|
{ map { $_->[0] } @Ellipsoid; |
|
0
|
|
|
|
|
0
|
|
98
|
|
|
|
|
|
|
} |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# Returns "official" name, equator radius and square eccentricity |
101
|
|
|
|
|
|
|
# The specified name can be numeric (for compatibility reasons) or |
102
|
|
|
|
|
|
|
# a more-or-less exact name |
103
|
|
|
|
|
|
|
# Examples: my($name, $r, $sqecc) = ellipsoid_info 'wgs84'; |
104
|
|
|
|
|
|
|
# my($name, $r, $sqecc) = ellipsoid_info 'WGS 84'; |
105
|
|
|
|
|
|
|
# my($name, $r, $sqecc) = ellipsoid_info 'WGS-84'; |
106
|
|
|
|
|
|
|
# my($name, $r, $sqecc) = ellipsoid_info 'WGS-84 (new specs)'; |
107
|
|
|
|
|
|
|
# my($name, $r, $sqecc) = ellipsoid_info 22; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub ellipsoid_info($) |
110
|
3077
|
|
|
3077
|
1
|
5182
|
{ my $id = shift; |
111
|
|
|
|
|
|
|
|
112
|
3077
|
100
|
66
|
|
|
19992
|
my $el = $id !~ m/\D/ |
113
|
|
|
|
|
|
|
? $Ellipsoid[$id-1] # old system counted from 1 |
114
|
|
|
|
|
|
|
: $Ellipsoid{$id} || $Ellipsoid{_cleanup_name $id}; |
115
|
|
|
|
|
|
|
|
116
|
3077
|
50
|
|
|
|
18334
|
defined $el ? @$el : (); |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
# Expects Ellipsoid Number or name, Latitude, Longitude |
120
|
|
|
|
|
|
|
# (Latitude and Longitude in decimal degrees) |
121
|
|
|
|
|
|
|
# Returns UTM Zone, UTM Easting, UTM Northing |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub latlon_to_utm($$$) |
124
|
1039
|
|
|
1039
|
1
|
1915784
|
{ my ($ellips, $latitude, $longitude) = @_; |
125
|
|
|
|
|
|
|
|
126
|
1039
|
50
|
33
|
|
|
16523
|
croak "Longitude value ($longitude) invalid." |
127
|
|
|
|
|
|
|
if $longitude < -180 || $longitude > 180; |
128
|
|
|
|
|
|
|
|
129
|
1039
|
|
|
|
|
4238
|
my $long2 = $longitude - int(($longitude + 180)/360) * 360; |
130
|
1039
|
|
|
|
|
3176
|
my $zone = _latlon_zone_number($latitude, $long2); |
131
|
|
|
|
|
|
|
|
132
|
1039
|
|
|
|
|
2785
|
_latlon_to_utm($ellips, $zone, $latitude, $long2); |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub latlon_to_utm_force_zone($$$$) |
136
|
999
|
|
|
999
|
1
|
1033071
|
{ my ($ellips, $zone, $latitude, $longitude) = @_; |
137
|
|
|
|
|
|
|
|
138
|
999
|
50
|
33
|
|
|
17690
|
croak "Longitude value ($longitude) invalid." |
139
|
|
|
|
|
|
|
if $longitude < -180 || $longitude > 180; |
140
|
|
|
|
|
|
|
|
141
|
999
|
|
|
|
|
4163
|
my $long2 = $longitude - int(($longitude + 180)/360) * 360; |
142
|
|
|
|
|
|
|
|
143
|
999
|
|
|
|
|
6464
|
my ($zone_number) = $zone =~ /^(\d+)[CDEFGHJKLMNPQRSTUVWX]?$/i; |
144
|
|
|
|
|
|
|
|
145
|
999
|
50
|
33
|
|
|
8568
|
croak "Zone value ($zone) invalid." |
146
|
|
|
|
|
|
|
unless defined($zone_number) && $zone_number <= 60; |
147
|
|
|
|
|
|
|
|
148
|
999
|
|
|
|
|
16342
|
_latlon_to_utm($ellips, $zone_number, $latitude, $long2); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub _latlon_zone_number |
152
|
1039
|
|
|
1039
|
|
2107
|
{ my ($latitude, $long2) = @_; |
153
|
|
|
|
|
|
|
|
154
|
1039
|
|
|
|
|
2525
|
my $zone = int( ($long2 + 180)/6) + 1; |
155
|
1039
|
100
|
100
|
|
|
5347
|
if($latitude >= 56.0 && $latitude < 64.0 && $long2 >= 3.0 && $long2 < 12.0) |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
156
|
1
|
|
|
|
|
3
|
{ $zone = 32; |
157
|
|
|
|
|
|
|
} |
158
|
1039
|
100
|
66
|
|
|
31771
|
if($latitude >= 72.0 && $latitude < 84.0) { |
159
|
82
|
100
|
100
|
|
|
5657
|
$zone = ($long2 >= 0.0 && $long2 < 9.0) ? 31 |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
|
|
100
|
100
|
|
|
|
|
160
|
|
|
|
|
|
|
: ($long2 >= 9.0 && $long2 < 21.0) ? 33 |
161
|
|
|
|
|
|
|
: ($long2 >= 21.0 && $long2 < 33.0) ? 35 |
162
|
|
|
|
|
|
|
: ($long2 >= 33.0 && $long2 < 42.0) ? 37 |
163
|
|
|
|
|
|
|
: $zone; |
164
|
|
|
|
|
|
|
} |
165
|
1039
|
|
|
|
|
2653
|
return $zone; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub _latlon_to_utm |
169
|
2038
|
|
|
2038
|
|
3999
|
{ my ($ellips, $zone, $latitude, $long2) = @_; |
170
|
|
|
|
|
|
|
|
171
|
2038
|
50
|
|
|
|
4623
|
my ($name, $radius, $eccentricity) = ellipsoid_info $ellips |
172
|
|
|
|
|
|
|
or croak "Ellipsoid value ($ellips) invalid."; |
173
|
|
|
|
|
|
|
|
174
|
2038
|
|
|
|
|
5192
|
my $lat_radian = $deg2rad * $latitude; |
175
|
2038
|
|
|
|
|
4023
|
my $long_radian = $deg2rad * $long2; |
176
|
|
|
|
|
|
|
|
177
|
2038
|
|
|
|
|
2897
|
my $k0 = 0.9996; # scale |
178
|
|
|
|
|
|
|
|
179
|
2038
|
|
|
|
|
4738
|
my $longorigin = ($zone - 1)*6 - 180 + 3; |
180
|
2038
|
|
|
|
|
3190
|
my $longoriginradian = $deg2rad * $longorigin; |
181
|
2038
|
|
|
|
|
3356
|
my $eccentprime = $eccentricity/(1-$eccentricity); |
182
|
|
|
|
|
|
|
|
183
|
2038
|
|
|
|
|
7397
|
my $N = $radius / sqrt(1-$eccentricity * sin($lat_radian)*sin($lat_radian)); |
184
|
2038
|
|
|
|
|
8509
|
my $T = tan($lat_radian) * tan($lat_radian); |
185
|
2038
|
|
|
|
|
94731
|
my $C = $eccentprime * cos($lat_radian)*cos($lat_radian); |
186
|
2038
|
|
|
|
|
4026
|
my $A = cos($lat_radian) * ($long_radian - $longoriginradian); |
187
|
2038
|
|
|
|
|
14580
|
my $M = $radius |
188
|
|
|
|
|
|
|
* ( ( 1 - $eccentricity/4 - 3 * $eccentricity * $eccentricity/64 |
189
|
|
|
|
|
|
|
- 5 * $eccentricity * $eccentricity * $eccentricity/256 |
190
|
|
|
|
|
|
|
) * $lat_radian |
191
|
|
|
|
|
|
|
- ( 3 * $eccentricity/8 + 3 * $eccentricity * $eccentricity/32 |
192
|
|
|
|
|
|
|
+ 45 * $eccentricity * $eccentricity * $eccentricity/1024 |
193
|
|
|
|
|
|
|
) * sin(2 * $lat_radian) |
194
|
|
|
|
|
|
|
+ ( 15 * $eccentricity * $eccentricity/256 + |
195
|
|
|
|
|
|
|
45 * $eccentricity * $eccentricity * $eccentricity/1024 |
196
|
|
|
|
|
|
|
) * sin(4 * $lat_radian) |
197
|
|
|
|
|
|
|
- ( 35 * $eccentricity * $eccentricity * $eccentricity/3072 |
198
|
|
|
|
|
|
|
) * sin(6 * $lat_radian) |
199
|
|
|
|
|
|
|
); |
200
|
|
|
|
|
|
|
|
201
|
2038
|
|
|
|
|
9969
|
my $utm_easting = $k0*$N*($A+(1-$T+$C)*$A*$A*$A/6 |
202
|
|
|
|
|
|
|
+ (5-18*$T+$T*$T+72*$C-58*$eccentprime)*$A*$A*$A*$A*$A/120) |
203
|
|
|
|
|
|
|
+ 500000.0; |
204
|
|
|
|
|
|
|
|
205
|
2038
|
|
|
|
|
6736
|
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)); |
206
|
|
|
|
|
|
|
|
207
|
2038
|
100
|
|
|
|
39626
|
$utm_northing += 10000000.0 if $latitude < 0; |
208
|
|
|
|
|
|
|
|
209
|
2038
|
50
|
66
|
|
|
75546
|
my $utm_letter |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
66
|
|
|
|
|
|
|
100
|
33
|
|
|
|
|
210
|
|
|
|
|
|
|
= ( 84 >= $latitude && $latitude >= 72) ? 'X' |
211
|
|
|
|
|
|
|
: ( 72 > $latitude && $latitude >= 64) ? 'W' |
212
|
|
|
|
|
|
|
: ( 64 > $latitude && $latitude >= 56) ? 'V' |
213
|
|
|
|
|
|
|
: ( 56 > $latitude && $latitude >= 48) ? 'U' |
214
|
|
|
|
|
|
|
: ( 48 > $latitude && $latitude >= 40) ? 'T' |
215
|
|
|
|
|
|
|
: ( 40 > $latitude && $latitude >= 32) ? 'S' |
216
|
|
|
|
|
|
|
: ( 32 > $latitude && $latitude >= 24) ? 'R' |
217
|
|
|
|
|
|
|
: ( 24 > $latitude && $latitude >= 16) ? 'Q' |
218
|
|
|
|
|
|
|
: ( 16 > $latitude && $latitude >= 8) ? 'P' |
219
|
|
|
|
|
|
|
: ( 8 > $latitude && $latitude >= 0) ? 'N' |
220
|
|
|
|
|
|
|
: ( 0 > $latitude && $latitude >= -8) ? 'M' |
221
|
|
|
|
|
|
|
: ( -8 > $latitude && $latitude >= -16) ? 'L' |
222
|
|
|
|
|
|
|
: (-16 > $latitude && $latitude >= -24) ? 'K' |
223
|
|
|
|
|
|
|
: (-24 > $latitude && $latitude >= -32) ? 'J' |
224
|
|
|
|
|
|
|
: (-32 > $latitude && $latitude >= -40) ? 'H' |
225
|
|
|
|
|
|
|
: (-40 > $latitude && $latitude >= -48) ? 'G' |
226
|
|
|
|
|
|
|
: (-48 > $latitude && $latitude >= -56) ? 'F' |
227
|
|
|
|
|
|
|
: (-56 > $latitude && $latitude >= -64) ? 'E' |
228
|
|
|
|
|
|
|
: (-64 > $latitude && $latitude >= -72) ? 'D' |
229
|
|
|
|
|
|
|
: (-72 > $latitude && $latitude >= -80) ? 'C' |
230
|
|
|
|
|
|
|
: croak "Latitude ($latitude) out of UTM range."; |
231
|
|
|
|
|
|
|
|
232
|
2038
|
|
|
|
|
4265
|
$zone .= $utm_letter; |
233
|
|
|
|
|
|
|
|
234
|
2038
|
|
|
|
|
14167
|
($zone, $utm_easting, $utm_northing); |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# Expects Ellipsoid Number or name, UTM zone, UTM Easting, UTM Northing |
238
|
|
|
|
|
|
|
# Returns Latitude, Longitude |
239
|
|
|
|
|
|
|
# (Latitude and Longitude in decimal degrees, UTM Zone e.g. 23S) |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub utm_to_latlon($$$$) |
242
|
1039
|
|
|
1039
|
1
|
1797061
|
{ my ($ellips, $zone, $easting, $northing) = @_; |
243
|
|
|
|
|
|
|
|
244
|
1039
|
50
|
|
|
|
2878
|
my ($name, $radius, $eccentricity) = ellipsoid_info $ellips |
245
|
|
|
|
|
|
|
or croak "Ellipsoid value ($ellips) invalid."; |
246
|
|
|
|
|
|
|
|
247
|
1039
|
|
|
|
|
2532
|
my $zone_number = $zone; |
248
|
1039
|
|
|
|
|
2064
|
my $zone_letter = chop $zone_number; |
249
|
|
|
|
|
|
|
|
250
|
1039
|
50
|
|
|
|
3659
|
croak "UTM zone ($zone_letter) invalid." |
251
|
|
|
|
|
|
|
unless _valid_utm_zone $zone_letter; |
252
|
|
|
|
|
|
|
|
253
|
1039
|
|
|
|
|
1902
|
my $k0 = 0.9996; |
254
|
1039
|
|
|
|
|
1971
|
my $x = $easting - 500000; # Remove Longitude offset |
255
|
1039
|
|
|
|
|
4065
|
my $y = $northing; |
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
# Set hemisphere (1=Northern, 0=Southern) |
258
|
1039
|
|
|
|
|
2272
|
my $hemisphere = $zone_letter ge 'N'; |
259
|
1039
|
100
|
|
|
|
2557
|
$y -= 10000000.0 unless $hemisphere; # Remove Southern Offset |
260
|
|
|
|
|
|
|
|
261
|
1039
|
|
|
|
|
3270
|
my $longorigin = ($zone_number - 1)*6 - 180 + 3; |
262
|
1039
|
|
|
|
|
2097
|
my $eccPrimeSquared = ($eccentricity)/(1-$eccentricity); |
263
|
1039
|
|
|
|
|
1592
|
my $M = $y/$k0; |
264
|
1039
|
|
|
|
|
3470
|
my $mu = $M/($radius*(1-$eccentricity/4-3*$eccentricity*$eccentricity/64-5*$eccentricity*$eccentricity*$eccentricity/256)); |
265
|
|
|
|
|
|
|
|
266
|
1039
|
|
|
|
|
3117
|
my $e1 = (1-sqrt(1-$eccentricity))/(1+sqrt(1-$eccentricity)); |
267
|
1039
|
|
|
|
|
9301
|
my $phi1rad = $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); |
268
|
1039
|
|
|
|
|
1553
|
my $phi1 = $phi1rad*$rad2deg; |
269
|
1039
|
|
|
|
|
2726
|
my $N1 = $radius/sqrt(1-$eccentricity*sin($phi1rad)*sin($phi1rad)); |
270
|
1039
|
|
|
|
|
3515
|
my $T1 = tan($phi1rad)*tan($phi1rad); |
271
|
1039
|
|
|
|
|
29814
|
my $C1 = $eccentricity*cos($phi1rad)*cos($phi1rad); |
272
|
1039
|
|
|
|
|
18561
|
my $R1 = $radius * (1-$eccentricity) |
273
|
|
|
|
|
|
|
/ ((1-$eccentricity*sin($phi1rad)*sin($phi1rad))**1.5); |
274
|
1039
|
|
|
|
|
1834
|
my $D = $x/($N1*$k0); |
275
|
|
|
|
|
|
|
|
276
|
1039
|
|
|
|
|
3732
|
my $Latitude = $phi1rad-($N1*tan($phi1rad)/$R1)*($D*$D/2-(5+3*$T1+10*$C1-4*$C1*$C1-9*$eccPrimeSquared)*$D*$D*$D*$D/24+(61+90*$T1+298*$C1+45*$T1*$T1-252*$eccPrimeSquared-3*$C1*$C1)*$D*$D*$D*$D*$D*$D/720); |
277
|
1039
|
|
|
|
|
22598
|
$Latitude = $Latitude * $rad2deg; |
278
|
|
|
|
|
|
|
|
279
|
1039
|
|
|
|
|
5872
|
my $Longitude = ($D-(1+2*$T1+$C1)*$D*$D*$D/6+(5-2*$C1+28*$T1-3*$C1*$C1+8*$eccPrimeSquared+24*$T1*$T1)*$D*$D*$D*$D*$D/120)/cos($phi1rad); |
280
|
1039
|
|
|
|
|
1574
|
$Longitude = $longorigin + $Longitude * $rad2deg; |
281
|
|
|
|
|
|
|
|
282
|
1039
|
|
|
|
|
5351
|
($Latitude, $Longitude); |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
sub utm_to_mgrs($$$) |
286
|
39
|
|
|
39
|
1
|
49
|
{ my ($zone,$easting,$northing) = @_; |
287
|
39
|
|
|
|
|
43
|
my $zone_number = $zone; |
288
|
39
|
|
|
|
|
52
|
my $zone_letter = chop $zone_number; |
289
|
|
|
|
|
|
|
|
290
|
39
|
50
|
|
|
|
64
|
croak "UTM zone ($zone_letter) invalid." |
291
|
|
|
|
|
|
|
unless _valid_utm_zone $zone_letter; |
292
|
|
|
|
|
|
|
|
293
|
39
|
|
|
|
|
84
|
my $northing_zones="ABCDEFGHJKLMNPQRSTUV"; |
294
|
39
|
|
|
|
|
104
|
my $rnd_north=sprintf("%d",$northing); |
295
|
39
|
|
|
|
|
53
|
my $north_split=length($rnd_north)-5; |
296
|
39
|
50
|
|
|
|
71
|
$north_split=0 if $north_split < 0; |
297
|
39
|
|
|
|
|
65
|
my $mgrs_north=substr($rnd_north,(length($rnd_north)-5)); |
298
|
39
|
|
|
|
|
182
|
$rnd_north -=2000000 while ($rnd_north >= 2000000); |
299
|
39
|
50
|
|
|
|
70
|
$rnd_north+=2000000 if $rnd_north < 0; |
300
|
39
|
|
|
|
|
53
|
my $num_north=int($rnd_north/100000); |
301
|
39
|
100
|
|
|
|
87
|
$num_north+=5 if not ($zone_number % 2); |
302
|
39
|
|
|
|
|
150
|
$num_north-=20 until $num_north <20; |
303
|
39
|
|
|
|
|
55
|
my $lett_north=substr($northing_zones,$num_north,1); |
304
|
|
|
|
|
|
|
|
305
|
39
|
|
|
|
|
73
|
my $rnd_east=sprintf("%d",$easting); |
306
|
39
|
|
|
|
|
47
|
my $east_split=length($rnd_east)-5; |
307
|
39
|
50
|
|
|
|
67
|
$east_split=0 if $east_split < 0; |
308
|
39
|
|
|
|
|
54
|
my $mgrs_east=substr($rnd_east,(length($rnd_east)-5)); |
309
|
39
|
|
|
|
|
90
|
my $num_east=substr($rnd_east,0,(length($rnd_east)-5)); |
310
|
39
|
50
|
|
|
|
61
|
$num_east=0 if not $num_east; |
311
|
39
|
|
|
|
|
42
|
my $mgrs_zone=$zone_number; |
312
|
39
|
|
|
|
|
338
|
$mgrs_zone-=3 until $mgrs_zone < 4; |
313
|
|
|
|
|
|
|
# zones are 6deg wide, mgrs letters are 18deg = 8 per zone |
314
|
|
|
|
|
|
|
# calculate which zone required |
315
|
39
|
50
|
|
|
|
92
|
my $easting_zones |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
316
|
|
|
|
|
|
|
= ( $mgrs_zone == 1) ? 'ABCDEFGH' |
317
|
|
|
|
|
|
|
: ( $mgrs_zone == 2) ? 'JKLMNPQR' |
318
|
|
|
|
|
|
|
: ( $mgrs_zone == 3) ? 'STUVWXYZ' |
319
|
|
|
|
|
|
|
: croak "Could not calculate MGRS zone."; |
320
|
39
|
|
|
|
|
48
|
$num_east--; |
321
|
39
|
50
|
|
|
|
92
|
my $lett_east=substr($easting_zones,$num_east,1) or croak "Could not detect Easting Zone for MGRS coordinate"; |
322
|
|
|
|
|
|
|
|
323
|
39
|
|
|
|
|
67
|
my $MGRS="$zone$lett_east$lett_north$mgrs_east$mgrs_north"; |
324
|
39
|
|
|
|
|
158
|
($MGRS); |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub latlon_to_mgrs($$$) |
328
|
39
|
|
|
39
|
1
|
22505
|
{ my ($ellips, $latitude, $longitude) = @_; |
329
|
39
|
|
|
|
|
79
|
my ($zone,$x_coord,$y_coord)=latlon_to_utm($ellips, $latitude, $longitude); |
330
|
39
|
|
|
|
|
78
|
my $mgrs_string=utm_to_mgrs($zone,$x_coord,$y_coord); |
331
|
39
|
|
|
|
|
93
|
($mgrs_string); |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
sub mgrs_to_utm($) |
336
|
39
|
|
|
39
|
1
|
50
|
{ my ($mgrs_string) = @_; |
337
|
39
|
|
|
|
|
66
|
my $zone = substr($mgrs_string,0,2); |
338
|
39
|
100
|
|
|
|
208
|
$mgrs_string = "0$mgrs_string" if !($zone =~ /^\d+$/); |
339
|
39
|
|
|
|
|
76
|
$zone = substr($mgrs_string,0,3); |
340
|
39
|
|
|
|
|
44
|
my $zone_number = $zone; |
341
|
39
|
|
|
|
|
62
|
my $zone_letter = chop $zone_number; |
342
|
|
|
|
|
|
|
|
343
|
39
|
50
|
|
|
|
59
|
croak "UTM zone ($zone_letter) invalid." |
344
|
|
|
|
|
|
|
unless _valid_utm_zone $zone_letter; |
345
|
|
|
|
|
|
|
|
346
|
39
|
|
|
|
|
70
|
my $first_letter = substr($mgrs_string,3,1); |
347
|
39
|
50
|
|
|
|
122
|
croak "MGRS zone ($first_letter) invalid." |
348
|
|
|
|
|
|
|
unless $first_letter =~ /[ABCDEFGHJKLMNPQRSTUVWXYZ]/; |
349
|
|
|
|
|
|
|
|
350
|
39
|
|
|
|
|
53
|
my $second_letter = substr($mgrs_string,4,1); |
351
|
39
|
50
|
|
|
|
97
|
croak "MGRS zone ($second_letter) invalid." |
352
|
|
|
|
|
|
|
unless $second_letter =~ /[ABCDEFGHJKLMNPQRSTUV]/; |
353
|
|
|
|
|
|
|
|
354
|
39
|
|
|
|
|
60
|
my $coords=substr($mgrs_string,5); |
355
|
39
|
|
|
|
|
52
|
my $coord_len=length($coords); |
356
|
39
|
50
|
33
|
|
|
362
|
croak "MGRS coords ($coords) invalid." |
|
|
|
33
|
|
|
|
|
357
|
|
|
|
|
|
|
unless ((($coord_len > 0) and ($coord_len <= 10)) and !($coord_len % 2)); |
358
|
|
|
|
|
|
|
|
359
|
39
|
|
|
|
|
57
|
$coord_len=int($coord_len/2); |
360
|
39
|
|
|
|
|
53
|
my $x_coord=substr($coords,0,$coord_len); |
361
|
39
|
|
|
|
|
46
|
my $y_coord=substr($coords,$coord_len); |
362
|
39
|
|
|
|
|
75
|
$x_coord*=10 ** (5 - $coord_len); |
363
|
39
|
|
|
|
|
48
|
$y_coord*=10 ** (5 - $coord_len); |
364
|
|
|
|
|
|
|
|
365
|
39
|
50
|
|
|
|
241
|
my $east_pos |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
366
|
|
|
|
|
|
|
= ( $first_letter =~ /[ABCDEFGH]/) ? index('ABCDEFGH',$first_letter) |
367
|
|
|
|
|
|
|
: ( $first_letter =~ /[JKLMNPQR]/) ? index('JKLMNPQR',$first_letter) |
368
|
|
|
|
|
|
|
: ( $first_letter =~ /[STUVWXYZ]/) ? index('STUVWXYZ',$first_letter) |
369
|
|
|
|
|
|
|
: croak "Could not calculate MGRS Easting zone."; |
370
|
39
|
50
|
|
|
|
84
|
croak "MGRS Letter $first_letter invalid." if $east_pos < 0; |
371
|
39
|
|
|
|
|
35
|
$east_pos++; |
372
|
39
|
|
|
|
|
39
|
$east_pos*=100000; |
373
|
39
|
|
|
|
|
44
|
$x_coord+=$east_pos; |
374
|
|
|
|
|
|
|
|
375
|
39
|
|
|
|
|
40
|
my $northing_zones="ABCDEFGHJKLMNPQRSTUV"; |
376
|
39
|
|
|
|
|
51
|
my $north_pos=index($northing_zones,$second_letter); |
377
|
39
|
50
|
|
|
|
67
|
croak "MGRS Letter $second_letter invalid." if $north_pos < 0; |
378
|
39
|
|
|
|
|
42
|
$north_pos++; |
379
|
39
|
100
|
|
|
|
87
|
$north_pos-=5 if not ($zone_number % 2); |
380
|
39
|
|
|
|
|
81
|
$north_pos+=20 until $north_pos > 0; |
381
|
39
|
100
|
|
|
|
83
|
if ($zone_letter =~ /[NPQRSTUVWX]/) { |
382
|
|
|
|
|
|
|
# Northern hemisphere |
383
|
21
|
|
|
|
|
34
|
my $tmpNorth=index('NPQRSTUVWX',$zone_letter); |
384
|
21
|
|
|
|
|
21
|
$tmpNorth++; |
385
|
21
|
|
|
|
|
91
|
$tmpNorth*=8; |
386
|
21
|
|
|
|
|
23
|
$tmpNorth*=10/9; |
387
|
21
|
|
|
|
|
46
|
$tmpNorth=int((($tmpNorth-$north_pos)/20)+0.5)*20; |
388
|
21
|
|
|
|
|
22
|
$north_pos+=$tmpNorth; |
389
|
21
|
|
|
|
|
22
|
$north_pos*=100000; |
390
|
21
|
|
|
|
|
18
|
$north_pos-=100000; |
391
|
21
|
|
|
|
|
28
|
$y_coord+=$north_pos; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
else { |
394
|
|
|
|
|
|
|
#Southern Hemisphere |
395
|
18
|
|
|
|
|
27
|
my $tmpNorth=index('CDEFGHJKLM',$zone_letter); |
396
|
18
|
|
|
|
|
19
|
$tmpNorth*=8; |
397
|
18
|
|
|
|
|
18
|
$tmpNorth*=10/9; |
398
|
18
|
|
|
|
|
36
|
$tmpNorth=int((($tmpNorth-$north_pos)/20)+0.5)*20; |
399
|
18
|
|
|
|
|
19
|
$north_pos+=$tmpNorth; |
400
|
18
|
|
|
|
|
25
|
$north_pos*=100000; |
401
|
18
|
|
|
|
|
19
|
$north_pos-=100000; |
402
|
18
|
100
|
|
|
|
42
|
$north_pos+=2000000 if $zone_letter ne "C"; |
403
|
18
|
|
|
|
|
21
|
$y_coord+=$north_pos; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
39
|
|
|
|
|
133
|
($zone,$x_coord,$y_coord); |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
sub mgrs_to_latlon($$) |
410
|
39
|
|
|
39
|
1
|
23834
|
{ my ($ellips, $mgrs_string) = @_; |
411
|
39
|
|
|
|
|
70
|
my ($zone,$x_coord,$y_coord)=mgrs_to_utm($mgrs_string); |
412
|
39
|
|
|
|
|
84
|
my ($latitude,$longitude)=utm_to_latlon($ellips,$zone,$x_coord,$y_coord); |
413
|
39
|
|
|
|
|
155
|
($latitude,$longitude); |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
1; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
__END__ |