File Coverage

blib/lib/Astro/App/Satpass2/FormatValue.pm
Criterion Covered Total %
statement 547 583 93.8
branch 238 338 70.4
condition 83 151 54.9
subroutine 92 98 93.8
pod 20 20 100.0
total 980 1190 82.3


line stmt bran cond sub pod time code
1             package Astro::App::Satpass2::FormatValue;
2              
3 11     11   2895 use strict;
  11         25  
  11         661  
4 11     11   61 use warnings;
  11         60  
  11         313  
5              
6 11     11   53 use parent qw{ Astro::App::Satpass2::Copier };
  11         54  
  11         91  
7              
8 11     11   716 use Astro::App::Satpass2::FormatTime;
  11         25  
  11         236  
9 11     11   1938 use Astro::App::Satpass2::FormatValue::Formatter;
  11         57  
  11         414  
10 11     11   111 use Astro::App::Satpass2::Locale qw{ __localize };
  11         31  
  11         661  
11 11         1399 use Astro::App::Satpass2::Utils qw{
12             has_method instance merge_hashes
13             ARRAY_REF CODE_REF HASH_REF
14             @CARP_NOT
15 11     11   89 };
  11         22  
16 11     11   79 use Astro::App::Satpass2::Warner;
  11         32  
  11         458  
17 11     11   74 use Astro::Coord::ECI::Sun 0.059;
  11         322  
  11         389  
18 11     11   76 use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
  11         272  
  11         2860  
19 11     11   94 use Astro::Coord::ECI::Utils 0.059 qw{ deg2rad embodies julianday PI rad2deg TWOPI };
  11         270  
  11         902  
20 11     11   93 use Clone ();
  11         60  
  11         271  
21 11     11   79 use List::Util qw{ max min };
  11         27  
  11         1187  
22 11     11   73 use POSIX qw{ floor };
  11         31  
  11         117  
23 11     11   987 use Scalar::Util 1.26 qw{ isdual reftype };
  11         201  
  11         555  
24 11     11   5592 use Text::Wrap ();
  11         31395  
  11         446  
25              
26             our $VERSION = '0.052';
27              
28 11     11   85 use constant NONE => undef;
  11         40  
  11         916  
29 11     11   74 use constant RE_ALL_DIGITS => qr{ \A [0-9]+ \z }smx;
  11         29  
  11         591  
30 11     11   73 use constant TITLE_GRAVITY_BOTTOM => 'bottom';
  11         40  
  11         557  
31 11     11   72 use constant TITLE_GRAVITY_TOP => 'top';
  11         39  
  11         68740  
