File Coverage

blib/lib/Geo/Ellipsoids.pm
Criterion Covered Total %
statement 113 138 81.8
branch 40 58 68.9
condition 3 5 60.0
subroutine 24 29 82.7
pod 23 23 100.0
total 203 253 80.2


line stmt bran cond sub pod time code
1             package Geo::Ellipsoids;
2 1     1   182430 use strict;
  1         3  
  1         48  
3 1     1   6 use warnings;
  1         1  
  1         84  
4 1     1   630 use Geo::Constants qw{PI};
  1         614  
  1         94  
5 1     1   618 use Geo::Functions qw{rad_deg};
  1         1452  
  1         2876  
6              
7             our $VERSION = '0.17';
8             our $DEFAULT_ELIPS = 'WGS84';
9              
10             =head1 NAME
11              
12             Geo::Ellipsoids - Package for standard Geo:: ellipsoid a, b, f and 1/f values.
13              
14             =head1 SYNOPSIS
15              
16             use Geo::Ellipsoids;
17             my $obj = Geo::Ellipsoids->new();
18             $obj->set('WGS84'); #default
19             print "a=", $obj->a, "\n";
20             print "b=", $obj->b, "\n";
21             print "f=", $obj->f, "\n";
22             print "i=", $obj->i, "\n";
23             print "e=", $obj->e, "\n";
24             print "n=", $obj->n(45), "\n";
25              
26             =head1 DESCRIPTION
27              
28             Package for standard Geo:: ellipsoid a, b, f and 1/f values.
29              
30             =head1 CONSTRUCTOR
31              
32             =head2 new
33              
34             The new() constructor may be called with any parameter that is appropriate to the set method.
35              
36             my $obj = Geo::Ellipsoid->new();
37              
38             =cut
39              
40             sub new {
41 2     2 1 202 my $this = shift;
42 2 50       8 my $class = ref($this) ? ref($this) : $this;
43 2         5 my $self = {};
44 2         5 bless $self, $class;
45 2         8 $self->initialize(@_);
46 2         9 return $self;
47             }
48              
49             =head1 METHODS
50              
51             =head2 initialize
52              
53             =cut
54              
55             sub initialize {
56 2     2 1 4 my $self = shift;
57 2         5 my $param = shift;
58 2         8 $self->set($param);
59             }
60              
61             =head2 set
62              
63             Method sets the current ellipsoid. This method is called when the object is constructed (default is WGS84).
64              
65             $obj->set(); #default WGS84
66             $obj->set('Clarke 1866'); #All built in ellipsoids are stored in meters
67             $obj->set({a=>1, b=>1}); #Custom Sphere 1 unit radius
68              
69             =cut
70              
71             sub set {
72 11     11 1 258 my $self = shift;
73 11   66     40 my $param = shift || $DEFAULT_ELIPS;
74 11         57 undef($self->{'shortname'});
75 11         21 undef($self->{'longname'});
76 11 100       36 if ('HASH' eq ref($param)) {
    50          
77 5         14 return $self->_setref($param);
78             } elsif ('' eq ref($param)) {
79 6         19 return $self->_setname($param);
80             } else {
81 0         0 die('Error: Parameter must be the name of an ellipsoid or a hash reference');
82             }
83             }
84              
85             =head2 list
86              
87             Method returns a list of known ellipsoid names.
88              
89             my @list=$obj->list;
90              
91             my $list=$obj->list;
92             while (@$list) {
93             print "$_\n";
94             }
95              
96             =cut
97              
98             sub list {
99 0     0 1 0 my $self = shift;
100 0         0 my $data = $self->data;
101 0         0 my @keys = keys %$data;
102 0 0       0 return wantarray ? @keys : \@keys;
103             }
104              
105             =head2 a
106              
107             Method returns the value of the semi-major axis.
108              
109             my $a=$obj->a;
110              
111             =cut
112              
113             sub a {
114 17     17 1 116 my $self = shift;
115 17   50     96 return $self->{'a'} || die(q{Error: $self->{'a'} must be defined here});
116             }
117              
118             =head2 b
119              
120             Method returns the value of the semi-minor axis.
121              
122             my $b=$obj->b; #b=a(1-f)
123              
124             =cut
125              
126             sub b {
127 12     12 1 28 my $self = shift;
128 12 100       39 if (defined $self->{'b'}) {
    100          
    50          
129 6         32 return $self->{'b'};
130             } elsif (defined $self->{'f'}) {
131 1         7 return $self->{'a'}*(1-$self->{'f'});
132             } elsif (defined $self->{'i'}) {
133 5         27 return $self->{'a'}*(1-1/$self->{'i'});
134             } else {
135 0         0 return undef();
136             }
137             }
138              
139             =head2 f
140              
141             Method returns the value of flatting
142              
143             my $f=$obj->f; #f=(a-b)/a
144              
145             =cut
146              
147             sub f {
148 15     15 1 28 my $self = shift;
149 15 100       49 if (defined $self->{'f'}) {
    100          
    50          
150 2         14 return $self->{'f'};
151             } elsif (defined $self->{'b'}) {
152 5         26 return ($self->{'a'}-$self->{'b'})/$self->{'a'};
153             } elsif (defined $self->{'i'}) {
154 8         28 return 1/$self->{'i'};
155             } else {
156 0         0 return undef();
157             }
158             }
159              
160             =head2 i
161              
162             Method returns the value of the inverse flatting
163              
164             my $i = $obj->i; #i=1/f=a/(a-b)
165              
166             =cut
167              
168             sub i {
169 8     8 1 74 my $self = shift;
170 8 100       30 if (defined $self->{'i'}) {
    100          
    50          
171 4         17 return $self->{'i'};
172             } elsif (defined $self->{'b'}) {
173 3 100       10 if ($self->{'a'} == $self->{'b'}) {
174 2         9 return undef();
175             } else {
176 1         19 return $self->{'a'}/($self->{'a'}-$self->{'b'});
177             }
178             } elsif (defined $self->{'f'}) {
179 1         5 return 1/$self->{'f'};
180             } else {
181 0         0 return undef();
182             }
183             }
184              
185             =head2 invf
186              
187             Method synonym for the i method
188              
189             my $i = $obj->invf; #i=1/f
190              
191             =cut
192              
193             sub invf {
194 0     0 1 0 my $self = shift;
195 0         0 return $self->i(@_);
196             }
197              
198             =head2 e
199              
200             Method returns the value of the first eccentricity, e. This is the eccentricity of the earth's elliptical cross-section.
201              
202             my $e=$obj->e;
203              
204             =cut
205              
206             sub e {
207 2     2 1 5 my $self = shift;
208 2         5 return sqrt($self->e2);
209             }
210              
211             =head2 e2
212              
213             Method returns the value of eccentricity squared (e.g. e^2). This is not the second eccentricity, e' or e-prime see the "ep" method.
214              
215             my $e2 = sqrt($obj->e2); #e^2 = f(2-f) = 2f-f^2 = 1-b^2/a^2
216              
217             =cut
218              
219             sub e2 {
220 7     7 1 22 my $self = shift;
221 7         15 my $f = $self->f();
222 7         26 return $f*(2 - $f);
223             }
224              
225             =head2 ep
226              
227             Method returns the value of the second eccentricity, e' or e-prime. The second eccentricity is related to the first eccentricity by the equation: 1=(1-e^2)(1+e'^2).
228              
229             my $ep = $obj->ep;
230              
231             =cut
232              
233             sub ep {
234 0     0 1 0 my $self = shift;
235 0         0 return sqrt($self->ep2);
236             }
237              
238             =head2 ep2
239              
240             Method returns the square of value of second eccentricity, e' (e-prime). This is more useful in almost all equations.
241              
242             my $ep=sqrt($obj->ep2); #ep2=(ea/b)^2=e2/(1-e2)=a^2/b^2-1
243              
244             =cut
245              
246             sub ep2 {
247 1     1 1 5 my $self = shift;
248 1         4 my $a = $self->a();
249 1         4 my $b = $self->b();
250 1         7 return $a**2/$b**2 - 1;
251             }
252              
253             =head2 n
254              
255             Method returns the value of n given latitude (degrees). Typically represented by the Greek letter nu, this is the radius of curvature of the ellipsoid perpendicular to the meridian plane. It is also the distance from the point in question to the polar axis, measured perpendicular to the ellipsoid's surface.
256              
257             my $n = $obj->n($lat);
258              
259             Note: Some define a variable n as (a-b)/(a+b) this is not that variable.
260              
261             Note: It appears that n can also be calculated as
262              
263             n = a^2/sqrt(a^2 * cos($lat)^2 + $b^2 * sin($lat)^2);
264              
265             =cut
266              
267             sub n {
268 1     1 1 55 my $self = shift;
269 1         2 my $lat = shift; #degrees
270 1 50       4 die('Error: Latitude (degrees) required.') unless defined $lat;
271 1         26 return $self->n_rad(rad_deg($lat));
272             }
273              
274             =head2 n_rad
275              
276             Method returns the value of n given latitude (radians).
277              
278             my $n_rad = $obj->n_rad($lat);
279              
280             Reference: John P. Snyder, "Map Projections: A Working Manual", USGS, page 25, equation (4-20) http://pubs.er.usgs.gov/usgspubs/pp/pp1395
281              
282             =cut
283              
284             sub n_rad {
285 2     2 1 105 my $self = shift;
286 2         3 my $lat = shift; #radians
287 2 50       7 die('Error: Latitude (radians) required.') unless defined $lat;
288 2         6 my $a = $self->a;
289 2         6 my $e2 = $self->e2;
290 2         14 return $a / sqrt(1 - $e2 * sin($lat)**2);
291             }
292              
293             =head2 rho
294              
295             rho is the radius of curvature of the earth in the meridian plane.
296              
297             my $rho=$obj->rho($lat);
298              
299             =cut
300              
301             sub rho {
302 0     0 1 0 my $self = shift;
303 0         0 my $lat = shift; #degrees
304 0 0       0 die('Error: Latitude (degrees) required.') unless defined $lat;
305 0         0 return $self->rho_rad(rad_deg($lat));
306             }
307              
308             =head2 rho_rad
309              
310             rho is the radius of curvature of the earth in the meridian plane. Sometimes denoted as R'.
311              
312             my $rho = $obj->rho_rad($lat);
313              
314             Reference: John P. Snyder, "Map Projections: A Working Manual", USGS, page 24, equation (4-18) http://pubs.er.usgs.gov/usgspubs/pp/pp1395
315              
316             =cut
317              
318             sub rho_rad {
319 0     0 1 0 my $self = shift;
320 0         0 my $lat = shift; #radians
321 0 0       0 die('Error: Latitude (radians) required.') unless defined $lat;
322 0         0 my $a = $self->a;
323 0         0 my $e2 = $self->e2;
324 0         0 return $a * (1-$e2) / ( 1 - $e2 * sin($lat)**2 )**(3/2)
325             #return $a * (1-$e2) / sqrt(1 - $e2 * sin($lat)**(3/2)); #Bad formula from somewhere
326             }
327              
328             =head2 polar_circumference
329              
330             Method returns the value of the semi-minor axis times 2*PI.
331              
332             my $polar_circumference=$obj->polar_circumference;
333              
334             =cut
335              
336             sub polar_circumference {
337 2     2 1 124 my $self = shift;
338 2         5 return 2 * PI() * $self->b();
339             }
340              
341             =head2 equatorial_circumference
342              
343             Method returns the value of the semi-major axis times 2*PI.
344              
345             my $equatorial_circumference=$obj->equatorial_circumference;
346              
347             =cut
348              
349             sub equatorial_circumference {
350 2     2 1 4 my $self = shift;
351 2         7 return 2 * PI() * $self->a();
352             }
353              
354             sub _setref {
355 11     11   18 my $self = shift;
356 11         18 my $param = shift;
357 11 50       34 if ('HASH' eq ref($param)) {
358 11 50       27 if (defined($param->{'a'})) {
359 11         38 $self->{'a'} = $param->{'a'};
360 11 100       24 $self->{'shortname'} = 'Custom' unless defined($self->shortname);
361 11 100       35 if (defined $param->{'i'}) {
    100          
    100          
362 6         13 $self->{'i'} = $param->{'i'};
363 6         10 undef($self->{'b'});
364 6         13 undef($self->{'f'});
365 6 100       13 $self->{'longname'} = 'Custom Ellipsoid {a=>'.$self->a.',i=>'.$self->i.'}' unless defined($self->longname);
366             } elsif (defined $param->{'b'}){
367 3         7 $self->{'b'} = $param->{'b'};
368 3         7 undef($self->{'i'});
369 3         6 undef($self->{'f'});
370 3 100       6 $self->{'longname'} = 'Custom Ellipsoid {a=>'.$self->a.',b=>'.$self->b.'}' unless defined($self->longname);
371             } elsif (defined $param->{'f'}){
372 1         3 $self->{'f'} = $param->{'f'};
373 1         2 undef($self->{'b'});
374 1         27 undef($self->{'i'});
375 1 50       4 $self->{'longname'} = 'Custom Ellipsoid {a=>'.$self->a.',f=>'.$self->f.'}' unless defined($self->longname);
376             } else {
377 1         2 $self->{'b'} = $param->{'a'};
378 1         14 undef($self->{'f'});
379 1         3 undef($self->{'i'});
380 1 50       3 $self->{'longname'} = 'Custom Sphere {a=>'.$self->a.'}' unless defined($self->longname);
381             }
382             } else {
383 0         0 die('Error: a must be defined');
384             }
385             } else {
386 0         0 die('Error: a hash reference e.g. {a=>###, i=>###} must be define');
387             }
388 11         161 return 1;
389             }
390              
391             sub _setname {
392 6     6   11 my $self = shift;
393 6         12 my $param = shift;
394 6         14 my $ref = $self->name2ref($param);
395 6 50       21 if ('HASH' eq ref($ref)) {
396 6         16 $self->{'shortname'}=$param;
397 6         15 my $data = $self->data;
398 6         41 my %data = map {$_, $data->{$_}->{'name'}} (keys %$data);
  132         305  
399 6         35 $self->{'longname'} = $data{$param};
400 6         20 return $self->_setref($ref);
401             } else {
402 0         0 die("Error: Ellipsoid $param was not found");
403             }
404             }
405              
406             =head2 shortname
407              
408             Method returns the shortname, which is the hash key, of the current ellipsoid
409              
410             my $shortname = $obj->shortname;
411              
412             =cut
413              
414             sub shortname {
415 12     12 1 24 my $self = shift;
416 12         48 return $self->{'shortname'};
417             }
418              
419             =head2 longname
420              
421             Method returns the long name of the current ellipsoid
422              
423             my $longname = $obj->longname;
424              
425             =cut
426              
427             sub longname {
428 12     12 1 20 my $self = shift;
429 12         68 return $self->{'longname'};
430             }
431              
432             =head2 data
433              
434             Method returns a hash reference for the ellipsoid definition data structure.
435              
436             my $datastructure = $obj->data;
437              
438             =cut
439              
440             sub data {
441             #Information from
442             # http://earth-info.nga.mil/GandG/coordsys/datums/datumorigins.html
443             # http://www.ngs.noaa.gov/PC_PROD/Inv_Fwd/
444              
445             return {
446              
447 12     12 1 694 WGS84=>{name=>'World Geodetic System of 1984',
448             data=>{a=>6378137,i=>298.257223563},
449             alias=>[qw{WGS-84 NAD83 NAD-83}]},
450              
451             GRS80=>{name=>'Geodetic Reference System of 1980',
452             data=>{a=>6378137,i=>298.25722210088},
453             alias=>['GRS-80','GDA','Geocentric Datum of Australia']},
454            
455             'Clarke 1866'=>{name=>'Clarke Ellipsoid of 1866',
456             data=>{a=>6378206.4,i=>294.9786982138},
457             alias=>[qw{NAD27 NAD-27}]},
458            
459             'Airy 1858'=>{name=>'Airy 1858 Ellipsoid',
460             data=>{a=>6377563.396,i=>299.3249646}},
461              
462              
463             'Airy Modified'=>{name=>'Modified Airy Spheroid',
464             data=>{a=>6377340.189,b=>6356034.448}},
465              
466             'Australian National'=>{name=>'Australian National Spheroid of 1965',
467             data=>{a=>6378160,i=>298.25},
468             alias=>['Australian 1965']},
469              
470             'Bessel 1841'=>{name=>'Bessel 1841 Ellipsoid',
471             data=>{a=>6377397.155,i=>299.1528128}},
472              
473             'Clarke 1880'=>{name=>'Clarke Ellipsoid of 1880',
474             data=>{a=>6378249.145,b=>6356514.966}},
475              
476             'Clarke 1866'=>{name=>'Clarke Ellipsoid of 1866',
477             data=>{a=>6378206.4,b=>6356583.8}},
478              
479             'Danish 1876'=>{name=>'Danish Spheroid of 1876',
480             data=>{a=>3271883.25*1.94903631,i=>300.00}},
481              
482             'Everest 1830'=>{name=>'Everest Spheroid of 1830',
483             data=>{a=>6377276.345,i=>300.8017}},
484              
485             'Everest Modified'=>{name=>'Modified Everest Spheroid',
486             data=>{a=>6377304.063,i=>300.8017}},
487              
488             'Fisher 1960'=>{name=>'Fisher 1960',
489             data=>{a=>6378166,i=>298.3}},
490              
491             'Fisher 1968'=>{name=>'Fisher 1968',
492             data=>{a=>6378150,i=>298.3}},
493              
494             'Hough 1956'=>{name=>'Hough 1956',
495             data=>{a=>6378270,i=>297}},
496              
497             'International (Hayford)'=>{name=>'International - 1924 (Hayford - 1909)',
498             data=>{a=>6378388,i=>297}},
499              
500             'Krassovsky 1938'=>{name=>'Krassovsky 1938',
501             data=>{a=>6378245,i=>298.3},
502             alias=>['Krasovsky 1940']},
503              
504             'NWL-9D'=>{name=>'NWL-9D Ellipsoid',
505             data=>{a=>6378145,i=>298.25},
506             alias=>['WGS-66'=>'World Geodetic System 1966']},
507              
508             'SA69'=>{name=>'South American 1969',
509             data=>{a=>6378160,i=>298.25},
510             alias=>['SA-69']},
511              
512             'SGS85'=>{name=>'Soviet Geodetic System 1985',
513             data=>{a=>6378136,i=>298.257},
514             alias=>['SGS-85']},
515              
516             'WGS72'=>{name=>'World Geodetic System 1972',
517             data=>{a=>6378135,i=>298.26},
518             alias=>['WGS-72']},
519              
520             'WOS'=>{name=>'War Office Spheroid',
521             data=>{a=>6378300.58,i=>296}},
522              
523             'UTM'=>{name=>'Department of the Army Universal Transverse Mercator',
524             data=>{a=>6378249.2,b=>6356515.0}},
525             };
526             }
527              
528             =head2 name2ref
529              
530             Method returns a hash reference (e.g. {a=>6378137,i=>298.257223563}) when passed a valid ellipsoid name (e.g. 'WGS84').
531              
532             my $ref=$obj->name2ref('WGS84')
533              
534             =cut
535              
536             sub name2ref {
537 6     6 1 11 my $self = shift;
538 6         9 my $key = shift;
539 6         14 my $data = $self->data;
540 6         121 return $data->{$key}->{'data'};
541             }
542              
543             1;
544              
545             __END__