File Coverage

lib/Geo/Ellipsoids.pm
Criterion Covered Total %
statement 116 141 82.2
branch 39 56 69.6
condition 4 7 57.1
subroutine 25 30 83.3
pod 22 23 95.6
total 206 257 80.1


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