File Coverage

blib/lib/Geo/Inverse.pm
Criterion Covered Total %
statement 92 93 98.9
branch 16 18 88.8
condition 3 5 60.0
subroutine 10 10 100.0
pod 3 3 100.0
total 124 129 96.1


line stmt bran cond sub pod time code
1             package Geo::Inverse;
2 2     2   262696 use strict;
  2         5  
  2         120  
3 2     2   23 use warnings;
  2         5  
  2         149  
4 2     2   15 use base qw{Package::New};
  2         4  
  2         1372  
5 2     2   1882 use Geo::Constants qw{PI};
  2         1346  
  2         196  
6 2     2   1072 use Geo::Functions qw{rad_deg deg_rad};
  2         2637  
  2         173  
7 2     2   1179 use Geo::Ellipsoids;
  2         5131  
  2         1643  
8              
9             our $VERSION = '0.09';
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 2     2 1 389037 my $self = shift;
48 2   50     24 my $param = shift || undef;
49 2         11 $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 10     10 1 19 my $self = shift;
65 10 100       27 if (@_) {
66 2         3 my $param = shift();
67 2         18 $self->{'ellipsoid'} = Geo::Ellipsoids->new($param);
68             }
69 10         685 return $self->{'ellipsoid'};
70             }
71              
72             =head2 inverse
73              
74             This method is the user frontend to the mathematics. This interface will not change in future versions.
75              
76             my ($faz, $baz, $dist) = $obj->inverse($lat1,$lon1,$lat2,$lon2);
77              
78             =cut
79              
80             sub inverse {
81 12     12 1 12052 my $self = shift;
82 12         24 my $lat1 = shift; #degrees
83 12         21 my $lon1 = shift; #degrees
84 12         16 my $lat2 = shift; #degrees
85 12         18 my $lon2 = shift; #degrees
86 12 100       48 die('Syntax Error: function inverse lat1 not defined') unless defined $lat1;
87 11 100       44 die('Syntax Error: function inverse lon1 not defined') unless defined $lon1;
88 10 100       30 die('Syntax Error: function inverse lat2 not defined') unless defined $lat2;
89 9 100       26 die('Syntax Error: function inverse lon2 not defined') unless defined $lon2;
90 8         35 my ($faz, $baz, $dist) = $self->_inverse(rad_deg($lat1), rad_deg($lon1),
91             rad_deg($lat2), rad_deg($lon2));
92 8 100       37 return wantarray ? (deg_rad($faz), deg_rad($baz), $dist) : $dist;
93             }
94              
95             ########################################################################
96             #
97             # This function was copied from Geo::Ellipsoid
98             # Copyright 2005-2006 Jim Gibson, all rights reserved.
99             #
100             # This program is free software; you can redistribute it and/or modify it
101             # under the same terms as Perl itself.
102             #
103             # internal functions
104             #
105             # inverse
106             #
107             # Calculate the displacement from origin to destination.
108             # The input to this subroutine is
109             # ( latitude-1, longitude-1, latitude-2, longitude-2 ) in radians.
110             #
111             # Return the results as the list (range,bearing) with range in meters
112             # and bearing in radians.
113             #
114             ########################################################################
115              
116             sub _inverse {
117 8     8   258 my $self = shift;
118 8         20 my( $lat1, $lon1, $lat2, $lon2 ) = (@_);
119              
120 8         22 my $ellipsoid=$self->ellipsoid;
121 8         45 my $a = $ellipsoid->a;
122 8         66 my $f = $ellipsoid->f;
123              
124 8         72 my $eps = 1.0e-23;
125 8         12 my $max_loop_count = 20;
126 8         21 my $pi=PI;
127 8         28 my $twopi = 2 * $pi;
128              
129 8         14 my $r = 1.0 - $f;
130 8         58 my $tu1 = $r * sin($lat1) / cos($lat1);
131 8         14 my $tu2 = $r * sin($lat2) / cos($lat2);
132 8         19 my $cu1 = 1.0 / ( sqrt(($tu1*$tu1) + 1.0) );
133 8         52 my $su1 = $cu1 * $tu1;
134 8         14 my $cu2 = 1.0 / ( sqrt( ($tu2*$tu2) + 1.0 ));
135 8         13 my $s = $cu1 * $cu2;
136 8         14 my $baz = $s * $tu2;
137 8         13 my $faz = $baz * $tu1;
138 8         14 my $dlon = $lon2 - $lon1;
139              
140 8         13 my $x = $dlon;
141 8         41 my $cnt = 0;
142 8         19 my( $c2a, $c, $cx, $cy, $cz, $d, $del, $e, $sx, $sy, $y );
143 8   66     13 do {
144 48         90 $sx = sin($x);
145 48         79 $cx = cos($x);
146 48         58 $tu1 = $cu2*$sx;
147 48         71 $tu2 = $baz - ($su1*$cu2*$cx);
148 48         68 $sy = sqrt( $tu1*$tu1 + $tu2*$tu2 );
149 48         64 $cy = $s*$cx + $faz;
150 48         74 $y = atan2($sy,$cy);
151 48         56 my $sa;
152 48 50       88 if( $sy == 0.0 ) {
153 0         0 $sa = 1.0;
154             }else{
155 48         80 $sa = ($s*$sx) / $sy;
156             }
157              
158 48         63 $c2a = 1.0 - ($sa*$sa);
159 48         66 $cz = $faz + $faz;
160 48 50       86 if( $c2a > 0.0 ) {
161 48         66 $cz = ((-$cz)/$c2a) + $cy;
162             }
163 48         71 $e = ( 2.0 * $cz * $cz ) - 1.0;
164 48         76 $c = ( ((( (-3.0 * $c2a) + 4.0)*$f) + 4.0) * $c2a * $f )/16.0;
165 48         63 $d = $x;
166 48         105 $x = ( ($e * $cy * $c + $cz) * $sy * $c + $y) * $sa;
167 48         72 $x = ( 1.0 - $c ) * $x * $f + $dlon;
168 48         199 $del = $d - $x;
169            
170             } while( (abs($del) > $eps) && ( ++$cnt <= $max_loop_count ) );
171              
172 8         48 $faz = atan2($tu1,$tu2);
173 8         19 $baz = atan2($cu1*$sx,($baz*$cx - $su1*$cu2)) + $pi;
174 8         19 $x = sqrt( ((1.0/($r*$r)) -1.0 ) * $c2a+1.0 ) + 1.0;
175 8         16 $x = ($x-2.0)/$x;
176 8         34 $c = 1.0 - $x;
177 8         18 $c = (($x*$x)/4.0 + 1.0)/$c;
178 8         16 $d = ((0.375*$x*$x) - 1.0)*$x;
179 8         27 $x = $e*$cy;
180              
181 8         15 $s = 1.0 - $e - $e;
182 8         26 $s = (((((((( $sy * $sy * 4.0 ) - 3.0) * $s * $cz * $d/6.0) - $x) *
183             $d /4.0) + $cz) * $sy * $d) + $y ) * $c * $a * $r;
184              
185             # adjust azimuth to (0,360)
186 8 100       22 $faz += $twopi if $faz < 0;
187              
188 8         36 return($faz, $baz, $s);
189             }
190              
191             =head1 LIMITATIONS
192              
193             No guarantees that Perl handles all of the double precision calculations in the same manner as Fortran.
194              
195             =head1 LICENSE
196              
197             Copyright (c) 2025 Michael R. Davis
198             Copyright (c) 2005-2006 Jim Gibson, all rights reserved.
199              
200             This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
201              
202             =head1 SEE ALSO
203              
204             L, L, L
205              
206             =cut
207              
208             1;