File Coverage

blib/lib/Geo/Forward.pm
Criterion Covered Total %
statement 80 89 89.8
branch 3 4 75.0
condition 1 2 50.0
subroutine 10 11 90.9
pod 4 4 100.0
total 98 110 89.0


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;