line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Geo::Forward; |
2
|
3
|
|
|
3
|
|
207352
|
use strict; |
|
3
|
|
|
|
|
25
|
|
|
3
|
|
|
|
|
87
|
|
3
|
3
|
|
|
3
|
|
16
|
use warnings; |
|
3
|
|
|
|
|
4
|
|
|
3
|
|
|
|
|
83
|
|
4
|
3
|
|
|
3
|
|
14
|
use base qw{Package::New}; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
1389
|
|
5
|
3
|
|
|
3
|
|
1538
|
use Geo::Constants 0.04 qw{PI}; |
|
3
|
|
|
|
|
843
|
|
|
3
|
|
|
|
|
203
|
|
6
|
3
|
|
|
3
|
|
835
|
use Geo::Functions 0.03 qw{deg_rad rad_deg}; |
|
3
|
|
|
|
|
1908
|
|
|
3
|
|
|
|
|
175
|
|
7
|
3
|
|
|
3
|
|
1349
|
use Geo::Ellipsoids 0.09 qw{}; |
|
3
|
|
|
|
|
7148
|
|
|
3
|
|
|
|
|
2160
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our $VERSION = '0.16'; |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
=head1 NAME |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
Geo::Forward - Calculate geographic location from latitude, longitude, distance, and heading. |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 SYNOPSIS |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
use Geo::Forward; |
18
|
|
|
|
|
|
|
my $gf = Geo::Forward->new(); # default "WGS84" |
19
|
|
|
|
|
|
|
my ($lat1, $lon1, $faz, $dist) = (38.871022, -77.055874, 62.888507083, 4565.6854); |
20
|
|
|
|
|
|
|
my ($lat2, $lon2, $baz) = $gf->forward($lat1, $lon1, $faz, $dist); |
21
|
|
|
|
|
|
|
print "Input Lat: $lat1 Lon: $lon1\n"; |
22
|
|
|
|
|
|
|
print "Input Forward Azimuth: $faz (degrees)\n"; |
23
|
|
|
|
|
|
|
print "Input Distance: $dist (meters)\n"; |
24
|
|
|
|
|
|
|
print "Output Lat: $lat2 Lon: $lon2\n"; |
25
|
|
|
|
|
|
|
print "Output Back Azimuth: $baz (degreees)\n"; |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=head1 DESCRIPTION |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
This module is a pure Perl port of the NGS program in the public domain "forward" by Robert (Sid) Safford and Stephen J. Frakes. |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head2 new |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
The new() constructor may be called with any parameter that is appropriate to the ellipsoid method which establishes the ellipsoid. |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
my $gf = Geo::Forward->new(); # default "WGS84" |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=head1 METHODS |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
=head2 initialize |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
=cut |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub initialize { |
46
|
3
|
|
|
3
|
1
|
304
|
my $self = shift; |
47
|
3
|
|
50
|
|
|
21
|
my $param = shift || undef; |
48
|
3
|
|
|
|
|
13
|
$self->ellipsoid($param); |
49
|
|
|
|
|
|
|
} |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 ellipsoid |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Method to set or retrieve the current ellipsoid object. The ellipsoid is a L object. |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
my $ellipsoid = $gf->ellipsoid; #Default is WGS84 |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
$gf->ellipsoid('Clarke 1866'); #Built in ellipsoids from Geo::Ellipsoids |
58
|
|
|
|
|
|
|
$gf->ellipsoid({a=>1}); #Custom Sphere 1 unit radius |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=cut |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
sub ellipsoid { |
63
|
1012
|
|
|
1012
|
1
|
2586
|
my $self = shift; |
64
|
1012
|
100
|
|
|
|
2325
|
if (@_) { |
65
|
3
|
|
|
|
|
6
|
my $param = shift; |
66
|
3
|
|
|
|
|
20
|
$self->{'ellipsoid'} = Geo::Ellipsoids->new($param); |
67
|
|
|
|
|
|
|
} |
68
|
1012
|
|
|
|
|
2950
|
return $self->{'ellipsoid'}; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head2 forward |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
This method is the user frontend to the mathematics. This interface will not change in future versions. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
my ($lat2, $lon2, $baz) = $gf->forward($lat1, $lon1, $faz, $dist); |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Note: Latitude and longitude units are signed decimal degrees. The distance units are based on the ellipsoid semi-major axis which is meters for WGS-84. The forward and backward azimuths units are signed degrees clockwise from North. |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=cut |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub forward { |
82
|
1007
|
|
|
1007
|
1
|
569659
|
my $self = shift; |
83
|
1007
|
|
|
|
|
1550
|
my $lat = shift; #degrees |
84
|
1007
|
|
|
|
|
1470
|
my $lon = shift; #degrees |
85
|
1007
|
|
|
|
|
1349
|
my $heading = shift; #degrees |
86
|
1007
|
|
|
|
|
1399
|
my $distance = shift; #meters (or the units of the semi-major axis) |
87
|
1007
|
|
|
|
|
2169
|
my ($lat2, $lon2, $baz) = $self->_dirct1(rad_deg($lat),rad_deg($lon),rad_deg($heading),$distance); |
88
|
1007
|
|
|
|
|
2169
|
return(deg_rad($lat2), deg_rad($lon2), deg_rad($baz)); |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub _dirct1 { |
92
|
1007
|
|
|
1007
|
|
21928
|
my $self = shift; #provides A and F |
93
|
1007
|
|
|
|
|
1281
|
my $GLAT1 = shift; #radians |
94
|
1007
|
|
|
|
|
1255
|
my $GLON1 = shift; #radians |
95
|
1007
|
|
|
|
|
1288
|
my $FAZ = shift; #radians |
96
|
1007
|
|
|
|
|
1297
|
my $S = shift; #units of semi-major axis (default meters) |
97
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
# SUBROUTINE DIRCT1(GLAT1,GLON1,GLAT2,GLON2,FAZ,BAZ,S) |
99
|
|
|
|
|
|
|
#C |
100
|
|
|
|
|
|
|
#C *** SOLUTION OF THE GEODETIC DIRECT PROBLEM AFTER T.VINCENTY |
101
|
|
|
|
|
|
|
#C *** MODIFIED RAINSFORD'S METHOD WITH HELMERT'S ELLIPTICAL TERMS |
102
|
|
|
|
|
|
|
#C *** EFFECTIVE IN ANY AZIMUTH AND AT ANY DISTANCE SHORT OF ANTIPODAL |
103
|
|
|
|
|
|
|
#C |
104
|
|
|
|
|
|
|
#C *** A IS THE SEMI-MAJOR AXIS OF THE REFERENCE ELLIPSOID |
105
|
|
|
|
|
|
|
#C *** F IS THE FLATTENING OF THE REFERENCE ELLIPSOID |
106
|
|
|
|
|
|
|
#C *** LATITUDES AND LONGITUDES IN RADIANS POSITIVE NORTH AND EAST |
107
|
|
|
|
|
|
|
#C *** AZIMUTHS IN RADIANS CLOCKWISE FROM NORTH |
108
|
|
|
|
|
|
|
#C *** GEODESIC DISTANCE S ASSUMED IN UNITS OF SEMI-MAJOR AXIS A |
109
|
|
|
|
|
|
|
#C |
110
|
|
|
|
|
|
|
#C *** PROGRAMMED FOR CDC-6600 BY LCDR L.PFEIFER NGS ROCKVILLE MD 20FEB75 |
111
|
|
|
|
|
|
|
#C *** MODIFIED FOR SYSTEM 360 BY JOHN G GERGEN NGS ROCKVILLE MD 750608 |
112
|
|
|
|
|
|
|
#C |
113
|
|
|
|
|
|
|
# IMPLICIT REAL*8 (A-H,O-Z) |
114
|
|
|
|
|
|
|
# COMMON/CONST/PI,RAD |
115
|
|
|
|
|
|
|
# COMMON/ELIPSOID/A,F |
116
|
1007
|
|
|
|
|
1820
|
my $ellipsoid=$self->ellipsoid; |
117
|
1007
|
|
|
|
|
2361
|
my $A=$ellipsoid->a; |
118
|
1007
|
|
|
|
|
5054
|
my $F=$ellipsoid->f; |
119
|
|
|
|
|
|
|
# DATA EPS/0.5D-13/ |
120
|
1007
|
|
|
|
|
6586
|
my $EPS=0.5E-13; |
121
|
|
|
|
|
|
|
# R=1.-F |
122
|
1007
|
|
|
|
|
1606
|
my $R=1.-$F; |
123
|
|
|
|
|
|
|
# TU=R*DSIN(GLAT1)/DCOS(GLAT1) |
124
|
1007
|
|
|
|
|
2345
|
my $TU=$R*sin($GLAT1)/cos($GLAT1); |
125
|
|
|
|
|
|
|
# SF=DSIN(FAZ) |
126
|
1007
|
|
|
|
|
1579
|
my $SF=sin($FAZ); |
127
|
|
|
|
|
|
|
# CF=DCOS(FAZ) |
128
|
1007
|
|
|
|
|
1542
|
my $CF=cos($FAZ); |
129
|
|
|
|
|
|
|
# BAZ=0. |
130
|
1007
|
|
|
|
|
1220
|
my $BAZ=0.; |
131
|
|
|
|
|
|
|
# IF(CF.NE.0.) BAZ=DATAN2(TU,CF)*2. |
132
|
1007
|
50
|
|
|
|
3015
|
$BAZ=atan2($TU,$CF)*2. if ($CF != 0); |
133
|
|
|
|
|
|
|
# CU=1./DSQRT(TU*TU+1.) |
134
|
1007
|
|
|
|
|
1749
|
my $CU=1./sqrt($TU*$TU+1.); |
135
|
|
|
|
|
|
|
# SU=TU*CU |
136
|
1007
|
|
|
|
|
1343
|
my $SU=$TU*$CU; |
137
|
|
|
|
|
|
|
# SA=CU*SF |
138
|
1007
|
|
|
|
|
1319
|
my $SA=$CU*$SF; |
139
|
|
|
|
|
|
|
# C2A=-SA*SA+1. |
140
|
1007
|
|
|
|
|
1601
|
my $C2A=-$SA*$SA+1.; |
141
|
|
|
|
|
|
|
# X=DSQRT((1./R/R-1.)*C2A+1.)+1. |
142
|
1007
|
|
|
|
|
1673
|
my $X=sqrt((1./$R/$R-1.)*$C2A+1.)+1.; |
143
|
|
|
|
|
|
|
# X=(X-2.)/X |
144
|
1007
|
|
|
|
|
1403
|
$X=($X-2.)/$X; |
145
|
|
|
|
|
|
|
# C=1.-X |
146
|
1007
|
|
|
|
|
1296
|
my $C=1.-$X; |
147
|
|
|
|
|
|
|
# C=(X*X/4.+1)/C |
148
|
1007
|
|
|
|
|
1608
|
$C=($X*$X/4.+1)/$C; |
149
|
|
|
|
|
|
|
# D=(0.375D0*X*X-1.)*X |
150
|
1007
|
|
|
|
|
1633
|
my $D=(0.375*$X*$X-1.)*$X; |
151
|
|
|
|
|
|
|
# TU=S/R/A/C |
152
|
1007
|
|
|
|
|
1549
|
$TU=$S/$R/$A/$C; |
153
|
|
|
|
|
|
|
# Y=TU |
154
|
1007
|
|
|
|
|
1274
|
my $Y=$TU; |
155
|
|
|
|
|
|
|
# 100 SY=DSIN(Y) |
156
|
1007
|
|
|
|
|
1389
|
my ($SY, $CY, $CZ, $E); |
157
|
1007
|
|
|
|
|
1353
|
do{ $SY=sin($Y); |
|
1020
|
|
|
|
|
1391
|
|
158
|
|
|
|
|
|
|
# CY=DCOS(Y) |
159
|
1020
|
|
|
|
|
1269
|
$CY=cos($Y); |
160
|
|
|
|
|
|
|
# CZ=DCOS(BAZ+Y) |
161
|
1020
|
|
|
|
|
1460
|
$CZ=cos($BAZ+$Y); |
162
|
|
|
|
|
|
|
# E=CZ*CZ*2.-1. |
163
|
1020
|
|
|
|
|
1422
|
$E=$CZ*$CZ*2.-1.; |
164
|
|
|
|
|
|
|
# C=Y |
165
|
1020
|
|
|
|
|
1312
|
$C=$Y; |
166
|
|
|
|
|
|
|
# X=E*CY |
167
|
1020
|
|
|
|
|
1250
|
$X=$E*$CY; |
168
|
|
|
|
|
|
|
# Y=E+E-1. |
169
|
1020
|
|
|
|
|
1325
|
$Y=$E+$E-1.; |
170
|
|
|
|
|
|
|
# Y=(((SY*SY*4.-3.)*Y*CZ*D/6.+X)*D/4.-CZ)*SY*D+TU |
171
|
1020
|
|
|
|
|
3541
|
$Y=((($SY*$SY*4.-3.)*$Y*$CZ*$D/6.+$X)*$D/4.-$CZ)*$SY*$D+$TU; |
172
|
|
|
|
|
|
|
# IF(DABS(Y-C).GT.EPS)GO TO 100 |
173
|
|
|
|
|
|
|
} while (abs($Y-$C) > $EPS); |
174
|
|
|
|
|
|
|
# BAZ=CU*CY*CF-SU*SY |
175
|
1007
|
|
|
|
|
1543
|
$BAZ=$CU*$CY*$CF-$SU*$SY; |
176
|
|
|
|
|
|
|
# C=R*DSQRT(SA*SA+BAZ*BAZ) |
177
|
1007
|
|
|
|
|
1498
|
$C=$R*sqrt($SA*$SA+$BAZ*$BAZ); |
178
|
|
|
|
|
|
|
# D=SU*CY+CU*SY*CF |
179
|
1007
|
|
|
|
|
1357
|
$D=$SU*$CY+$CU*$SY*$CF; |
180
|
|
|
|
|
|
|
# GLAT2=DATAN2(D,C) |
181
|
1007
|
|
|
|
|
1580
|
my $GLAT2=atan2($D,$C); |
182
|
|
|
|
|
|
|
# C=CU*CY-SU*SY*CF |
183
|
1007
|
|
|
|
|
1387
|
$C=$CU*$CY-$SU*$SY*$CF; |
184
|
|
|
|
|
|
|
# X=DATAN2(SY*SF,C) |
185
|
1007
|
|
|
|
|
1342
|
$X=atan2($SY*$SF,$C); |
186
|
|
|
|
|
|
|
# C=((-3.*C2A+4.)*F+4.)*C2A*F/16. |
187
|
1007
|
|
|
|
|
1615
|
$C=((-3.*$C2A+4.)*$F+4.)*$C2A*$F/16.; |
188
|
|
|
|
|
|
|
# D=((E*CY*C+CZ)*SY*C+Y)*SA |
189
|
1007
|
|
|
|
|
1713
|
$D=(($E*$CY*$C+$CZ)*$SY*$C+$Y)*$SA; |
190
|
|
|
|
|
|
|
# GLON2=GLON1+X-(1.-C)*D*F |
191
|
1007
|
|
|
|
|
1540
|
my $GLON2=$GLON1+$X-(1.-$C)*$D*$F; |
192
|
|
|
|
|
|
|
# BAZ=DATAN2(SA,BAZ)+PI |
193
|
1007
|
|
|
|
|
2171
|
$BAZ=atan2($SA,$BAZ)+PI; |
194
|
|
|
|
|
|
|
# RETURN |
195
|
1007
|
|
|
|
|
4082
|
return $GLAT2, $GLON2, $BAZ; |
196
|
|
|
|
|
|
|
# END |
197
|
|
|
|
|
|
|
} |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=head2 bbox |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
Returns a hash reference for the bounding box around a point with the given radius. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
my $bbox = $gf->bbox($lat, $lon, $radius); #isa HASH {north=>$north, east=>$east, south=>$south, west=>$west} |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
Note: This is not an optimised solution input is welcome |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
UOM: radius units of semi-major axis (default meters for WGS-84) |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=cut |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
sub bbox { |
212
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
213
|
0
|
|
|
|
|
|
my $lat = shift; |
214
|
0
|
|
|
|
|
|
my $lon = shift; |
215
|
0
|
|
|
|
|
|
my $dist = shift; |
216
|
0
|
|
|
|
|
|
my ($north, undef, undef) = $self->forward($lat, $lon, 0, $dist); |
217
|
0
|
|
|
|
|
|
my ( undef, $east, undef) = $self->forward($lat, $lon, 90, $dist); |
218
|
0
|
|
|
|
|
|
my ($south, undef, undef) = $self->forward($lat, $lon, 180, $dist); |
219
|
0
|
|
|
|
|
|
my ( undef, $west, undef) = $self->forward($lat, $lon, 270, $dist); |
220
|
0
|
|
|
|
|
|
return {north=>$north, east=>$east, south=>$south, west=>$west}; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=head1 BUGS |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
Please open an issue on GitHub |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
=head1 LIMITS |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
No guarantees that Perl handles all of the double precision calculations in the same manner as Fortran. |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=head1 LICENSE |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
MIT License |
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
Copyright (c) 2022 Michael R. Davis |
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=head1 SEE ALSO |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head3 Similar Packages |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
L, L, L |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=head2 Opposite Package |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
L |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=head2 Building Blocks |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
L, L, L |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=cut |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
1; |