File Coverage

blib/lib/Geo/Inverse.pm
Criterion Covered Total %
statement 89 90 98.8
branch 7 10 70.0
condition 3 5 60.0
subroutine 10 10 100.0
pod 3 3 100.0
total 112 118 94.9


line stmt bran cond sub pod time code
1             package Geo::Inverse;
2 1     1   474 use strict;
  1         2  
  1         29  
3 1     1   5 use warnings;
  1         2  
  1         26  
4 1     1   5 use base qw{Package::New};
  1         1  
  1         491  
5 1     1   689 use Geo::Constants qw{PI};
  1         390  
  1         70  
6 1     1   470 use Geo::Functions qw{rad_deg deg_rad};
  1         882  
  1         58  
7 1     1   480 use Geo::Ellipsoids;
  1         2163  
  1         713  
8              
9             our $VERSION = '0.07';
10              
11             =head1 NAME
12              
13             Geo::Inverse - Calculate geographic distance from a latitude and longitude pair
14              
15             =head1 SYNOPSIS
16              
17             use Geo::Inverse;
18             my $obj = Geo::Inverse->new(); #default "WGS84"
19             my ($lat1, $lon1, $lat2, $lon2) = (38.87, -77.05, 38.95, -77.23);
20             my ($faz, $baz, $dist) = $obj->inverse($lat1,$lon1,$lat2,$lon2); #array context
21             my $dist=$obj->inverse($lat1, $lon1, $lat2, $lon2); #scalar context
22             print "Input Lat: $lat1 Lon: $lon1\n";
23             print "Input Lat: $lat2 Lon: $lon2\n";
24             print "Output Distance: $dist\n";
25             print "Output Forward Azimuth: $faz\n";
26             print "Output Back Azimuth: $baz\n";
27              
28             =head1 DESCRIPTION
29              
30             This module is a pure Perl port of the NGS program in the public domain "inverse" by Robert (Sid) Safford and Stephen J. Frakes.
31              
32             =head1 CONSTRUCTOR
33              
34             =head2 new
35              
36             The new() constructor may be called with any parameter that is appropriate to the ellipsoid method which establishes the ellipsoid.
37              
38             my $obj = Geo::Inverse->new(); # default "WGS84"
39              
40             =head1 METHODS
41              
42             =head2 initialize
43              
44             =cut
45              
46             sub initialize {
47 1     1 1 150 my $self = shift;
48 1   50     10 my $param = shift || undef;
49 1         4 $self->ellipsoid($param);
50             }
51              
52             =head2 ellipsoid
53              
54             Method to set or retrieve the current ellipsoid object. The ellipsoid is a Geo::Ellipsoids object.
55              
56             my $ellipsoid = $obj->ellipsoid; #Default is WGS84
57              
58             $obj->ellipsoid('Clarke 1866'); #Built in ellipsoids from Geo::Ellipsoids
59             $obj->ellipsoid({a=>1}); #Custom Sphere 1 unit radius
60              
61             =cut
62              
63             sub ellipsoid {
64 5     5 1 7 my $self = shift();
65 5 100       12 if (@_) {
66 1         1 my $param = shift();
67 1         5 my $obj = Geo::Ellipsoids->new($param);
68 1         196 $self->{'ellipsoid'}=$obj;
69             }
70 5         11 return $self->{'ellipsoid'};
71             }
72              
73             =head2 inverse
74              
75             This method is the user frontend to the mathematics. This interface will not change in future versions.
76              
77             my ($faz, $baz, $dist) = $obj->inverse($lat1,$lon1,$lat2,$lon2);
78              
79             =cut
80              
81             sub inverse {
82 4     4 1 796 my $self = shift();
83 4         6 my $lat1 = shift(); #degrees
84 4         6 my $lon1 = shift(); #degrees
85 4         6 my $lat2 = shift(); #degrees
86 4         6 my $lon2 = shift(); #degrees
87 4         9 my ($faz, $baz, $dist) = $self->_inverse(rad_deg($lat1), rad_deg($lon1),
88             rad_deg($lat2), rad_deg($lon2));
89 4 100       12 return wantarray ? (deg_rad($faz), deg_rad($baz), $dist) : $dist;
90             }
91              
92             ########################################################################
93             #
94             # This function was copied from Geo::Ellipsoid
95             # Copyright 2005-2006 Jim Gibson, all rights reserved.
96             #
97             # This program is free software; you can redistribute it and/or modify it
98             # under the same terms as Perl itself.
99             #
100             # internal functions
101             #
102             # inverse
103             #
104             # Calculate the displacement from origin to destination.
105             # The input to this subroutine is
106             # ( latitude-1, longitude-1, latitude-2, longitude-2 ) in radians.
107             #
108             # Return the results as the list (range,bearing) with range in meters
109             # and bearing in radians.
110             #
111             ########################################################################
112              
113             sub _inverse {
114 4     4   109 my $self = shift;
115 4         8 my( $lat1, $lon1, $lat2, $lon2 ) = (@_);
116              
117 4         8 my $ellipsoid=$self->ellipsoid;
118 4         11 my $a = $ellipsoid->a;
119 4         21 my $f = $ellipsoid->f;
120              
121 4         29 my $eps = 1.0e-23;
122 4         5 my $max_loop_count = 20;
123 4         8 my $pi=PI;
124 4         11 my $twopi = 2 * $pi;
125              
126 4         6 my $r = 1.0 - $f;
127 4         39 my $tu1 = $r * sin($lat1) / cos($lat1);
128 4         25 my $tu2 = $r * sin($lat2) / cos($lat2);
129 4         8 my $cu1 = 1.0 / ( sqrt(($tu1*$tu1) + 1.0) );
130 4         6 my $su1 = $cu1 * $tu1;
131 4         7 my $cu2 = 1.0 / ( sqrt( ($tu2*$tu2) + 1.0 ));
132 4         6 my $s = $cu1 * $cu2;
133 4         4 my $baz = $s * $tu2;
134 4         7 my $faz = $baz * $tu1;
135 4         5 my $dlon = $lon2 - $lon1;
136              
137 4         5 my $x = $dlon;
138 4         6 my $cnt = 0;
139 4         5 my( $c2a, $c, $cx, $cy, $cz, $d, $del, $e, $sx, $sy, $y );
140 4   66     6 do {
141 22         24 $sx = sin($x);
142 22         26 $cx = cos($x);
143 22         30 $tu1 = $cu2*$sx;
144 22         27 $tu2 = $baz - ($su1*$cu2*$cx);
145 22         28 $sy = sqrt( $tu1*$tu1 + $tu2*$tu2 );
146 22         28 $cy = $s*$cx + $faz;
147 22         33 $y = atan2($sy,$cy);
148 22         27 my $sa;
149 22 50       41 if( $sy == 0.0 ) {
150 0         0 $sa = 1.0;
151             }else{
152 22         28 $sa = ($s*$sx) / $sy;
153             }
154              
155 22         28 $c2a = 1.0 - ($sa*$sa);
156 22         25 $cz = $faz + $faz;
157 22 50       37 if( $c2a > 0.0 ) {
158 22         33 $cz = ((-$cz)/$c2a) + $cy;
159             }
160 22         24 $e = ( 2.0 * $cz * $cz ) - 1.0;
161 22         33 $c = ( ((( (-3.0 * $c2a) + 4.0)*$f) + 4.0) * $c2a * $f )/16.0;
162 22         25 $d = $x;
163 22         32 $x = ( ($e * $cy * $c + $cz) * $sy * $c + $y) * $sa;
164 22         25 $x = ( 1.0 - $c ) * $x * $f + $dlon;
165 22         71 $del = $d - $x;
166            
167             } while( (abs($del) > $eps) && ( ++$cnt <= $max_loop_count ) );
168              
169 4         17 $faz = atan2($tu1,$tu2);
170 4         7 $baz = atan2($cu1*$sx,($baz*$cx - $su1*$cu2)) + $pi;
171 4         8 $x = sqrt( ((1.0/($r*$r)) -1.0 ) * $c2a+1.0 ) + 1.0;
172 4         5 $x = ($x-2.0)/$x;
173 4         5 $c = 1.0 - $x;
174 4         7 $c = (($x*$x)/4.0 + 1.0)/$c;
175 4         5 $d = ((0.375*$x*$x) - 1.0)*$x;
176 4         5 $x = $e*$cy;
177              
178 4         7 $s = 1.0 - $e - $e;
179 4         9 $s = (((((((( $sy * $sy * 4.0 ) - 3.0) * $s * $cz * $d/6.0) - $x) *
180             $d /4.0) + $cz) * $sy * $d) + $y ) * $c * $a * $r;
181              
182             # adjust azimuth to (0,360)
183 4 50       8 $faz += $twopi if $faz < 0;
184              
185 4         13 return($faz, $baz, $s);
186             }
187              
188             =head1 BUGS
189              
190             Please open an issue on GitHub
191              
192             =head1 LIMITS
193              
194             No guarantees that Perl handles all of the double precision calculations in the same manner as Fortran.
195              
196             =head1 LICENSE
197              
198             MIT License
199              
200             Copyright (c) 2022 Michael R. Davis
201              
202             =head1 SEE ALSO
203              
204             L, L, L
205              
206             =cut
207              
208             1;