File Coverage

blib/lib/Geo/ECEF.pm
Criterion Covered Total %
statement 70 94 74.4
branch 7 10 70.0
condition 19 29 65.5
subroutine 12 13 92.3
pod 8 8 100.0
total 116 154 75.3


line stmt bran cond sub pod time code
1             package Geo::ECEF;
2 1     1   920 use strict;
  1         3  
  1         59  
3 1     1   6 use warnings;
  1         2  
  1         41  
4 1     1   1112 use Geo::Ellipsoids;
  1         6055  
  1         47  
5 1     1   14 use Geo::Functions qw{rad_deg deg_rad};
  1         2  
  1         280  
6              
7             our $VERSION="1.10";
8              
9             =head1 NAME
10              
11             Geo::ECEF - Converts between ECEF (earth centered earth fixed) coordinates and latitude, longitude and height above ellipsoid.
12              
13             =head1 SYNOPSIS
14              
15             use Geo::ECEF;
16             my $obj=Geo::ECEF->new(); #WGS84 is the default
17             my ($x, $y, $z)=$obj->ecef(39.197807, -77.108574, 55); #Lat (deg), Lon (deg), HAE (meters)
18             print "X: $x\tY: $y\tZ: $z\n";
19              
20             my ($lat, $lon, $hae)=$obj->geodetic($x, $y, $z); #X (meters), Y (meters), Z (meters)
21             print "Lat: $lat \tLon: $lon \tHAE $hae\n";
22              
23              
24             =head1 DESCRIPTION
25              
26             Geo::ECEF provides two methods ecef and geodetic. The ecef method calculates the X,Y and Z coordinates in the ECEF (earth centered earth fixed) coordinate system from latitude, longitude and height above the ellipsoid. The geodetic method calculates the latitude, longitude and height above ellipsoid from ECEF coordinates.
27              
28             The formulas were found at http://www.u-blox.ch/ and http://waas.stanford.edu/~wwu/maast/maastWWW1_0.zip.
29              
30             This code is an object Perl rewrite of a similar package by Morten Sickel, Norwegian Radiation Protection Authority
31              
32             =head1 CONSTRUCTOR
33              
34             =head2 new
35              
36             The new() constructor initializes the ellipsoid method.
37              
38             my $obj=Geo::ECEF->new("WGS84"); #WGS84 is the default
39              
40             =cut
41              
42             sub new {
43 1     1 1 576 my $this = shift();
44 1   33     11 my $class = ref($this) || $this;
45 1         3 my $self = {};
46 1         2 bless $self, $class;
47 1         4 $self->initialize(@_);
48 1         3 return $self;
49             }
50              
51             =head1 METHODS
52              
53             =head2 initialize
54              
55             =cut
56              
57             sub initialize {
58 2     2 1 559 my $self = shift();
59 2   100     11 my $param = shift()||undef();
60 2         5 $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 27     27 1 32 my $self = shift();
76 27 100       57 if (@_) {
77 2         3 my $param=shift();
78 1     1   19 use Geo::Ellipsoids;
  1         4  
  1         969  
79 2         11 my $obj=Geo::Ellipsoids->new($param);
80 2         576 $self->{'ellipsoid'}=$obj;
81             }
82 27         65 return $self->{'ellipsoid'};
83             }
84              
85             =head2 ecef
86              
87             Method returns X (meters), Y (meters), Z (meters) from lat (degrees), lon (degrees), HAE (meters).
88              
89             my ($x, $y, $z)=$obj->ecef(39.197807, -77.108574, 55);
90              
91             =cut
92              
93             sub ecef {
94 15     15 1 6082 my $self = shift();
95 15   100     68 my $lat_rad=rad_deg(shift()||0);
96 15   100     186 my $lon_rad=rad_deg(shift()||0);
97 15   100     143 my $hae=shift()||0;
98 15         35 return $self->ecef_rad($lat_rad, $lon_rad, $hae);
99             }
100              
101             =head2 ecef_rad
102              
103             Method returns X (meters), Y (meters), Z (meters) from lat (radians), lon (radians), HAE (meters).
104              
105             my ($x, $y, $z)=$obj->ecef(0.678, -0.234, 55);
106              
107             This method may be copyright Michael Kleder, April 2006 from mathworks.com
108              
109             =cut
110              
111             sub ecef_rad {
112 15     15 1 17 my $self = shift();
113 15   100     38 my $lat=shift()||0;
114 15   100     45 my $lon=shift()||0;
115 15   100     43 my $hae=shift()||0;
116 15         28 my $ellipsoid=$self->ellipsoid;
117 15         43 my $e2=$ellipsoid->e2;
118 15         174 my $n=$ellipsoid->n_rad($lat);
119 15         359 my $x=($n+$hae) * cos($lat) * cos($lon);
120 15         35 my $y=($n+$hae) * cos($lat) * sin($lon);
121 15         29 my $z=((1-$e2) * $n + $hae) * sin($lat);
122 15         34 my @array=($x, $y, $z);
123 15 100       84 return wantarray ? @array : \@array;
124             }
125              
126             =head2 geodetic
127              
128             Calls the default geodetic method. This user interface will not change,
129              
130             Method returns latitude (degrees), longitude (degrees), HAE (meters) from X (meters), Y (meters), Z (meters).
131              
132             my ($lat, $lon, $hae)=$obj->geodetic($x, $y, $z);
133              
134             =cut
135              
136             sub geodetic {
137 6     6 1 3886 my $self = shift();
138 6         18 return $self->geodetic_direct(@_);
139             }
140              
141             =head2 geodetic_iterative
142              
143             Method returns latitude (degrees), longitude (degrees), HAE (meters) from X (meters), Y (meters), Z (meters). This is an iterative calculation.
144              
145             my ($lat, $lon, $hae)=$obj->geodetic($x, $y, $z);
146              
147             Portions of this method may be...
148              
149             *************************************************************************
150             * Copyright c 2001 The board of trustees of the Leland Stanford *
151             * Junior University. All rights reserved. *
152             * This script file may be distributed and used freely, provided *
153             * this copyright notice is always kept with it. *
154             * *
155             * Questions and comments should be directed to Todd Walter at: *
156             * twalter@stanford.edu *
157             *************************************************************************
158              
159             =cut
160              
161             sub geodetic_iterative {
162 0     0 1 0 my $self = shift();
163 0   0     0 my $x=shift()||0;
164 0   0     0 my $y=shift()||0;
165 0   0     0 my $z=shift()||0;
166 0         0 my $ellipsoid=$self->ellipsoid;
167 0         0 my $e2=$ellipsoid->e2;
168 0         0 my $p=sqrt($x**2 + $y**2);
169 0         0 my $lon=atan2($y,$x);
170 0         0 my $lat=atan2($z/$p, 0.01);
171 0         0 my $n=$ellipsoid->n_rad($lat);
172 0         0 my $hae=$p/cos($lat) - $n;
173 0         0 my $old_hae=-1e-9;
174 0         0 my $num=$z/$p;
175 0         0 while (abs($hae-$old_hae) > 1e-4) {
176 0         0 $old_hae=$hae;
177 0         0 my $den=1 - $e2 * $n /($n + $hae);
178 0         0 $lat=atan2($num, $den);
179 0         0 $n=$ellipsoid->n_rad($lat);
180 0         0 $hae=$p/cos($lat)-$n;
181             }
182 0         0 $lat=deg_rad($lat);
183 0         0 $lon=deg_rad($lon);
184 0         0 my @array=($lat, $lon, $hae);
185 0 0       0 return wantarray ? @array : \@array;
186             }
187              
188             =head2 geodetic_direct
189              
190             Method returns latitude (degrees), longitude (degrees), HAE (meters) from X (meters), Y (meters), Z (meters). This is a direct (non-iterative) calculation from the gpsd distribution.
191              
192             my ($lat, $lon, $hae)=$obj->geodetic($x, $y, $z);
193              
194             This method may be copyright Michael Kleder, April 2006 from mathworks.com
195              
196             =cut
197              
198             sub geodetic_direct {
199 10     10 1 1238 my $self = shift();
200 10   50     25 my $x=shift()||0;
201 10   100     28 my $y=shift()||0;
202 10   50     20 my $z=shift()||0;
203              
204 10         23 my $ellipsoid=$self->ellipsoid;
205 10         32 my $a = $ellipsoid->a;
206 10         58 my $b = $ellipsoid->b;
207 10         93 my $e2 = $ellipsoid->e2;
208 10         96 my $ep2 = $ellipsoid->ep2;
209 10         151 my $p = sqrt($x**2 + $y**2);
210 10         34 my $t = atan2($z*$a, $p*$b);
211 10         22 my $lon=atan2($y, $x);
212 10         61 my $lat=atan2($z + $ep2*$b*sin($t)**3, $p - $e2*$a*cos($t)**3);
213 10         30 my $n = $ellipsoid->n_rad($lat);
214 10         159 my $hae;
215 10         15 eval {
216 10         21 $hae = $p/cos($lat) - $n; #Just in case $lat is +-90 degrees
217             };
218              
219 10         11 my @array;
220 10 50       18 if ($@) {
221 0         0 @array=(deg_rad($lat), 0, abs($z)-$b); #Is this correct?
222             } else {
223 10         53 @array=(deg_rad($lat), deg_rad($lon), $hae);
224             }
225 10 100       171 return wantarray ? @array : \@array;
226             }
227              
228             =head1 TODO
229              
230             =head1 SUPPORT
231              
232             DavisNetworks.com supports all Perl applications big or small.
233              
234             =head1 BUGS
235              
236             Please log on RT and send email to the geo-perl email list.
237              
238             =head1 LIMITS
239              
240             Win32 platforms cannot tell the difference between the deprecated L module and the current L module. The way to tell is if Geo::ECEF->can("new");
241              
242             =head1 AUTHORS
243              
244             Michael R. Davis qw/perl michaelrdavis com/
245              
246             Morten Sickel http://sickel.net/
247              
248             =head1 LICENSE
249              
250             Copyright (c) 2007-2010 Michael R. Davis (mrdvt92)
251              
252             Copyright (c) 2005 Morten Sickel (sickel.net)
253              
254             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
255              
256             =head1 SEE ALSO
257              
258             L, L, L, L, L, http://www.ngs.noaa.gov/cgi-bin/xyz_getxyz.prl, http://www.mathworks.com/matlabcentral/
259              
260             =cut
261              
262             1;