File Coverage

blib/lib/Geo/Forward.pm
Criterion Covered Total %
statement 80 80 100.0
branch 3 4 75.0
condition 1 2 50.0
subroutine 10 10 100.0
pod 3 3 100.0
total 97 99 97.9


line stmt bran cond sub pod time code
1             package Geo::Forward;
2 3     3   436656 use strict;
  3         9  
  3         143  
3 3     3   20 use warnings;
  3         6  
  3         136  
4 3     3   26 use base qw{Package::New};
  3         11  
  3         3647  
5 3     3   3163 use Geo::Constants 0.04 qw{PI};
  3         836  
  3         209  
6 3     3   1490 use Geo::Functions 0.03 qw{deg_rad rad_deg};
  3         1859  
  3         196  
7 3     3   2468 use Geo::Ellipsoids 0.09 qw{};
  3         6405  
  3         2207  
8              
9             our $VERSION="0.14";
10              
11             =head1 NAME
12              
13             Geo::Forward - Calculate geographic location from lat, lon, distance, and heading.
14              
15             =head1 SYNOPSIS
16              
17             use Geo::Forward;
18             my $obj = Geo::Forward->new(); # default "WGS84"
19             my ($lat1,$lon1,$faz,$dist)=(38.871022, -77.055874, 62.888507083, 4565.6854);
20             my ($lat2,$lon2,$baz) = $obj->forward($lat1,$lon1,$faz,$dist);
21             print "Input Lat: $lat1 Lon: $lon1\n";
22             print "Input Forward Azimuth: $faz\n";
23             print "Input Distance: $dist\n";
24             print "Output Lat: $lat2 Lon: $lon2\n";
25             print "Output Back Azimuth: $baz\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 $obj = Geo::Forward->new(); # default "WGS84"
38              
39             =head1 METHODS
40              
41             =head2 initialize
42              
43             =cut
44              
45             sub initialize {
46 3     3 1 59 my $self = shift;
47 3   50     19 my $param = shift || undef;
48 3         14 $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=$obj->ellipsoid; #Default is WGS84
56              
57             $obj->ellipsoid('Clarke 1866'); #Built in ellipsoids from Geo::Ellipsoids
58             $obj->ellipsoid({a=>1}); #Custom Sphere 1 unit radius
59              
60             =cut
61              
62             sub ellipsoid {
63 1012     1012 1 2693 my $self=shift;
64 1012 100       2370 if (@_) {
65 3         4 my $param=shift;
66 3         19 $self->{'ellipsoid'}=Geo::Ellipsoids->new($param);
67             }
68 1012         2484 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) = $obj->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 701974 my $self = shift;
83 1007         1292 my $lat = shift; #degrees
84 1007         1011 my $lon = shift; #degrees
85 1007         990 my $heading = shift; #degrees
86 1007         941 my $distance = shift; #meters (or the units of the semi-major axis)
87 1007         2444 my ($lat2, $lon2, $baz)= $self->_dirct1(rad_deg($lat),rad_deg($lon),rad_deg($heading),$distance);
88 1007         2814 return(deg_rad($lat2), deg_rad($lon2), deg_rad($baz));
89             }
90              
91             sub _dirct1 {
92 1007     1007   18094 my $self = shift; #provides A and F
93 1007         1216 my $GLAT1 = shift; #radians
94 1007         972 my $GLON1 = shift; #radians
95 1007         938 my $FAZ = shift; #radians
96 1007         1022 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         1833 my $ellipsoid=$self->ellipsoid;
117 1007         2691 my $A=$ellipsoid->a;
118 1007         5322 my $F=$ellipsoid->f;
119             # DATA EPS/0.5D-13/
120 1007         6368 my $EPS=0.5E-13;
121             # R=1.-F
122 1007         1299 my $R=1.-$F;
123             # TU=R*DSIN(GLAT1)/DCOS(GLAT1)
124 1007         2080 my $TU=$R*sin($GLAT1)/cos($GLAT1);
125             # SF=DSIN(FAZ)
126 1007         1378 my $SF=sin($FAZ);
127             # CF=DCOS(FAZ)
128 1007         1170 my $CF=cos($FAZ);
129             # BAZ=0.
130 1007         989 my $BAZ=0.;
131             # IF(CF.NE.0.) BAZ=DATAN2(TU,CF)*2.
132 1007 50       3337 $BAZ=atan2($TU,$CF)*2. if ($CF != 0);
133             # CU=1./DSQRT(TU*TU+1.)
134 1007         1763 my $CU=1./sqrt($TU*$TU+1.);
135             # SU=TU*CU
136 1007         1173 my $SU=$TU*$CU;
137             # SA=CU*SF
138 1007         1103 my $SA=$CU*$SF;
139             # C2A=-SA*SA+1.
140 1007         1535 my $C2A=-$SA*$SA+1.;
141             # X=DSQRT((1./R/R-1.)*C2A+1.)+1.
142 1007         1924 my $X=sqrt((1./$R/$R-1.)*$C2A+1.)+1.;
143             # X=(X-2.)/X
144 1007         1504 $X=($X-2.)/$X;
145             # C=1.-X
146 1007         1253 my $C=1.-$X;
147             # C=(X*X/4.+1)/C
148 1007         1328 $C=($X*$X/4.+1)/$C;
149             # D=(0.375D0*X*X-1.)*X
150 1007         1525 my $D=(0.375*$X*$X-1.)*$X;
151             # TU=S/R/A/C
152 1007         1381 $TU=$S/$R/$A/$C;
153             # Y=TU
154 1007         1017 my $Y=$TU;
155             # 100 SY=DSIN(Y)
156 1007         1075 my ($SY, $CY, $CZ, $E);
157 1007         956 do{ $SY=sin($Y);
  1020         1224  
158             # CY=DCOS(Y)
159 1020         988 $CY=cos($Y);
160             # CZ=DCOS(BAZ+Y)
161 1020         1471 $CZ=cos($BAZ+$Y);
162             # E=CZ*CZ*2.-1.
163 1020         1273 $E=$CZ*$CZ*2.-1.;
164             # C=Y
165 1020         1055 $C=$Y;
166             # X=E*CY
167 1020         1194 $X=$E*$CY;
168             # Y=E+E-1.
169 1020         1059 $Y=$E+$E-1.;
170             # Y=(((SY*SY*4.-3.)*Y*CZ*D/6.+X)*D/4.-CZ)*SY*D+TU
171 1020         4447 $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         1449 $BAZ=$CU*$CY*$CF-$SU*$SY;
176             # C=R*DSQRT(SA*SA+BAZ*BAZ)
177 1007         1352 $C=$R*sqrt($SA*$SA+$BAZ*$BAZ);
178             # D=SU*CY+CU*SY*CF
179 1007         1356 $D=$SU*$CY+$CU*$SY*$CF;
180             # GLAT2=DATAN2(D,C)
181 1007         1657 my $GLAT2=atan2($D,$C);
182             # C=CU*CY-SU*SY*CF
183 1007         1337 $C=$CU*$CY-$SU*$SY*$CF;
184             # X=DATAN2(SY*SF,C)
185 1007         1155 $X=atan2($SY*$SF,$C);
186             # C=((-3.*C2A+4.)*F+4.)*C2A*F/16.
187 1007         1551 $C=((-3.*$C2A+4.)*$F+4.)*$C2A*$F/16.;
188             # D=((E*CY*C+CZ)*SY*C+Y)*SA
189 1007         1625 $D=(($E*$CY*$C+$CZ)*$SY*$C+$Y)*$SA;
190             # GLON2=GLON1+X-(1.-C)*D*F
191 1007         1668 my $GLON2=$GLON1+$X-(1.-$C)*$D*$F;
192             # BAZ=DATAN2(SA,BAZ)+PI
193 1007         2731 $BAZ=atan2($SA,$BAZ)+PI;
194             # RETURN
195 1007         4574 return $GLAT2, $GLON2, $BAZ;
196             # END
197             }
198              
199             =head1 TODO
200              
201             Add tests for more ellipsoids.
202              
203             =head1 BUGS
204              
205             Please log on RT and email to the geo-perl email list as well as the author.
206              
207             =head1 SUPPORT
208              
209             DavisNetworks.com supports all Perl applications including this package.
210              
211             =head1 LIMITS
212              
213             No guarantees that Perl handles all of the double precision calculations in the same manner as Fortran.
214              
215             =head1 AUTHOR
216              
217             Michael R. Davis qw/perl michaelrdavis com/
218             CPAN ID: MRDVT
219              
220             =head1 LICENSE
221              
222             Copyright (c) 2011 Michael R. Davis (mrdvt92)
223              
224             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
225              
226             =head1 SEE ALSO
227              
228             =head3 Similar Packages
229              
230             L, L
231              
232             =head2 Opposite Packages
233              
234             L
235              
236             =head2 Building Blocks
237              
238             L, L, L
239              
240             =cut
241              
242             1;