File Coverage

lib/Geo/Inverse.pm
Criterion Covered Total %
statement 92 93 98.9
branch 7 10 70.0
condition 4 8 50.0
subroutine 10 10 100.0
pod 3 4 75.0
total 116 125 92.8


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