32              
33             # Instantiator
34              
35             {
36              
37             sub new {
38 749     749 1 9455 my ( $class, %args ) = @_;
39 749 100       2343 ref $class and $class = ref $class;
40 749         1873 my $self = {};
41 749         1617 bless $self, $class;
42              
43 749         2875 $self->warner( delete $args{warner} );
44              
45 749         1738 foreach my $name ( qw{ data default } ) {
46 1498   100     4072 $self->{$name} = $args{$name} || {};
47             ref $self->{$name}
48             and HASH_REF eq reftype( $self->{$name} )
49             or $self->{warner}->wail(
50 1498 50 33     6821 "Argument '$name' must be a hash reference" );
51             }
52              
53             $self->{desired_equinox_dynamical} =
54 749   50     2853 $args{desired_equinox_dynamical} || 0;
55              
56             $self->{fixed_width} = exists $args{fixed_width} ?
57             $args{fixed_width} :
58 749 100       1767 1;
59              
60 749   50     2237 $self->{overflow} = $args{overflow} || 0;
61              
62             defined( $self->{local_coordinates} = $args{local_coordinates} )
63 749 100       1848 or $self->{local_coordinates} = \&__local_coord_azel_rng;
64             ref $self->{local_coordinates}
65             or $self->{local_coordinates} = $self->can(
66             "__local_coord_$self->{local_coordinates}" )
67             or $self->{warner}->wail(
68 749 50 66     2034 "Unknown local_coordinates $self->{local_coordinates}" );
69             CODE_REF eq ref $self->{local_coordinates}
70             or $self->{warner}->wail(
71 749 50       1819 'Argument local_coordinates must be a code reference ',
72             'or the name of a known coordinate system'
73             );
74              
75             defined( $self->{list_formatter} = $args{list_formatter} )
76 749 100       2598 or $self->{list_formatter} = $self->can( '__list_formatter' );
77             CODE_REF eq ref $self->{list_formatter}
78             or $self->{warner}->wail(
79 749 50       1774 'Argument list_formatter must be a code reference ',
80             'or the name of a known coordinate system'
81             );
82              
83 749         1522 $self->{title} = $args{title};
84              
85             $self->title_gravity( _dor( $args{title_gravity},
86 749         2353 TITLE_GRAVITY_TOP ) );
87              
88             $self->{time_formatter} = $args{time_formatter} ||
89 749   100     2422 'Astro::App::Satpass2::FormatTime';
90             ref $self->{time_formatter}
91 749 100       2147 or $self->{time_formatter} = $self->{time_formatter}->new();
92             instance( $self->{time_formatter},
93             'Astro::App::Satpass2::FormatTime' )
94             or $self->{warner}->wail(
95 749 50       2478 'Argument time_formatter must be an Astro::App::Satpass2::FormatTime'
96             );
97 749         1819 $self->{date_format} = $args{date_format};
98             defined $self->{date_format}
99 749 100       1729 or $self->{date_format} = $self->{time_formatter}->DATE_FORMAT();
100 749         1729 $self->{time_format} = $args{time_format};
101             defined $self->{time_format}
102 749 100       1766 or $self->{time_format} = $self->{time_formatter}->TIME_FORMAT();
103 749 100       1819 if ( exists $args{round_time} ) {
104 731         1606 $self->{round_time} = $args{round_time};
105             } else {
106 18         95 $self->{round_time} = $self->{time_formatter}->ROUND_TIME();
107             }
108              
109 749         1511 $self->{report} = $args{report};
110              
111 749         3972 return $self;
112             }
113              
114             }
115              
116             # Overrides
117              
118             sub clone {
119 282     282 1 5736 my ( $self, @args ) = @_;
120 282         457 my %arg;
121 282 50 33     881 if ( @args == 1 && HASH_REF eq ref $args[0] ) {
122 0         0 %arg = %{ $args[0] };
  0         0  
123             } else {
124 282         621 %arg = @args;
125             }
126 282         462 foreach my $name ( keys %{ $self } ) {
  282         1179  
127             defined $arg{$name}
128 4383 100       9369 or $arg{$name} = $self->{ $name };
129             }
130 282         731 delete $arg{internal};
131 282         1057 return $self->new( %arg );
132             }
133              
134             # Accessors.
135              
136             sub body { # Required for template 'list', which needs to figure
137             # out whether the body is inertial or not.
138 53     53 1 1004 my ( $self ) = @_;
139 53         180 return $self->_get_eci( 'body' );
140             }
141              
142             sub data {
143 0     0 1 0 my ( $self ) = @_;
144 0         0 return $self->_get( 'data' );
145             }
146              
147             # Mutators. These should be kept to a minimum.
148              
149             sub fixed_width {
150 14     14 1 91717 my ( $self, @args ) = @_;
151 14 50       39 if ( @args ) {
152 14         28 $self->{fixed_width} = $args[0];
153 14         42 return $self;
154             } else {
155 0         0 return $self->{fixed_width};
156             }
157             }
158              
159             sub title_gravity {
160 846     846 1 605882 my ( $self, @args ) = @_;
161 846 100       2117 if ( @args ) {
162             is_valid_title_gravity( $args[0] )
163             or $self->{warner}->wail(
164 786 50       2065 "Attribute title_gravity value '$args[0]' invalid"
165             );
166 786         1743 $self->{title_gravity} = $args[0];
167 786         1617 return $self;
168             } else {
169 60         548 return $self->{title_gravity};
170             }
171             }
172              
173             # Transformations
174              
175             sub appulse {
176 49     49 1 33997 my ( $self ) = @_;
177 49         143 return $self->_variant( 'appulse' );
178             }
179              
180             sub bodies {
181 9     9 1 229 my ( $self ) = @_;
182 9 50       41 my $bodies = $self->_get( data => 'bodies' )
183             or return;
184              
185 9         41 my $questionable = $self->_get( data => 'questionable' );
186 9         91 my $sta = $self->_get( data => 'station' );
187 9         74 my $time = $self->_get( data => 'time' );
188 9         55 my $twilight = $self->_get( data => 'twilight' );
189 9 100       101 defined $twilight
190             or $twilight = deg2rad( -6 ); # Civil
191              
192 9         62 my @rslt;
193 9         27 foreach my $body ( @{ $bodies } ) {
  9         35  
194 20 50       79 embodies( $body, 'Astro::Coord::ECI' )
195             or next;
196              
197 20         770 my $data = {
198             body => $body,
199             illumination => _illumination(
200             body => $body,
201             station => $sta,
202             time => $time,
203             twilight => $twilight,
204             ),
205             questionable => $questionable,
206             };
207              
208 20         452 push @rslt, $self->_variant( $data );
209             }
210              
211 9         87 return \@rslt;
212             }
213              
214             sub _elevation {
215 6     6   26 my ( $station, $body, $time ) = @_;
216 6 50       55 defined $time and $body->universal( $time );
217 6         154 return ( $station->azel( $body ) )[1];
218             }
219              
220             sub _illumination {
221 20     20   92 my %arg = @_;
222              
223 20 100       64 embodies( $arg{body}, 'Astro::Coord::ECI::TLE' )
224             or return PASS_EVENT_NONE;
225              
226             defined $arg{time}
227 6 50       211 or $arg{time} = $arg{body}->universal();
228              
229             embodies( $arg{sun}, 'Astro::Coord::ECI' )
230 6 50       47 or $arg{sun} = $arg{body}->get( 'sun' );
231             embodies( $arg{sun}, 'Astro::Coord::ECI' )
232 6 50       526 or $arg{sun} = Astro::Coord::ECI::Sun->new();
233              
234             defined $arg{twilight}
235             or $arg{twilight} = _dor(
236 6 50       208 $arg{body}->get( 'twilight' ),
237             deg2rad( -6 ), # Civil
238             );
239              
240             defined $arg{time}
241 6 50 33     59 and embodies( $arg{station}, 'Astro::Coord::ECI' )
242             # and _elevation( $arg{station}, $arg{body}, $arg{time} ) >= 0
243             or return PASS_EVENT_NONE;
244              
245             $arg{body}->illuminated( $arg{time} )
246 6 50       195 or return PASS_EVENT_SHADOWED;
247              
248             _elevation( $arg{station}, $arg{sun}, $arg{time} ) > $arg{twilight}
249 6 50 33     5836 and _elevation( $arg{station}, $arg{body}, $arg{time} ) >= 0
250             and return PASS_EVENT_DAY;
251              
252 6         1837 return PASS_EVENT_LIT;
253             }
254              
255             sub center {
256 36     36 1 26610 my ( $self ) = @_;
257 36         102 return $self->_variant( 'center' );
258             }
259              
260             sub earth {
261 5     5 1 2225 my ( $self ) = @_;
262 5         14 my $earth = $self->_variant();
263 5         28 $earth->{data}{station} = Astro::Coord::ECI->new()->ecef( 0, 0, 0 );
264 5         557 return $earth;
265             }
266              
267             sub events {
268 31     31 1 39669 my ( $self ) = @_;
269 31         157 return [ map { $self->clone( data => $_ ) } $self->__raw_events() ];
  118         278  
270             }
271              
272             sub tle_events {
273 2     2 1 10 my ( $self ) = @_;
274 2         6 my @rslt;
275              
276 2         6 foreach my $evt ( $self->__raw_events() ) {
277 10 100       73 embodies( $evt->{body}, 'Astro::Coord::ECI::TLE' )
278             or next;
279 8         207 push @rslt, $self->clone( data => $evt );
280             }
281 2         51 return \@rslt;
282             }
283              
284             sub __raw_events {
285 36     36   105 my ( $self ) = @_;
286              
287 36 50       120 my $events = $self->_get( data => 'events' )
288             or return;
289              
290 36 50       199 ARRAY_REF eq ref $events
291             or return;
292              
293 36         107 return @{ $events };
  36         170  
294             }
295              
296             sub reflections {
297 20     20 1 509 my ( $self ) = @_;
298              
299 20 100       71 my $body = $self->_get_tle( 'body' )
300             or return;
301              
302 6 50       232 my $sta = $self->_get_eci( 'station' )
303             or return;
304              
305 6         189 my $time = $self->_get( data => 'time' );
306 6 50       29 defined $time
307             or $time = $body->universal();
308 6 50       36 defined $time or return;
309              
310 6         20 my $illum = $self->_get( data => 'illumination' );
311 6 50       61 defined $illum
312             or $illum = _illumination(
313             body => $body,
314             station => $sta,
315             time => $time,
316             );
317              
318 6 50 33     84 $illum
      33        
      33        
319             and ( PASS_EVENT_LIT == $illum
320             or PASS_EVENT_DAY == $illum )
321             and $body->can_flare( $self->_get( data => 'questionable' ) )
322             or return;
323              
324 0         0 $body->set( horizon => 0 );
325 0         0 my @rslt;
326 0         0 foreach my $info ( $body->reflection( $sta, $time ) ) {
327 0         0 push @rslt, $self->_variant( $info );
328             }
329              
330 0         0 return \@rslt;
331             }
332              
333             sub station {
334 39     39 1 28218 my ( $self ) = @_;
335 39         106 my $station = $self->_variant();
336             ( $station->{data}{body}, $station->{data}{station} ) = (
337 39         83 map { $station->_get( data => $_ ) } qw{ station body } );
  78         148  
338 39         100 return $station;
339             }
340              
341             # Formatters
342              
343             sub list {
344 23     23 1 3455 my ( $self, %arg ) = _arguments( @_ );
345 23         139 return $self->{list_formatter}->( $self, %arg );
346             }
347              
348             sub __list_formatter {
349 3     3   8 my ( $self, @arg ) = _arguments( @_ );
350 3         6 my $body;
351 3 100       9 my $type = ( $body = $self->body() ) ?
352             $body->__list_type() :
353             'inertial';
354 3         68 my $code;
355 3 50       27 $code = $self->can( "__list_formatter_$type" )
356             and return $code->( $self, @arg );
357 3   33     15 $code = $self->can( "__list_formatter_args_$type" ) ||
358             $self->can( '__list_formatter_args_inertial' );
359 3         11 my $rslt = join ' ', map { $self->$_( @arg ) } $code->( $self );
  13         66  
360 3         20 $rslt =~ s/ \s+ \z //smx;
361 3         13 return $rslt;
362             }
363              
364             sub __list_formatter_args_fixed {
365 1     1   5 return ( qw{ oid name latitude longitude altitude } );
366             }
367              
368             sub __list_formatter_args_inertial {
369 2     2   9 return ( qw{ oid name epoch period } );
370             }
371              
372             sub local_coord {
373 205     205 1 8491 my ( $self, %arg ) = _arguments( @_ );
374 205         1157 return $self->{local_coordinates}->( $self, %arg );
375             }
376              
377             sub __local_coord_az_rng {
378 1     1   4 my ( $self, @arg ) = _arguments( @_ );
379 1         15 return join ' ', $self->azimuth( @arg, { bearing => 2 } ),
380             $self->range( @arg );
381             }
382              
383             sub __local_coord_azel {
384 1     1   5 my ( $self, @arg ) = _arguments( @_ );
385 1         6 return join ' ', $self->elevation( @arg ),
386             $self->azimuth( @arg, { bearing => 2 } );
387             }
388              
389             sub __local_coord_azel_rng {
390 3     3   9 my ( $self, @arg ) = _arguments( @_ );
391 3         12 return join ' ', $self->elevation( @arg ),
392             $self->azimuth( @arg, { bearing => 2 } ),
393             $self->range( @arg );
394             }
395              
396             sub __local_coord_equatorial {
397 1     1   8 my ( $self, @arg ) = _arguments( @_ );
398 1         6 return join ' ', $self->right_ascension( @arg ),
399             $self->declination( @arg );
400             }
401              
402             sub __local_coord_equatorial_rng {
403 1     1   5 my ( $self, @arg ) = _arguments( @_ );
404 1         5 return join ' ', $self->right_ascension( @arg ),
405             $self->declination( @arg ),
406             $self->range( @arg );
407             }
408              
409             # The %dimensions hash defines physical dimensions and the
410             # allowable units for each. The keys of this hash are the names of
411             # physical dimensions (e.g. 'length', 'mass', 'volume', and so
412             # on), and the values are hashes defining the dimension.
413             #
414             # Each dimension definition hash must have the following keys:
415             #
416             # align_left => boolean
417             # This optional key, if true, specifies that the value is to
418             # be aligned to the left in its field. This value can be
419             # overridden in the {define} key, or when the formatter is
420             # called.
421             #
422             # default => the name of the default units for the dimension. This
423             # value must appear as a key in the define hash (see below).
424             # This default can be overridden by a given format effector.
425             #
426             # define => a hash defining the legal units for the dimension. The
427             # keys are the names of the units (e.g. for length
428             # 'kilometers', 'meters', 'miles', 'feet'). The value is a
429             # hash containing zero or more of the following keys:
430             #
431             # alias => name
432             # This optional key specifies that the name is just an
433             # alias for another key, which must exist in the define
434             # hash. No other keys need be specified.
435             #
436             # align_left => boolean
437             # This optional key, if true, specifies that the value is
438             # to be aligned to the left of its field. It can be
439             # overridden by a value specified when the formatter is
440             # called.
441             #
442             # factor => number
443             # A number to multiply the value by to do the conversion.
444             #
445             # formatter => name
446             # This optional key specifies the name of the formatter
447             # routine to use instead of the normal one.
448             #
449             # method => _name
450             # This optional key specifies a method to call. The method
451             # is passed the value being formatted, and the method's
452             # return becomes the new value to format. If both {factor}
453             # and {method} are specified, {method} is done first.
454             #
455             # formatter => name
456             # This key specifies the formatter to use for the units. It
457             # can be overridden in the {define} key.
458              
459             my %dimensions = (
460              
461             almanac_pseudo_units => {
462             default => 'description',
463             define => {
464             event => {},
465             detail => {
466             formatter => '_format_integer',
467             },
468             description => {},
469             },
470             formatter => '_format_string',
471             },
472              
473             angle_units => {
474             align_left => 0,
475             default => 'degrees',
476             define => {
477             bearing => {
478             align_left => 1,
479             formatter => '_format_bearing',
480             },
481             decimal => {
482             alias => 'degrees',
483             },
484             degrees => {
485             factor => 90/atan2( 1, 0 ),
486             },
487             radians => {},
488             phase => {
489             align_left => 1,
490             formatter => '_format_phase',
491             },
492             right_ascension => {
493             formatter => '_format_right_ascension',
494             },
495             },
496             formatter => '_format_number',
497             },
498              
499             dimensionless => {
500             default => 'unity',
501             define => {
502             percent => {
503             factor => 100,
504             },
505             unity => {},
506             },
507             formatter => '_format_number',
508             },
509              
510             duration => {
511             default => 'composite',
512             define => {
513             composite => {
514             formatter => '_format_duration',
515             },
516             seconds => {},
517             minutes => {
518             factor => 1/60,
519             },
520             hours => {
521             factor => 1/3600,
522             },
523             days => {
524             factor => 1/86400,
525             },
526             },
527             formatter => '_format_number',
528             },
529              
530             event_pseudo_units => {
531             default => 'localized',
532             define => {
533             localized => {},
534             integer => {
535             formatter => '_format_integer',
536             },
537             string => {},
538             },
539             formatter => '_format_event',
540             },
541              
542             integer_pseudo_units => {
543             align_left => 0,
544             default => 'integer',
545             define => {
546             integer => {},
547             },
548             formatter => '_format_integer',
549             },
550              
551             length => {
552             align_left => 0,
553             default => 'kilometers',
554             define => {
555             kilometers => {},
556             km => {},
557             meters => {
558             factor => 1000,
559             },
560             m => {
561             alias => 'meters',
562             },
563             miles => {
564             factor => 0.62137119,
565             },
566             mi => {
567             alias => 'miles',
568             },
569             feet => {
570             factor => 3280.8399,
571             },
572             ft => {
573             alias => 'feet',
574             },
575             },
576             formatter => '_format_number',
577             },
578              
579             number => { # Just for consistency's sake
580             align_left => 0,
581             default => 'number',
582             define => {
583             number => {},
584             },
585             formatter => '_format_number',
586             },
587              
588             scientific => { # Just for consistency's sake
589             align_left => 0,
590             default => 'scientific',
591             define => {
592             scientific => {},
593             },
594             formatter => '_format_number_scientific',
595             },
596              
597             string => { # for tle, to prevent munging data. ONLY
598             # 'string' is to be defined.
599             default => 'string',
600             define => {
601             string => {},
602             },
603             formatter => '_format_string',
604             },
605              
606             string_pseudo_units => {
607             default => 'string',
608             define => {
609             lower_case => {
610             formatter => '_format_lower_case',
611             },
612             string => {},
613             title_case => {
614             formatter => '_format_title_case',
615             },
616             upper_case => {
617             formatter => '_format_upper_case',
618             },
619             },
620             formatter => '_format_string',
621             },
622              
623             time_units => {
624             default => 'local',
625             define => {
626             days_since_epoch => {
627             factor => 1/86400,
628             formatter => '_format_number',
629             method => '_subtract_epoch',
630             },
631             gmt => {
632             gmt => 1,
633             },
634             julian => {
635             formatter => '_format_number',
636             method => '_julian_day',
637             },
638             local => {},
639             universal => {
640             alias => 'gmt',
641             },
642             z => {
643             alias => 'gmt',
644             },
645             zulu => {
646             alias => 'gmt',
647             },
648             },
649             formatter => '_format_time',
650             },
651              
652             );
653              
654             # The following was for a utility script to generate documentation for
655             # the dimensions.
656             #
657             # sub __get_dimension_data {
658             # my ( $class, $name ) = @_;
659             # return $dimensions{$name};
660             # }
661              
662             # The following hash is used for generating formatter methods, as
663             # a way of avoiding the replication of common code. The keys are
664             # the method names, and the values are hashes which specify the
665             # method to generate. If the named method already exists, it is
666             # not replaced.
667             #
668             # The hash specifying each method contains the following keys,
669             # which are all requited unless the documentation for the key says
670             # otherwise.
671             #
672             # {chain} - An optional code reference which may (but need not)
673             # expand the formatter to produce multiple representations of
674             # the same value. It takes the arguments ( $self, $name,
675             # $value, $arg ) where $self is the invocant, $name is the
676             # name of the formatter method, $value is the value being
677             # formatted, and $arg is the formatter arguments, which have
678             # already had the defaults applied. It returns at least one
679             # argument hash. If it returns more than one, the same value
680             # is formatted using each set of arguments, with the results
681             # made into a single string using join( ' ', ... ). The
682             # returned argument sets MUST keep the same field width for
683             # the same arguments.
684             #
685             # This is used only for azimuth(), to process the 'bearing'
686             # argument.
687             #
688             # {default} - A hash specifying all legal arguments, and their
689             # default values. You can specify undef to make the argument
690             # legal but give it no value (i.e. to pick up the value from
691             # somewhere else).
692             #
693             # {dimension} - A hash specifying the dimension of the value to be
694             # formatted. This must contain a {dimension} key specifying
695             # the name of the dimension, and may contain a {units} value
696             # overriding the default units.
697             #
698             # {fetch} - A code reference which returns the value to be
699             # formatted. It will be passed arguments ( $self, $name, $arg
700             # ), where $self is the invocant, $name is the name of the
701             # formatter method, and $arg is a refernce to the arguments
702             # hash, which has already had _apply_defaults() called on it.
703             # This code is _not_ called if the invocant was initialized
704             # with title => 1.
705             #
706             # {locale} - A hash specifying last-ditch localization
707             # information. The keys are locale, the formatter name
708             # (yes, this is a duplicate) and the item name.
709              
710             my %formatter_data = ( # For generating formatters
711              
712             almanac => {
713             default => {
714             width => 40,
715             },
716             dimension => {
717             dimension => 'almanac_pseudo_units',
718             },
719             fetch => sub {
720             my ( $self, undef, $arg ) = @_; # $name unused
721             my $field = $arg->{units} ||= 'description';
722             return $self->_get( data => almanac => $field );
723             },
724             },
725              
726             altitude => {
727             default => {
728             places => 1,
729             width => 7,
730             },
731             dimension => {
732             dimension => 'length',
733             },
734             fetch => sub {
735             my ( $self ) = @_; # $name, $arg unused
736             my $value;
737             if ( my $body = $self->_get_eci( 'body' ) ) {
738             $value = ( $body->geodetic() )[2];
739             }
740             return $value;
741             },
742             },
743              
744             angle => {
745             default => {
746             places => 1,
747             width => 5,
748             },
749             dimension => {
750             dimension => 'angle_units',
751             },
752             fetch => sub {
753             my ( $self ) = @_; # $name, $arg unused
754             return $self->_get( data => 'angle' );
755             },
756             },
757              
758             apoapsis => {
759             default => {
760             as_altitude => 1,
761             places => 0,
762             width => 6,
763             },
764             dimension => {
765             dimension => 'length',
766             },
767             fetch => sub {
768             my ( $self, $name, $arg ) = @_;
769              
770             my $body;
771             $body = $self->_get_eci( 'body' )
772             and $body->can( $name )
773             or return NONE;
774              
775             my $value = $body->$name();
776              
777             if ( $arg->{as_altitude} ) {
778             $body->can( 'semimajor' )
779             or return NONE;
780             $value -= $body->get( 'semimajor' );
781             }
782              
783             return $value;
784             },
785             },
786              
787             # apogee => duplicated from apoapsis, below
788              
789             argument_of_perigee => {
790             default => {
791             places => 4,
792             width => 9,
793             },
794             dimension => {
795             dimension => 'angle_units',
796             },
797             fetch => sub {
798             my ( $self ) = @_; # $name, $arg unused
799             return $self->_get_tle_attr( body => 'argumentofperigee' );
800             },
801             },
802              
803             ascending_node => {
804             default => {
805             places => 2,
806             width => 11,
807             },
808             dimension => {
809             dimension => 'angle_units',
810             units => 'right_ascension',
811             },
812             fetch => sub {
813             my ( $self ) = @_; # $name, $arg unused
814             return $self->_get_tle_attr( body => 'ascendingnode' );
815             },
816             },
817              
818             azimuth => {
819             chain => \&__chain_bearing,
820             default => {
821             bearing => 0,
822             places => 1,
823             width => 5,
824             },
825             dimension => {
826             dimension => 'angle_units',
827             },
828             fetch => sub {
829             my ( $self ) = @_; # $name, $arg unused
830             my $body = $self->_get_eci( 'body' )
831             or return NONE;
832             my $station = $self->_get_eci( 'station' )
833             or return NONE;
834             return ( $station->azel( $body ) )[0];
835             },
836             },
837              
838             b_star_drag => {
839             default => {
840             places => 4,
841             width => 11,
842             },
843             dimension => {
844             dimension => 'scientific',
845             },
846             fetch => sub {
847             my ( $self ) = @_; # $name, $arg unused
848             return $self->_get_tle_attr( body => 'bstardrag' );
849             },
850             },
851              
852             classification => {
853             default => {
854             width => 1,
855             },
856             dimension => {
857             dimension => 'string_pseudo_units',
858             },
859             fetch => sub {
860             my ( $self ) = @_; # $name, $arg unused
861             return $self->_get_tle_attr( body => 'classification' );
862             },
863             },
864              
865             date => {
866             default => {
867             delta => 0,
868             format => undef, # Just to get it looked at
869             gmt => undef,
870             places => 5,
871             round_time => undef, # Just to get it looked at
872             width => '',
873             },
874             dimension => {
875             dimension => 'time_units',
876             format => [ 'date_format' ],
877             },
878             fetch => sub {
879             my ( $self, undef, $arg ) = @_; # $name not used
880             defined( my $value = $self->_get( data => 'time' ) )
881             or return NONE;
882             return $value + $arg->{delta};
883             },
884             },
885              
886             declination => {
887             default => {
888             places => 1,
889             width => 5,
890             },
891             dimension => {
892             dimension => 'angle_units',
893             },
894             fetch => sub {
895             my ( $self ) = @_; # $name, $arg unused
896             my $body = $self->_get_eci( 'body' )
897             or return NONE;
898             my $station = $self->_get_eci( 'station' )
899             or return NONE;
900             return ( $self->_get_precessed_coordinates(
901             equatorial => $body, $station ) )[ 1 ];
902             },
903             },
904              
905             eccentricity => {
906             default => {
907             places => 5,
908             width => 8,
909             },
910             dimension => {
911             dimension => 'dimensionless',
912             },
913             fetch => sub {
914             my ( $self ) = @_; # $name, $arg unused
915             return $self->_get_tle_attr( body => 'eccentricity' );
916             },
917             },
918              
919             effective_date => {
920             default => {
921             format => undef, # Just to get it looked at
922             gmt => undef,
923             places => '',
924             round_time => undef, # Just to get it looked at
925             width => '',
926             },
927             dimension => {
928             dimension => 'time_units',
929             format => [ 'date_format', 'time_format' ],
930             },
931             fetch => sub {
932             my ( $self ) = @_; # $name, $arg unused
933             return $self->_get_tle_attr( body => 'effective' );
934             },
935             },
936              
937             element_number => {
938             default => {
939             align_left => 0,
940             width => 4,
941             },
942             dimension => {
943             dimension => 'string',
944             },
945             fetch => sub {
946             my ( $self ) = @_; # $name, $arg unused
947             my $value = $self->_get_tle_attr( body => 'elementnumber' );
948             defined $value and $value =~ s/ \A \s+ //sxm;
949             return $value;
950             },
951             },
952              
953             elevation => {
954             default => {
955             places => 1,
956             width => 5,
957             },
958             dimension => {
959             dimension => 'angle_units',
960             },
961             fetch => sub {
962             my ( $self ) = @_; # $name, $arg unused
963             my $body = $self->_get_eci( 'body' )
964             or return NONE;
965             my $station = $self->_get_eci( 'station' )
966             or return NONE;
967             return ( $station->azel( $body ) )[1];
968             },
969             },
970              
971             ephemeris_type => {
972             default => {
973             width => 1,
974             },
975             dimension => {
976             dimension => 'string_pseudo_units',
977             },
978             fetch => sub {
979             my ( $self ) = @_; # $name, $arg unused
980             return $self->_get_tle_attr( body => 'ephemeristype' );
981             },
982             },
983              
984             epoch => {
985             default => {
986             format => undef, # Just to get it looked at
987             gmt => undef,
988             places => '',
989             round_time => undef, # Just to get it looked at
990             width => '',
991             },
992             dimension => {
993             dimension => 'time_units',
994             format => [ 'date_format', 'time_format' ],
995             },
996             fetch => sub {
997             my ( $self ) = @_; # $name, $arg unused
998             return $self->_get_tle_attr( body => 'epoch' );
999             },
1000             },
1001              
1002             event => {
1003             default => {
1004             width => 5,
1005             },
1006             dimension => {
1007             dimension => 'event_pseudo_units',
1008             },
1009             fetch => sub {
1010             my ( $self, $name ) = @_; # $arg unused
1011             defined( my $value = $self->_get( data => $name ) )
1012             or return NONE;
1013             return $value;
1014             },
1015             },
1016              
1017             first_derivative => {
1018             default => {
1019             places => 10,
1020             width => 17,
1021             },
1022             dimension => {
1023             dimension => 'angle_units',
1024             formatter => '_format_number_scientific',
1025             },
1026             fetch => sub {
1027             my ( $self ) = @_; # $name, $arg unused
1028             return $self->_get_tle_attr( body => 'firstderivative' );
1029             },
1030             },
1031              
1032             fraction_lit => {
1033             default => {
1034             places => 2,
1035             width => 4,
1036             },
1037             dimension => {
1038             dimension => 'dimensionless',
1039             },
1040             fetch => sub {
1041             my ( $self ) = @_; # $name, $arg unused
1042             my $body = $self->_get_eci( 'body' )
1043             or return NONE;
1044             $body->can( 'phase' )
1045             or return NONE;
1046             return ( $body->phase() )[1];
1047             },
1048             },
1049              
1050             illumination => {
1051             default => {
1052             width => 5,
1053             },
1054             dimension => {
1055             dimension => 'event_pseudo_units',
1056             },
1057             fetch => sub {
1058             my ( $self, $name ) = @_; # $arg unused
1059             my $value;
1060             defined( $value = $self->_get( data => $name ) )
1061             and $value ne ''
1062             and return $value;
1063             return NONE;
1064             },
1065             },
1066              
1067             inclination => {
1068             default => {
1069             places => 4,
1070             width => 8,
1071             },
1072             dimension => {
1073             dimension => 'angle_units',
1074             },
1075             fetch => sub {
1076             my ( $self ) = @_; # $name, $arg unused
1077             return $self->_get_tle_attr( body => 'inclination' );
1078             },
1079             },
1080              
1081             inertial => {
1082             default => {
1083             width => 1,
1084             },
1085             dimension => {
1086             dimension => 'integer_pseudo_units',
1087             },
1088             fetch => sub {
1089             my ( $self ) = @_; # $name, $arg unused
1090             my $body = $self->_get_eci( 'body' )
1091             or return NONE;
1092             return $body->get( 'inertial' ) ? 1 : 0;
1093             },
1094             },
1095              
1096             international => {
1097             default => {
1098             align_left => 1,
1099             width => 8,
1100             },
1101             dimension => {
1102             dimension => 'string_pseudo_units',
1103             },
1104             fetch => sub {
1105             my ( $self ) = @_; # $name, $arg unused
1106             return $self->_get_tle_attr( body => 'international' );
1107             },
1108             },
1109              
1110             latitude => {
1111             default => {
1112             places => 4,
1113             width => 8,
1114             },
1115             dimension => {
1116             dimension => 'angle_units',
1117             },
1118             fetch => sub {
1119             my ( $self ) = @_; # $name, $arg unused
1120             my $body = $self->_get_eci( 'body' )
1121             or return NONE;
1122             return ( $body->geodetic() )[0];
1123             },
1124             },
1125              
1126             longitude => {
1127             default => {
1128             places => 4,
1129             width => 9,
1130             },
1131             dimension => {
1132             dimension => 'angle_units',
1133             },
1134             fetch => sub {
1135             my ( $self ) = @_; # $name, $arg unused
1136             my $body = $self->_get_eci( 'body' )
1137             or return NONE;
1138             return ( $body->geodetic() )[1];
1139             },
1140             },
1141              
1142             magnitude => {
1143             default => {
1144             align_left => 0,
1145             places => 1,
1146             width => 4,
1147             },
1148             dimension => {
1149             dimension => 'number',
1150             },
1151             fetch => sub {
1152             my ( $self ) = @_; # $name, $arg unused
1153             my $mag;
1154             defined( $mag = $self->_get( data => 'magnitude' ) )
1155             and return $mag;
1156              
1157             my ( $body, $sta );
1158             $body = $self->_get_eci( 'body' )
1159             and $body->can( 'magnitude' )
1160             and $sta = $self->_get_eci( 'station' )
1161             or return NONE;
1162             if ( defined( my $time = $self->_get( data => 'time' ) ) ) {
1163             $body->universal( $time );
1164             } elsif ( ! defined( $body->universal() ) ) {
1165             return NONE;
1166             }
1167             return $body->magnitude( $sta );
1168             },
1169             },
1170              
1171             maidenhead => {
1172             default => {
1173             width => 6,
1174             places => undef,
1175             },
1176             dimension => {
1177             dimension => 'string_pseudo_units',
1178             },
1179             fetch => sub {
1180             my ( $self, undef, $arg ) = @_; # $name unused
1181             my $body = $self->_get_eci( 'body' )
1182             or return NONE;
1183             my $places = defined $arg->{places} ?
1184             $arg->{places} :
1185             $arg->{width} ?
1186             floor( $arg->{width} / 2 ) :
1187             3;
1188             return ( $body->maidenhead( $places ) )[0];
1189             },
1190             },
1191              
1192             mean_anomaly => {
1193             default => {
1194             places => 4,
1195             width => 9,
1196             },
1197             dimension => {
1198             dimension => 'angle_units',
1199             },
1200             fetch => sub {
1201             my ( $self ) = @_; # $name, $arg unused
1202             return $self->_get_tle_attr( body => 'meananomaly' );
1203             },
1204             },
1205              
1206             mean_motion => {
1207             default => {
1208             places => 10,
1209             width => 12,
1210             },
1211             dimension => {
1212             dimension => 'angle_units',
1213             },
1214             fetch => sub {
1215             my ( $self ) = @_; # $name, $arg unused
1216             return $self->_get_tle_attr( body => 'meanmotion' );
1217             },
1218             },
1219              
1220             mma => {
1221             default => {
1222             width => 3,
1223             },
1224             dimension => {
1225             dimension => 'string_pseudo_units',
1226             },
1227             fetch => sub {
1228             my ( $self ) = @_; # $name, $arg unused
1229             return $self->_get( data => 'mma' );
1230             },
1231             },
1232              
1233             name => {
1234             default => {
1235             width => 24, # Per http://celestrak.com/NORAD/documentation/tle-fmt.asp
1236             },
1237             dimension => {
1238             dimension => 'string_pseudo_units',
1239             },
1240             fetch => sub {
1241             my ( $self, undef, $arg ) = @_; # $name unused
1242             my $body = $self->_get_eci( 'body' )
1243             or return NONE;
1244             my $value;
1245             defined( $value = $body->get( 'name' ) )
1246             and return $value;
1247             defined $arg->{missing}
1248             and 'oid' eq $arg->{missing}
1249             and return $body->get( 'id' );
1250             return NONE;
1251             },
1252             },
1253              
1254             oid => {
1255             default => {
1256             width => 6,
1257             },
1258             dimension => {
1259             dimension => 'string_pseudo_units',
1260             },
1261             fetch => sub {
1262             my ( $self, undef, $arg ) = @_; # $name unused
1263             my $body = $self->_get_eci( 'body' )
1264             or return NONE;
1265             defined( my $value = $body->get( 'id' ) )
1266             or return NONE;
1267             not defined $arg->{align_left}
1268             and $arg->{align_left} = $value !~ RE_ALL_DIGITS;
1269             return $value;
1270             },
1271             },
1272              
1273             operational => {
1274             default => {
1275             width => 1,
1276             },
1277             dimension => {
1278             dimension => 'string_pseudo_units',
1279             },
1280             fetch => sub {
1281             my ( $self ) = @_; # $nane, $arg unused
1282             return $self->_get_tle_attr( body => 'status' );
1283             },
1284             },
1285              
1286             # periapsis => duplicated from apoapsis, below
1287              
1288             # perigee => duplicated from apoapsis, below
1289              
1290             period => {
1291             default => {
1292             places => 0,
1293             width => 12,
1294             },
1295             dimension => {
1296             dimension => 'duration',
1297             },
1298             fetch => sub {
1299             my ( $self ) = @_; # $name, $arg unused
1300             my $body = $self->_get_eci( 'body' )
1301             or return NONE;
1302             $body->can( 'period' )
1303             or return NONE;
1304             return $body->period();
1305             },
1306             },
1307              
1308             phase => {
1309             default => {
1310             places => 0,
1311             width => 4,
1312             },
1313             dimension => {
1314             dimension => 'angle_units',
1315             },
1316             fetch => sub {
1317             my ( $self ) = @_; # $name, $arg unused
1318             my $body = $self->_get_eci( 'body' )
1319             or return NONE;
1320             $body->can( 'phase' )
1321             or return NONE;
1322             return ( $body->phase() )[0];
1323             },
1324             },
1325              
1326             range => {
1327             default => {
1328             places => 1,
1329             width => 10,
1330             },
1331             dimension => {
1332             dimension => 'length',
1333             },
1334             fetch => sub {
1335             my ( $self ) = @_; # $name, $arg unused
1336             my $body = $self->_get_eci( 'body' )
1337             or return NONE;
1338             my $station = $self->_get_eci( 'station' )
1339             or return NONE;
1340             return ( $station->azel( $body ) )[2];
1341             },
1342             },
1343              
1344             revolutions_at_epoch => {
1345             default => {
1346             align_left => 0,
1347             width => 6,
1348             },
1349             dimension => {
1350             dimension => 'string',
1351             },
1352             fetch => sub {
1353             my ( $self ) = @_; # $name, $arg unused
1354             my $value = $self->_get_tle_attr( body => 'revolutionsatepoch' );
1355             defined $value and $value =~ s/ \A \s+ //sxm;
1356             return $value;
1357             },
1358             },
1359              
1360             right_ascension => {
1361             default => {
1362             places => 0,
1363             width => 8,
1364             },
1365             dimension => {
1366             dimension => 'angle_units',
1367             units => 'right_ascension',
1368             },
1369             fetch => sub {
1370             my ( $self ) = @_; # $name, $arg unused
1371             my $body = $self->_get_eci( 'body' )
1372             or return NONE;
1373             my $station = $self->_get_eci( 'station' )
1374             or return NONE;
1375             return ( $self->_get_precessed_coordinates(
1376             equatorial => $body, $station ) )[ 0 ];
1377             },
1378             },
1379              
1380             second_derivative => {
1381             default => {
1382             places => 10,
1383             width => 17,
1384             },
1385             dimension => {
1386             dimension => 'angle_units',
1387             formatter => '_format_number_scientific',
1388             },
1389             fetch => sub {
1390             my ( $self ) = @_; # $name, $arg unused
1391             return $self->_get_tle_attr( body => 'secondderivative' );
1392             },
1393             },
1394              
1395             semimajor => {
1396             default => {
1397             places => 0,
1398             width => 6,
1399             },
1400             dimension => {
1401             dimension => 'length',
1402             },
1403             fetch => sub {
1404             my ( $self, $name ) = @_; # $arg unused
1405             my $body = $self->_get_eci( 'body' )
1406             or return NONE;
1407             $body->can( $name )
1408             or return NONE;
1409             return $body->$name();
1410             },
1411             },
1412              
1413             # semiminor => duplicated from semimajor, below
1414              
1415             status => {
1416             default => {
1417             width => 60,
1418             },
1419             dimension => {
1420             dimension => 'string_pseudo_units',
1421             },
1422             fetch => sub {
1423             my ( $self ) = @_; # $name, $arg unused
1424             return $self->_get( data => 'status' );
1425             },
1426             },
1427              
1428             time => {
1429             default => {
1430             delta => 0,
1431             format => undef, # Just to get it looked at
1432             gmt => undef,
1433             places => 5,
1434             round_time => undef, # Just to get it looked at
1435             width => '',
1436             },
1437             dimension => {
1438             dimension => 'time_units',
1439             format => [ 'time_format' ],
1440             },
1441             fetch => sub {
1442             my ( $self, undef, $arg ) = @_; # $name unused
1443             defined( my $value = $self->_get( data => 'time' ) )
1444             or return NONE;
1445             return $value + $arg->{delta};
1446             },
1447             },
1448              
1449             tle => {
1450             default => {},
1451             dimension => {
1452             dimension => 'string',
1453             },
1454             fetch => sub {
1455             my ( $self ) = @_; # $name, $arg unused
1456             return $self->_get_tle_attr( body => 'tle' );
1457             },
1458             },
1459              
1460             type => {
1461             default => {
1462             align_left => 1,
1463             width => 3,
1464             },
1465             dimension => {
1466             dimension => 'string_pseudo_units',
1467             },
1468             fetch => sub {
1469             my ( $self ) = @_; # $name, $arg unused
1470             return $self->_get( data => 'type' );
1471             },
1472             },
1473              
1474             );
1475              
1476             foreach my $fmtr_name ( keys %formatter_data ) {
1477             $formatter_data{$fmtr_name}{name} = $fmtr_name;
1478             }
1479              
1480             sub _clone_formatter {
1481 44     44   115 my ( $from, $to ) = @_;
1482 44         979 %{ $formatter_data{$to} } = %{ $formatter_data{$from} };
  44         207  
  44         118  
1483 44         104 $formatter_data{$to}{name} = $to;
1484 44         82 return;
1485             }
1486              
1487             _clone_formatter( apoapsis => 'apogee' );
1488             _clone_formatter( apoapsis => 'periapsis' );
1489             _clone_formatter( apoapsis => 'perigee' );
1490             _clone_formatter( semimajor => 'semiminor' );
1491              
1492             sub _fetch {
1493 1868     1868   4331 my ( $self, $info, $name, $arg ) = @_;
1494              
1495 1868 100       5047 if ( ! $self->{internal}{time_set} ) {
1496 356 100       920 if ( defined( my $time = $self->_get( data => 'time' ) ) ) {
1497 329         755 foreach my $key ( qw{ body station } ) {
1498 658 100       75111 my $obj = $self->_get_eci( $key )
1499             or next;
1500 606         16113 $obj->universal( $time );
1501             }
1502             }
1503 356         8204 $self->{internal}{time_set} = 1;
1504             }
1505 1868         6238 return $info->{fetch}->( $self, $name, $arg );
1506             }
1507              
1508             sub __list_formatter_names {
1509 0     0   0 return ( keys %formatter_data );
1510             }
1511              
1512             sub __get_formatter_data {
1513 11     11   29 my ( undef, $name ) = @_; # Invocant unused
1514 11 50       107 defined $name
1515             or return ( values %formatter_data );
1516 0         0 return $formatter_data{$name};
1517             }
1518              
1519             # Used when the normal reporting mechanism is unavailable.
1520             sub _confess {
1521 0     0   0 my ( @arg ) = @_;
1522 0         0 require Carp;
1523 0         0 Carp::confess( @arg );
1524             }
1525              
1526             # Note that this implementation of add_formatter_method() modifies our
1527             # name space by adding a stub method that dispatches to the
1528             # object-specific code, or throws an error if there is none. The
1529             # previous implementation used AUTOLOAD, but this had problems on most
1530             # smokers involving calls to DESTROY(). I was never able to duplicate
1531             # these, and rather than try to figure out how to handle any and all
1532             # Perl-reserved subs, I decided to switch to an implementation which,
1533             # while still fairly grody, did not use AUTOLOAD.
1534             {
1535             my $fmtr_class = 'Astro::App::Satpass2::FormatValue::Formatter';
1536             my %defined_here;
1537             sub add_formatter_method {
1538 449     449 1 1017 my ( $self, @formatters ) = @_;
1539 449         965 foreach my $fmtr_obj ( @formatters ) {
1540             instance( $fmtr_obj, $fmtr_class )
1541             or $self->{warner}->wail(
1542 3 50       10 "Formatters must be instances of $fmtr_class" );
1543 3         17 my $name = $fmtr_obj->name();
1544             $self->can( $name )
1545             and not $defined_here{$name}
1546             and $self->{warner}->wail(
1547 3 50 66     21 "Formatter $name can not override built-in format" );
1548             $self->{formatter_method}{$name}
1549             and $self->{warner}->wail(
1550 3 50       18 "Formatter $name can not replace previously-set formatter of same name" );
1551 3         9 $self->{formatter_method}{$name} = $fmtr_obj;
1552 3 100       12 unless ( $defined_here{$name} ) {
1553 1         3 $defined_here{$name} = 1;
1554 11     11   113 no strict qw{ refs };
  11         54  
  11         9517  
1555             *$name = sub {
1556 1     1   2090 my ( $self ) = @_;
1557             my $obj = $self->{formatter_method}{$name}
1558 1 50       7 or $self->{warner}->wail( "No such formatter as '$name'" );
1559 1         2 goto &{ $obj->code() };
  1         5  
1560 1         9 };
1561             }
1562             }
1563 449         943 return $self;
1564             }
1565             }
1566              
1567             sub __make_formatter_code {
1568 540     540   938 my ( $class, $fmtr ) = @_;
1569              
1570 540 50       1191 HASH_REF eq ref $fmtr
1571             or _confess( 'The argument must be a HASH reference' );
1572             defined( my $fmtr_name = $fmtr->{name} )
1573 540 50       1107 or _confess( 'The {name} must be defined' );
1574              
1575             # Validate the dimension information
1576             $fmtr->{dimension}
1577 540 50       1032 or _confess(
1578             "'$fmtr_name' does not specify a {dimension} hash" );
1579             defined( my $dim_name = $fmtr->{dimension}{dimension} )
1580 540 50       1055 or _confess(
1581             "'$fmtr_name' does not specify the dimension" );
1582 540 50       1063 $dimensions{$dim_name}
1583             or _confess( "'$fmtr_name' specifies invalid dimension '$dim_name'" );
1584 540 50       1123 if ( defined( my $dflt = $fmtr->{dimension}{default} ) ) {
1585 0 0       0 defined $dimensions{$dim_name}{define}{$dflt}
1586             or _confess( "'$fmtr_name' specifies invalid default units '$dflt'" );
1587             }
1588              
1589             # If the dimension is 'time_units' we need to validate that the
1590             # format key is defined and valid
1591 540 100       967 if ( 'time_units' eq $dim_name ) {
1592 44 50       171 if ( ARRAY_REF eq ref $fmtr->{dimension}{format} ) {
1593 44         81 foreach my $entry ( @{ $fmtr->{dimension}{format} } ) {
  44         110  
1594 66 50       206 $class->_valid_time_format_name( $entry )
1595             or _confess(
1596             "In '$fmtr_name', '$entry' is not a valid format" );
1597             }
1598             $fmtr->{default}{format} = sub {
1599 403     403   937 my ( $self ) = @_;
1600 403         1151 return $self->_get_date_format_data( $fmtr_name, format => $fmtr );
1601 44         227 };
1602             $fmtr->{default}{width} = sub {
1603 377     377   830 my ( $self ) = @_;
1604 377         1047 return $self->_get_date_format_data( $fmtr_name, width => $fmtr );
1605 44         149 };
1606             } else {
1607 0         0 _confess(
1608             "'$fmtr_name' must specify a {format} key in {dimension}" );
1609             }
1610             $fmtr->{default}{round_time} = sub {
1611 408     408   950 my ( $self ) = @_;
1612 408         1121 return $self->{round_time};
1613 44         211 };
1614             }
1615              
1616             # Validate the fetch information
1617             CODE_REF eq ref $fmtr->{fetch}
1618 540 50       1060 or _confess(
1619             "In '$fmtr_name', {fetch} is not a code reference" );
1620              
1621             return sub {
1622 2526     2526   2276860 my ( $self, %arg ) = _arguments( @_ );
1623              
1624 2526         8291 $self->_apply_defaults( \%arg, $fmtr );
1625              
1626 2526 100 66     11302 my $value = ( $self->{title} || defined $arg{literal} ) ?
1627             NONE :
1628             $self->_fetch( $fmtr, $fmtr_name, \%arg );
1629              
1630 2526         216662 my @rslt;
1631 2526 100       7445 foreach my $parm ( $fmtr->{chain} ?
1632             $fmtr->{chain}->( $self, $fmtr_name, $value, \%arg ) :
1633             \%arg ) {
1634              
1635             push @rslt, defined $arg{literal} ?
1636 2662 50       7922 $self->_format_string( $arg{literal}, \%arg, $fmtr ) :
1637             $self->_apply_dimension( $value, $parm, $fmtr );
1638              
1639             }
1640              
1641 2526         15819 return join ' ', @rslt;
1642 540         4215 };
1643             }
1644              
1645             sub __make_formatter_methods {
1646 11     11   36 my ( $class ) = @_;
1647              
1648 11         45 foreach my $fmtr ( $class->__get_formatter_data() ) {
1649 539         1114 my $fmtr_name = $fmtr->{name};
1650              
1651 539 50       3209 $class->can( $fmtr_name )
1652             and next;
1653              
1654 539         1254 my $fq = "${class}::$fmtr_name";
1655              
1656 11     11   101 no strict qw{ refs };
  11         29  
  11         56652  
1657              
1658 539         948 *$fq = __PACKAGE__->__make_formatter_code( $fmtr );
1659              
1660             }
1661 11         54 return;
1662             }
1663              
1664             __PACKAGE__->__make_formatter_methods();
1665              
1666             # Title control
1667              
1668             # sub is_valid_title_gravity would normally be here, but in order to
1669             # reduce technical debt it shares a hash with _do_title(), and is placed
1670             # with it, below.
1671              
1672             sub more_title_lines {
1673 116     116 1 2314 my ( $self ) = @_;
1674             exists $self->{internal}{_title_info}
1675 116 100       620 or return 1;
1676 79         209 my $more;
1677 79 100       394 if ( $more = delete $self->{internal}{_title_info}{more} ) {
1678 42         126 $self->{internal}{_title_info}{inx}++
1679             } else {
1680 37         226 $self->reset_title_lines();
1681             }
1682 79         277 return $more;
1683             }
1684              
1685             sub reset_title_lines {
1686 37     37 1 137 my ( $self ) = @_;
1687 37         138 delete $self->{internal}{_title_info};
1688 37         79 return;
1689             }
1690              
1691             # Private methods and subroutines of all sorts.
1692              
1693             {
1694              
1695             my @always = qw{ align_left missing title };
1696              
1697             sub _apply_defaults {
1698 2526     2526   4897 my ( $self, $arg, $fmtr ) = @_;
1699              
1700 2526         6099 my $fmtr_name = $fmtr->{name};
1701 2526   50     5934 my $dflt = $fmtr->{default} || {};
1702              
1703             defined $arg->{width}
1704             or $self->{fixed_width}
1705 2526 100 100     10052 or $arg->{width} = '';
1706              
1707 2526 100 66     6038 if ( defined $arg->{format} && ! defined $arg->{width} ) {
1708             $arg->{width} = $self->{time_formatter}->
1709 5         39 format_datetime_width( $arg->{format} );
1710             }
1711              
1712             # TODO maybe apply locale here? But see also _do_title.
1713             APPLY_DEFAULT_LOOP:
1714 2526         3788 foreach my $key ( keys %{ $dflt }, @always ) {
  2526         7359  
1715              
1716 13560 100       26600 defined $arg->{$key} and next;
1717              
1718 12625         19346 foreach my $source ( qw{ default internal } ) {
1719 25231 100       46597 defined( $arg->{$key} = $self->_get( $source, $fmtr_name,
1720             $key ) )
1721             and next APPLY_DEFAULT_LOOP;
1722             }
1723              
1724             defined( $arg->{$key} = __localize(
1725             text => [ $fmtr_name, $key ],
1726             locale => $fmtr->{locale},
1727 12606 100       43141 ) )
1728             and next;
1729              
1730 10123         21920 my $default = $dflt->{$key};
1731 10123 100       25772 $arg->{$key} = CODE_REF eq ref $default ?
1732             $default->( $self, $fmtr_name, $arg ) : $default
1733              
1734             }
1735              
1736             defined $arg->{width}
1737 2526 100       6517 or $arg->{width} = '';
1738             $arg->{width} =~ m/ \D /sxm
1739 2526 50       8705 and $arg->{width} = '';
1740              
1741 2526 100       5823 if ( $self->{report} ) {
1742 2145         4417 my $report = "-$self->{report}";
1743 2145         3619 foreach my $key ( qw{ literal missing title } ) {
1744 6435 100       12836 defined $arg->{$key}
1745             or next;
1746             $arg->{$key} = __localize(
1747             text => [ $report, 'string', $arg->{$key} ],
1748             default => $arg->{$key},
1749             locale => $fmtr->{locale},
1750 2166         8114 );
1751             }
1752              
1753             }
1754              
1755 2526         4964 return;
1756             }
1757              
1758             }
1759              
1760             sub _apply_dimension {
1761 2662     2662   5819 my ( $self, $value, $arg, $fmtr ) = @_;
1762              
1763 2662         5287 my $fmtr_name = $fmtr->{name};
1764             defined( my $dim_name = $fmtr->{dimension}{dimension} )
1765 2662 50       7244 or $self->weep( 'No dimension specified' );
1766              
1767 2662         3729 my $dim;
1768             $dim = $dimensions{$dim_name}
1769             and defined( my $unit_name = _dor( $arg->{units}, $fmtr->{dimension}{units},
1770             $self->_get( default => $fmtr_name, 'units' ),
1771 2662 50 33     12460 $dim->{default} ) )
1772             or $self->weep( "Dimension $dim_name undefined" );
1773              
1774             my $unit = $dim->{define}{$unit_name}
1775             or $self->{warner}->wail(
1776 2662 50       9027 "Units $unit_name not valid for $dim_name" );
1777              
1778 2662 100       5717 if ( defined $unit->{alias} ) {
1779             my $alias = $dim->{define}{$unit->{alias}}
1780 13 50       87 or $self->weep( "Undefined alias '$unit->{alias}'" );
1781 13         36 $unit_name = $unit->{alias};
1782 13         26 $unit = $alias;
1783             }
1784              
1785             defined $arg->{align_left}
1786             or $arg->{align_left} = _dor( $unit->{align_left},
1787 2662 100       8460 $dim->{align_left} );
1788              
1789             $self->{title}
1790 2662 100       7489 and return $self->_do_title( $arg, $fmtr );
1791              
1792 2004 100       4315 defined $value
1793             or return $self->_format_undef( undef, $arg, $fmtr );
1794              
1795             defined $unit->{method}
1796 1846 100       4348 and do {
1797 8         38 my $method = $unit->{method};
1798 8 100       60 defined( $value = $self->$method( $value ) )
1799             or return $self->_format_undef( undef, $arg, $fmtr );
1800             };
1801              
1802             defined $unit->{factor}
1803 1845 100       3948 and $value *= $unit->{factor};
1804              
1805             defined $unit->{gmt}
1806             and not defined $arg->{gmt}
1807 1845 100 66     4375 and $arg->{gmt} = $unit->{gmt};
1808              
1809 1845         3334 $arg->{units} = $unit_name;
1810              
1811             $value = __localize(
1812             text => [ $fmtr_name, 'localize_value', $value ],
1813             default => $value,
1814             locale => $fmtr->{locale},
1815 1845         7819 );
1816              
1817             defined( my $formatter = _dor( $unit->{formatter},
1818             $fmtr->{dimension}{formatter},
1819             $dim->{formatter},
1820 1845 50       6829 ) )
1821             or $self->weep( "No formatter for $dim_name $unit_name" );
1822              
1823 1845         7298 return $self->$formatter( $value, $arg, $fmtr );
1824             }
1825              
1826             sub _arguments {
1827 2764     2764   6453 my @arg = @_;
1828              
1829 2764         4958 my $obj = shift @arg;
1830 2764 100       7853 my $hash = HASH_REF eq ref $arg[-1] ? pop @arg : {};
1831              
1832 2764         4548 my ( @clean, @append );
1833 2764         5821 foreach my $item ( @arg ) {
1834 1178 100       3275 if ( has_method( $item, 'dereference' ) ) {
1835 637         1899 push @append, $item->dereference();
1836             } else {
1837 541         1176 push @clean, $item;
1838             }
1839             }
1840              
1841 2764 100       6993 @clean % 2 and splice @clean, 0, 0, 'title';
1842              
1843 2764         4468 return ( $obj, %{ $hash }, @clean, @append );
  2764         12485  
1844             }
1845              
1846             =begin comment
1847              
1848             # TODO remove this after October 1 2016
1849             # It's only still here because, although I can't find a call for it, and
1850             # testcover shows it is not called, I'm paranoid that I did something
1851             # tricky that I can not now remember and is not covered by the tests.
1852              
1853             sub _attrib_hash {
1854             my ( $self, $name, @arg ) = @_;
1855             if ( @arg ) {
1856             my $value = shift @arg;
1857             ref $value
1858             and HASH_REF eq reftype( $value )
1859             or $self->{warner}->wail(
1860             "Attribute $name must be a hash reference" );
1861             $self->{$name} = $value;
1862             return $self;
1863             } else {
1864             return $self->{$name};
1865             }
1866             }
1867              
1868             =end comment
1869              
1870             =cut
1871              
1872             {
1873              
1874             my %do_title = (
1875             TITLE_GRAVITY_TOP() => sub {
1876             my ( $self, $wrapped, $arg, $fmtr ) = @_;
1877             defined $self->{internal}{_title_info}{inx}
1878             or $self->{internal}{_title_info}{inx} = 0;
1879             my $inx = $self->{internal}{_title_info}{inx};
1880             $self->{internal}{_title_info}{more} ||=
1881             defined $wrapped->[$inx + 1];
1882              
1883             return defined $wrapped->[$inx] ?
1884             $wrapped->[$inx] :
1885             $self->_format_string( '', $arg, $fmtr );
1886             },
1887             TITLE_GRAVITY_BOTTOM() => sub {
1888             my ( $self, $wrapped, $arg, $fmtr ) = @_;
1889             defined $self->{internal}{_title_info}{inx}
1890             or do {
1891             $self->{internal}{_title_info}{inx} = -1;
1892             $self->{internal}{_title_info}{max} = 0;
1893             };
1894             my $size = @{ $wrapped };
1895             my $inx = $self->{internal}{_title_info}{inx};
1896             if ( $inx < 0 ) {
1897             $self->{internal}{_title_info}{max} = max(
1898             $size,
1899             $self->{internal}{_title_info}{max},
1900             );
1901             }
1902             my $max = $self->{internal}{_title_info}{max};
1903             $self->{internal}{_title_info}{more} ||= $inx + 1 < $max;
1904             $inx = $inx - $max + $size;
1905             return ( $inx >= 0 && defined $wrapped->[$inx] ) ?
1906             $wrapped->[$inx] :
1907             $self->_format_string( '', $arg, $fmtr );
1908             },
1909             );
1910              
1911             sub _do_title {
1912 658     658   1370 my ( $self, $arg, $fmtr ) = @_;
1913 658         1196 my $fmtr_name = $fmtr->{name};
1914             # TODO this looks like a good place to insert localized title
1915             # code. But see also _apply_defaults().
1916             defined $arg->{title}
1917 658 50       1598 or $arg->{title} = '';
1918 658         1075 my $title = $arg->{title};
1919             my $wrapped = $self->{internal}{$fmtr_name}{_title}{$title}{$arg->{width}}
1920 658   66     3871 ||= $self->_do_title_wrap( $arg, $fmtr );
1921              
1922 658         1979 return $do_title{$self->{title_gravity}}->( $self, $wrapped,
1923             $arg, $fmtr );
1924             }
1925              
1926             sub is_valid_title_gravity {
1927 786     786 1 1553 my ( @args ) = @_;
1928 786 50       2063 defined( my $value = pop @args )
1929             or return 0;
1930 786 50       2857 return $do_title{$value} ? 1 : 0;
1931             }
1932              
1933             }
1934              
1935             sub _do_title_wrap {
1936 341     341   693 my ( $self, $arg, $fmtr ) = @_;
1937 341         674 my $title = $arg->{title};
1938 341 100       948 $arg->{width} eq ''
1939             and return [ $title ];
1940             $arg->{width}
1941 277 50       579 or return [ '' ];
1942 277         576 local $Text::Wrap::columns = $arg->{width} + 1;
1943 277         524 local $Text::Wrap::huge = 'overflow';
1944 277         907 my $wrap = Text::Wrap::wrap( '', '', $title );
1945 277         51118 my @lines = split qr{ \n }sxm, $wrap;
1946 277         858 return [ map { $self->_format_string( $_, $arg, $fmtr ) } @lines ];
  281         860  
1947             }
1948              
1949             sub __chain_bearing {
1950 188     188   476 my ( undef, undef, $value, $arg ) = @_; # Invocant, $name unused
1951             $arg->{bearing}
1952             and $arg->{bearing} =~ RE_ALL_DIGITS
1953 188 100 66     1469 or $arg->{bearing} = 0;
1954              
1955 188 100       528 $arg->{bearing} or return $arg;
1956              
1957 185 100       679 if ( defined $value ) {
1958 136         228 my $ab = { %{ $arg } }; # Shallow clone
  136         814  
1959 136 100       515 $ab->{width} and $ab->{width} = $ab->{bearing};
1960 136         274 $ab->{units} = 'bearing';
1961 136         345 return ( $arg, $ab );
1962             } else {
1963             $arg->{width}
1964 49 50       204 and $arg->{width} += $arg->{bearing} + 1;
1965 49         133 return $arg;
1966             }
1967             }
1968              
1969             sub _dor {
1970 7694     7694   14100 foreach ( @_ ) {
1971 20923 100       51803 defined $_ and return $_;
1972             }
1973 1100         2567 return $_[-1];
1974             }
1975              
1976             sub _get {
1977 32327     32327   65524 my ( $self, @arg ) = @_;
1978 32327         43787 my $hash = $self;
1979 32327         50215 foreach my $key ( @arg ) {
1980 91773 100       207329 ref $hash or return NONE;
1981 65130 50       107962 defined $key
1982             or $self->weep( 'Undefined key' );
1983 65130         116717 my $ref = reftype( $hash );
1984 65130 50       108170 if ( HASH_REF eq $ref ) {
    0          
    0          
1985 65130         112610 $hash = $hash->{$key};
1986             } elsif ( ARRAY_REF eq $ref ) {
1987 0         0 $hash = $hash->[$key];
1988             } elsif ( CODE_REF eq $ref ) {
1989 0         0 $hash = $hash->( $self, $key );
1990             } else {
1991 0         0 return NONE;
1992             }
1993             }
1994 5684         15939 return $hash;
1995             }
1996              
1997             sub _get_eci {
1998 2154     2154   4475 my ( $self, @arg ) = @_;
1999 2154         4260 my $eci = $self->_get( data => @arg );
2000 2154 100       6202 embodies( $eci, 'Astro::Coord::ECI' )
2001             and return $eci;
2002 85         1599 return NONE;
2003             }
2004              
2005             # @coords = $self->_get_precessed_coordinates( $method, $body,
2006             # $station );
2007             #
2008             # This method fetches the coordinates of the given body which are
2009             # specified by the given method. These must be inertial, and are
2010             # precessed if desired. If the body is not defined, nothing is
2011             # returned. If the station is passed, the coordinates are relative
2012             # to it; if it is undefined, nothing is returned.
2013              
2014             sub _get_precessed_coordinates {
2015 41     41   148 my ( $self, $method, $body, $station ) = @_;
2016              
2017 41         103 foreach my $thing ( $body, $station ) {
2018 82 50       925 embodies( $thing, 'Astro::Coord::ECI' )
2019             or return;
2020             }
2021              
2022             # TODO need to set station time from body? I think not now, but
2023             # Astro::App::Satpass2::FormatValue needed this.
2024              
2025 41 50       819 if ( my $equinox = $self->{desired_equinox_dynamical} ) {
2026 0         0 foreach my $thing ( $body, $station ) {
2027 0         0 $thing = $thing->clone()->precess_dynamical( $equinox );
2028             }
2029             }
2030              
2031 41         163 return $station->$method( $body );
2032             }
2033              
2034             sub _get_tle {
2035 20     20   81 my ( $self, @arg ) = @_;
2036 20         59 my $tle = $self->_get( data => @arg );
2037 20 100       150 embodies( $tle, 'Astro::Coord::ECI::TLE' )
2038             and return $tle;
2039 14         490 return NONE;
2040             }
2041              
2042             sub _get_tle_attr {
2043 132     132   413 my ( $self, @arg ) = @_;
2044 132         260 my $attr = pop @arg;
2045 132         335 my $tle = $self->_get( data => @arg );
2046 132 100 100     512 embodies( $tle, 'Astro::Coord::ECI::TLE' )
2047             and $tle->attribute( $attr )
2048             or return NONE;
2049 78         2758 return $tle->get( $attr );
2050             }
2051              
2052             # $string = $self->_format_*( $value, \%arg, \%fmtr );
2053             #
2054             # These methods take the value and turn it into a string.
2055             # Recognized arguments are:
2056             # {places} => decimal places, ignored if not a non-negative
2057             # number;
2058             # {width} => field width, ignored if not a non-negative
2059             # number;
2060              
2061             # Called as $self->$method()
2062             sub _format_bearing { ## no critic (ProhibitUnusedPrivateSubroutines)
2063 139     139   769 my ( $self, $value, $arg, $fmtr ) = @_;
2064 139 50       368 defined $value
2065             or goto &_format_undef;
2066              
2067 139         208 my $table;
2068              
2069 139         292 foreach my $source ( qw{ default } ) {
2070 139 100       318 $table = $self->_get( $source => bearing => 'table' )
2071             and last;
2072             }
2073              
2074             $table ||= __localize(
2075             text => [ bearing => 'table' ],
2076             default => [],
2077             locale => $fmtr->{locale},
2078 139   66     909 );
2079              
2080             $arg->{bearing}
2081 139 100 50     497 or $arg->{bearing} = ( $arg->{width} || 2 );
2082             $arg->{width}
2083             and $arg->{bearing} > $arg->{width}
2084 139 50 66     613 and $arg->{bearing} = $arg->{width};
2085              
2086 139   50     346 my $inx = min( $arg->{bearing} || 2, scalar @{ $table } ) - 1;
  139         478  
2087 139         272 my $tags = $table->[$inx];
2088 139         258 my $bins = @{ $tags };
  139         232  
2089 139         637 $inx = floor ($value / TWOPI * $bins + .5) % $bins;
2090 139         461 return $self->_format_string( $tags->[$inx], $arg, $fmtr );
2091             }
2092              
2093             # Called as $self->$method()
2094             sub _format_duration { ## no critic (ProhibitUnusedPrivateSubroutines)
2095 12     12   51 my ( $self, $value, $arg, $fmtr ) = @_;
2096              
2097             defined $arg->{align_left}
2098 12 100       52 or $arg->{align_left} = 0;
2099              
2100 12 50       53 defined $value
2101             or goto &_format_undef;
2102              
2103 12         53 my $secs = floor ($value + .5);
2104 12         49 my $mins = floor ($secs / 60);
2105 12         28 $secs %= 60;
2106 12         49 my $hrs = floor ($mins / 60);
2107 12         30 $mins %= 60;
2108 12         37 my $days = floor ($hrs / 24);
2109 12         21 $hrs %= 24;
2110              
2111 12         27 my $buffer;
2112 12 100       41 if ($days > 0) {
2113 1         12 $buffer = sprintf '%d %02d:%02d:%02d', $days, $hrs, $mins, $secs;
2114             } else {
2115 11         68 $buffer = sprintf '%02d:%02d:%02d', $hrs, $mins, $secs;
2116             }
2117              
2118             '' eq $arg->{width}
2119 12 100       56 and return $buffer;
2120              
2121             length $buffer <= $arg->{width}
2122             or $self->{overflow}
2123 10 0 33     39 or return '*' x $arg->{width};
2124              
2125 10 50       53 $arg->{width} - length $buffer
2126             or return $buffer;
2127              
2128 10         36 return $self->_format_string( $buffer, $arg, $fmtr );
2129             }
2130              
2131             # Called as $self->$method()
2132             sub _format_event { ## no critic (ProhibitUnusedPrivateSubroutines)
2133 380     380   911 my ( $self, $value, $arg, $fmtr ) = @_;
2134              
2135 380 50       845 defined $value
2136             or goto &_format_undef;
2137              
2138 380 100 100     1342 isdual( $value )
2139             or $value !~ m/ \D /sxm
2140             or goto &_format_string;
2141              
2142 368         535 my $table;
2143 368 100       838 if ( 'string' ne $arg->{units} ) {
2144 367         608 foreach my $source ( qw{ default } ) {
2145 367 100       686 $table = $self->_get( $source => event => 'table' )
2146             and last;
2147             }
2148             }
2149 368   66     1739 $table ||= __localize(
2150             text => [ event => 'table' ],
2151             default => [],
2152             );
2153              
2154 368   100     1692 return $self->_format_string( $table->[$value] || '', $arg, $fmtr );
2155             }
2156              
2157             # Called as $self->$method()
2158             sub _format_integer { ## no critic (ProhibitUnusedPrivateSubroutines)
2159 5     5   14 my ( $self, $value, $arg ) = @_; # $fmtr unused
2160 5 50       14 defined $value
2161             or goto &_format_undef;
2162              
2163             $arg->{width}
2164 5 100 66     42 and $arg->{width} =~ RE_ALL_DIGITS
2165             or return sprintf '%d', $value;
2166              
2167 4         20 my $buffer = sprintf '%*d', $arg->{width}, $value;
2168              
2169             length $buffer <= $arg->{width}
2170             or $self->{overflow}
2171 4 0 33     14 or return '*' x $arg->{width};
2172              
2173 4         15 return $buffer;
2174             }
2175              
2176             # Called as $self->$method()
2177             sub _format_lower_case { ## no critic (ProhibitUnusedPrivateSubroutines)
2178 0     0   0 my ( $self, $value, $arg, $fmtr ) = @_;
2179 0 0       0 defined $value
2180             or goto &_format_undef;
2181              
2182 0         0 return $self->_format_string( lc $value, $arg, $fmtr );
2183             }
2184              
2185             # Called as $self->$method()
2186             sub _format_number { ## no critic (ProhibitUnusedPrivateSubroutines)
2187 813     813   1782 my ( $self, $value, $arg, $fmtr ) = @_;
2188 813 50 33     6357 defined $value
2189             and $value ne ''
2190             or goto &_format_undef;
2191              
2192             my $width = ( $arg->{width} && $arg->{width} =~ RE_ALL_DIGITS )
2193 813 100 66     5110 ? $arg->{width} : '';
2194 813         1783 my $tplt = "%$width";
2195             defined $arg->{places}
2196 813 50 33     4381 and $arg->{places} =~ RE_ALL_DIGITS
2197             and $tplt .= ".$arg->{places}";
2198              
2199 813 50       1848 '%' eq $tplt
2200             and return "$value";
2201              
2202 813         4900 my $buffer = sprintf $tplt . 'f', $value;
2203              
2204             # The following line is because sprintf '%.1f', 0.04 produces
2205             # '-0.0'. This may not be a bug, given what 'perldoc -f sprintf'
2206             # says, but it sure looks like a wart to me.
2207 813         2502 $buffer =~ s/ \A ( \s* ) - ( 0* [.]? 0* \s* ) \z /$1 $2/smx;
2208              
2209 813 100       1765 $width or return $buffer;
2210              
2211 767 100 66     2862 if ($width && length $buffer > $width && $width >= 7) {
      66        
2212 6         29 $arg->{places} = $width - 7;
2213 6         35 return $self->_format_number_scientific( $value, $arg, $fmtr );
2214             }
2215              
2216             length $buffer <= $width
2217             or $self->{overflow}
2218 761 0 33     1648 or return '*' x $width;
2219              
2220 761         2989 return $buffer;
2221             }
2222              
2223             sub _format_number_scientific {
2224 15     15   79 my ( $self, $value, $arg ) = @_; # $fmtr unused
2225 15 50 33     152 defined $value
2226             and $value ne ''
2227             or goto &_format_undef;
2228              
2229             my $width = ( $arg->{width} && $arg->{width} =~ RE_ALL_DIGITS )
2230 15 100 66     137 ? $arg->{width} : '';
2231 15         61 my $tplt = "%$width";
2232             defined $arg->{places}
2233 15 50 33     150 and $arg->{places} =~ RE_ALL_DIGITS
2234             and $tplt .= ".$arg->{places}";
2235 15         34 $tplt .= 'e';
2236              
2237 15         112 my $buffer = sprintf $tplt, $value;
2238 15 0 33     79 $buffer =~ s/ e ( [-+]? ) 0 ( [0-9]{2} ) \z /e$1$2/smx # Normalize
      33        
2239             and $width
2240             and $width > length $buffer
2241             and $buffer = ' ' . $buffer; # Preserve width after normalize
2242              
2243 15 100       67 $width
2244             or return $buffer;
2245              
2246             length $buffer <= $width
2247             or $self->{overflow}
2248 9 0 33     37 or return '*' x $width;
2249              
2250 9         57 return $buffer;
2251             }
2252              
2253             # Called as $self->$method()
2254             sub _format_phase { ## no critic (ProhibitUnusedPrivateSubroutines)
2255 5     5   27 my ( $self, $value, $arg, $fmtr ) = @_;
2256 5 50       24 defined $value
2257             or goto &_format_undef;
2258 5         24 my $angle = rad2deg( $value );
2259              
2260 5         32 my $table;
2261 5         27 foreach my $source ( qw{ default } ) {
2262 5 100       22 $table = $self->_get( $source => phase => 'table' )
2263             and last;
2264             }
2265             $table ||= __localize(
2266             text => [ phase => 'table' ],
2267             default => [],
2268             locale => $fmtr->{locale},
2269 5   66     52 );
2270 5         17 foreach my $entry ( @{ $table } ) {
  5         19  
2271 20 100       56 $entry->[0] > $angle or next;
2272 5         24 return $self->_format_string( $entry->[1], $arg, $fmtr );
2273             }
2274 0         0 return $self->_format_string( $table->[0][1], $arg, $fmtr );
2275             }
2276              
2277             # Called as $self->$method()
2278             sub _format_right_ascension { ## no critic (ProhibitUnusedPrivateSubroutines)
2279 25     25   91 my ( $self, $value, $arg, $fmtr ) = @_;
2280 25 50       67 defined $value
2281             or goto &_format_undef;
2282 25         63 my $sec = $value / PI * 12;
2283 25         81 my $hr = floor($sec);
2284 25         53 $sec = ($sec - $hr) * 60;
2285 25         61 my $min = floor($sec);
2286 25         49 $sec = ($sec - $min) * 60;
2287 25         49 my ( $ps, $wid );
2288 25 50 33     236 if ( defined $arg->{places} && $arg->{places} =~ RE_ALL_DIGITS )
2289             {
2290 25         75 $ps = ".$arg->{places}";
2291 25 100       69 $wid = $arg->{places} ? 3 + $arg->{places} : 2;
2292             } else {
2293 0         0 $ps = '';
2294 0         0 $wid = 2;
2295             }
2296             defined $arg->{align_left}
2297 25 50       64 or $arg->{align_left} = 0;
2298 25         351 return $self->_format_string(
2299             sprintf( "%02d:%02d:%0$wid${ps}f", $hr, $min, $sec ), $arg,
2300             $fmtr );
2301             }
2302              
2303             sub _format_string {
2304 1613     1613   3696 my ( $self, $value, $arg ) = @_; # $fmtr unused
2305              
2306 1613 50       3368 defined $value
2307             or goto &_format_undef;
2308              
2309             defined $arg->{width}
2310 1613 100 66     9559 and $arg->{width} =~ RE_ALL_DIGITS
2311             or return "$value";
2312              
2313 1389 100       3842 my $left = defined $arg->{align_left} ? $arg->{align_left} : 1;
2314 1389 100       2974 $left = $left ? '-' : '';
2315              
2316 1389         5511 my $buffer = sprintf "%$left*s", $arg->{width}, $value;
2317              
2318             length $buffer <= $arg->{width}
2319             or $self->{overflow}
2320 1389 50 66     4125 or return substr $buffer, 0, $arg->{width};
2321              
2322 1306         6225 return $buffer;
2323             }
2324              
2325             # Called as $self->$method()
2326             sub _format_time { ## no critic (ProhibitUnusedPrivateSubroutines)
2327 285     285   751 my ( $self, $value, $arg, $fmtr ) = @_;
2328 285 50       705 defined $value
2329             or goto &_format_undef;
2330              
2331 285         604 my $time_fmtr = $self->{time_formatter};
2332 285         1112 $time_fmtr->round_time( $arg->{round_time} );
2333 285         666 my $fmt = $arg->{format};
2334 285 50       685 defined $fmt
2335             or $self->weep( 'No time format' );
2336              
2337             my $buffer = $time_fmtr->format_datetime(
2338 285         1104 $fmt, $value, $arg->{gmt} );
2339 285         1474 return $self->_format_string( $buffer, $arg, $fmtr );
2340             }
2341              
2342             # Called as $self->$method()
2343             sub _format_title_case { ## no critic (ProhibitUnusedPrivateSubroutines)
2344 0     0   0 my ( $self, $value, $arg, $fmtr ) = @_;
2345 0 0       0 defined $value
2346             or goto &_format_undef;
2347              
2348             ## $value = join '', map { ucfirst lc $_ }
2349             ## split qr{ (?<= [^[:alpha:]] ) (?= [[:alpha:]] ) }sxm, $value;
2350 0         0 $value =~ s{ (?: \A | (?<= \s ) ) ( [[:alpha:]] \S* ) }
  0         0  
2351 0         0 { ucfirst lc $1 }sxmge;
2352             return $self->_format_string( $value, $arg, $fmtr );
2353             }
2354              
2355 159     159   467 sub _format_undef {
2356             my ( $self, undef, $arg, $fmtr ) = @_; # $value unused
2357              
2358             $self->{title}
2359 159 50 33     479 and defined $arg->{title}
2360             and return $self->_format_string( $arg->{title}, $arg, $fmtr );
2361              
2362 159 100       410 defined $arg->{missing}
2363             and return $self->_format_string( $arg->{missing}, $arg, $fmtr );
2364              
2365             defined $arg->{width}
2366             and $arg->{width} =~ RE_ALL_DIGITS
2367 156 100 66     1296 and $arg->{width}
      66        
2368             or return '';
2369 125         625  
2370             return ' ' x $arg->{width};
2371             }
2372              
2373             # Called as $self->$method()
2374 0     0   0 sub _format_upper_case { ## no critic (ProhibitUnusedPrivateSubroutines)
2375 0 0       0 my ( $self, $value, $arg, $fmtr ) = @_;
2376             defined $value
2377             or goto &_format_undef;
2378 0         0  
2379             return $self->_format_string( uc $value, $arg, $fmtr );
2380             }
2381              
2382             # Called as $self->$method()
2383 4     4   11 sub _julian_day { ## no critic (ProhibitUnusedPrivateSubroutines)
2384 4         25 my ( undef, $value ) = @_; # Invocant unused
2385             return julianday( $value );
2386             }
2387              
2388 780     780   1783 sub _get_date_format_data {
2389 780   66     3201 my ( $self, $name, $datum, $info ) = @_;
2390             $self->{internal}{_date_format}{$name} ||=
2391 780         2418 $self->_manufacture_date_format( $name, $info );
2392             return $self->{internal}{_date_format}{$name}{$datum};
2393             }
2394              
2395 347     347   717 sub _manufacture_date_format {
2396 405 50       2130 my ( $self, undef, $info ) = @_; # $name unused
2397 347         578 my $fmt = join ' ', grep { defined $_ && '' ne $_ }
  405         1359  
  347         1003  
2398             map { $self->{$_} } @{ $info->{dimension}{format} };
2399 347         1568 my $wid =
2400 347         1812 $self->{time_formatter}->format_datetime_width( $fmt );
2401             return { format => $fmt, width => $wid };
2402             }
2403              
2404             {
2405              
2406             my %fmt;
2407              
2408 11     11   72 BEGIN {
  22         3804  
2409             %fmt = map { $_ => 1 } qw{ date_format time_format };
2410             }
2411              
2412 66     66   126 sub _valid_time_format_name {
2413 66         199 my ( undef, $name ) = @_;
2414             return $fmt{$name};
2415             }
2416             }
2417              
2418             =begin comment
2419              
2420             # TODO remove this after October 1 2016
2421             # It's only still here because, although I can't find a call for it, and
2422             # testcover shows it is not called, I'm paranoid that I did something
2423             # tricky that I can not now remember and is not covered by the tests.
2424              
2425             sub _set_time_format {
2426             my ($self, $name, $data) = @_;
2427             $self->_valid_time_format( $name )
2428             or $self->weep(
2429             "'$name' invalid for _set_time_format()" );
2430             $self->{$name} = $data;
2431             delete $self->{internal}{_date_format};
2432              
2433             return $self;
2434             }
2435              
2436             =end comment
2437              
2438             =cut
2439              
2440             # Called as $self->$method()
2441 4     4   16 sub _subtract_epoch { ## no critic (ProhibitUnusedPrivateSubroutines)
2442 4         10 my ( $self, $value ) = @_;
2443 4 100       70 my $epoch = $self->_get_tle_attr( body => 'epoch' );
2444             defined $epoch
2445 3         12 or return $epoch;
2446             return $value - $epoch;
2447             }
2448              
2449 149     149   339 sub _variant {
2450             my ( $self, $variant ) = @_;
2451 149         246  
2452 149 100       357 my $data;
2453             if ( defined $variant ) {
2454 105 100       269 $data = HASH_REF eq ref $variant ? $variant : { # Shallow clone
  85 100       213  
2455             %{ $self->_get( data => $variant ) || {} }
2456 105         269 };
2457 210         454 foreach my $key ( qw{ station time } ) {
2458             $data->{$key} = $self->_get( data => $key );
2459             }
2460 44         66 } else {
  44         97  
2461             $data = { %{ $self->_get( 'data' ) } }; # Shallow clone
2462             }
2463 149         437  
2464             return $self->clone( data => $data );
2465             }
2466              
2467             1;
2468              
2469             __END__