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   2757 use strict;
  11         26  
  11         330  
4 11     11   69 use warnings;
  11         38  
  11         320  
5              
6 11     11   72 use parent qw{ Astro::App::Satpass2::Copier };
  11         37  
  11         75  
7              
8 11     11   740 use Astro::App::Satpass2::FormatTime;
  11         39  
  11         238  
9 11     11   1923 use Astro::App::Satpass2::FormatValue::Formatter;
  11         36  
  11         425  
10 11     11   105 use Astro::App::Satpass2::Locale qw{ __localize };
  11         36  
  11         624  
11 11         1385 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   67 };
  11         44  
16 11     11   92 use Astro::App::Satpass2::Warner;
  11         37  
  11         404  
17 11     11   80 use Astro::Coord::ECI::Sun 0.059;
  11         292  
  11         377  
18 11     11   74 use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
  11         220  
  11         2579  
19 11     11   95 use Astro::Coord::ECI::Utils 0.059 qw{ deg2rad embodies julianday PI rad2deg TWOPI };
  11         262  
  11         894  
20 11     11   81 use Clone ();
  11         27  
  11         266  
21 11     11   97 use List::Util qw{ max min };
  11         25  
  11         884  
22 11     11   90 use POSIX qw{ floor };
  11         26  
  11         157  
23 11     11   958 use Scalar::Util 1.26 qw{ isdual reftype };
  11         185  
  11         545  
24 11     11   5409 use Text::Wrap ();
  11         30593  
  11         453  
25              
26             our $VERSION = '0.051_01';
27              
28 11     11   89 use constant NONE => undef;
  11         24  
  11         966  
29 11     11   87 use constant RE_ALL_DIGITS => qr{ \A [0-9]+ \z }smx;
  11         35  
  11         567  
30 11     11   81 use constant TITLE_GRAVITY_BOTTOM => 'bottom';
  11         27  
  11         562  
31 11     11   85 use constant TITLE_GRAVITY_TOP => 'top';
  11         55  
  11         68057  
32              
33             # Instantiator
34              
35             {
36              
37             sub new {
38 749     749 1 9967 my ( $class, %args ) = @_;
39 749 100       2496 ref $class and $class = ref $class;
40 749         1429 my $self = {};
41 749         1554 bless $self, $class;
42              
43 749         2902 $self->warner( delete $args{warner} );
44              
45 749         1623 foreach my $name ( qw{ data default } ) {
46 1498   100     4281 $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     6436 "Argument '$name' must be a hash reference" );
51             }
52              
53             $self->{desired_equinox_dynamical} =
54 749   50     2711 $args{desired_equinox_dynamical} || 0;
55              
56             $self->{fixed_width} = exists $args{fixed_width} ?
57             $args{fixed_width} :
58 749 100       1742 1;
59              
60 749   50     2333 $self->{overflow} = $args{overflow} || 0;
61              
62             defined( $self->{local_coordinates} = $args{local_coordinates} )
63 749 100       1885 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     2054 "Unknown local_coordinates $self->{local_coordinates}" );
69             CODE_REF eq ref $self->{local_coordinates}
70             or $self->{warner}->wail(
71 749 50       1874 '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       2744 or $self->{list_formatter} = $self->can( '__list_formatter' );
77             CODE_REF eq ref $self->{list_formatter}
78             or $self->{warner}->wail(
79 749 50       1769 'Argument list_formatter must be a code reference ',
80             'or the name of a known coordinate system'
81             );
82              
83 749         1729 $self->{title} = $args{title};
84              
85             $self->title_gravity( _dor( $args{title_gravity},
86 749         2400 TITLE_GRAVITY_TOP ) );
87              
88             $self->{time_formatter} = $args{time_formatter} ||
89 749   100     2301 'Astro::App::Satpass2::FormatTime';
90             ref $self->{time_formatter}
91 749 100       2157 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       2484 'Argument time_formatter must be an Astro::App::Satpass2::FormatTime'
96             );
97 749         1841 $self->{date_format} = $args{date_format};
98             defined $self->{date_format}
99 749 100       1736 or $self->{date_format} = $self->{time_formatter}->DATE_FORMAT();
100 749         1543 $self->{time_format} = $args{time_format};
101             defined $self->{time_format}
102 749 100       1649 or $self->{time_format} = $self->{time_formatter}->TIME_FORMAT();
103 749 100       1500 if ( exists $args{round_time} ) {
104 731         1599 $self->{round_time} = $args{round_time};
105             } else {
106 18         82 $self->{round_time} = $self->{time_formatter}->ROUND_TIME();
107             }
108              
109 749         1345 $self->{report} = $args{report};
110              
111 749         3958 return $self;
112             }
113              
114             }
115              
116             # Overrides
117              
118             sub clone {
119 282     282 1 5731 my ( $self, @args ) = @_;
120 282         461 my %arg;
121 282 50 33     844 if ( @args == 1 && HASH_REF eq ref $args[0] ) {
122 0         0 %arg = %{ $args[0] };
  0         0  
123             } else {
124 282         632 %arg = @args;
125             }
126 282         451 foreach my $name ( keys %{ $self } ) {
  282         1182  
127             defined $arg{$name}
128 4383 100       9798 or $arg{$name} = $self->{ $name };
129             }
130 282         711 delete $arg{internal};
131 282         1086 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 911 my ( $self ) = @_;
139 53         157 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 91440 my ( $self, @args ) = @_;
151 14 50       39 if ( @args ) {
152 14         28 $self->{fixed_width} = $args[0];
153 14         83 return $self;
154             } else {
155 0         0 return $self->{fixed_width};
156             }
157             }
158              
159             sub title_gravity {
160 846     846 1 601871 my ( $self, @args ) = @_;
161 846 100       1879 if ( @args ) {
162             is_valid_title_gravity( $args[0] )
163             or $self->{warner}->wail(
164 786 50       1982 "Attribute title_gravity value '$args[0]' invalid"
165             );
166 786         1723 $self->{title_gravity} = $args[0];
167 786         1474 return $self;
168             } else {
169 60         518 return $self->{title_gravity};
170             }
171             }
172              
173             # Transformations
174              
175             sub appulse {
176 49     49 1 36149 my ( $self ) = @_;
177 49         158 return $self->_variant( 'appulse' );
178             }
179              
180             sub bodies {
181 9     9 1 174 my ( $self ) = @_;
182 9 50       33 my $bodies = $self->_get( data => 'bodies' )
183             or return;
184              
185 9         38 my $questionable = $self->_get( data => 'questionable' );
186 9         39 my $sta = $self->_get( data => 'station' );
187 9         40 my $time = $self->_get( data => 'time' );
188 9         55 my $twilight = $self->_get( data => 'twilight' );
189 9 100       51 defined $twilight
190             or $twilight = deg2rad( -6 ); # Civil
191              
192 9         51 my @rslt;
193 9         25 foreach my $body ( @{ $bodies } ) {
  9         56  
194 20 50       70 embodies( $body, 'Astro::Coord::ECI' )
195             or next;
196              
197 20         553 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         370 push @rslt, $self->_variant( $data );
209             }
210              
211 9         60 return \@rslt;
212             }
213              
214             sub _elevation {
215 6     6   29 my ( $station, $body, $time ) = @_;
216 6 50       40 defined $time and $body->universal( $time );
217 6         109 return ( $station->azel( $body ) )[1];
218             }
219              
220             sub _illumination {
221 20     20   92 my %arg = @_;
222              
223 20 100       66 embodies( $arg{body}, 'Astro::Coord::ECI::TLE' )
224             or return PASS_EVENT_NONE;
225              
226             defined $arg{time}
227 6 50       168 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       362 or $arg{sun} = Astro::Coord::ECI::Sun->new();
233              
234             defined $arg{twilight}
235             or $arg{twilight} = _dor(
236 6 50       175 $arg{body}->get( 'twilight' ),
237             deg2rad( -6 ), # Civil
238             );
239              
240             defined $arg{time}
241 6 50 33     38 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       149 or return PASS_EVENT_SHADOWED;
247              
248             _elevation( $arg{station}, $arg{sun}, $arg{time} ) > $arg{twilight}
249 6 50 33     5252 and _elevation( $arg{station}, $arg{body}, $arg{time} ) >= 0
250             and return PASS_EVENT_DAY;
251              
252 6         1592 return PASS_EVENT_LIT;
253             }
254              
255             sub center {
256 36     36 1 27727 my ( $self ) = @_;
257 36         112 return $self->_variant( 'center' );
258             }
259              
260             sub earth {
261 5     5 1 2228 my ( $self ) = @_;
262 5         17 my $earth = $self->_variant();
263 5         33 $earth->{data}{station} = Astro::Coord::ECI->new()->ecef( 0, 0, 0 );
264 5         615 return $earth;
265             }
266              
267             sub events {
268 31     31 1 39223 my ( $self ) = @_;
269 31         133 return [ map { $self->clone( data => $_ ) } $self->__raw_events() ];
  118         331  
270             }
271              
272             sub tle_events {
273 2     2 1 7 my ( $self ) = @_;
274 2         5 my @rslt;
275              
276 2         8 foreach my $evt ( $self->__raw_events() ) {
277 10 100       57 embodies( $evt->{body}, 'Astro::Coord::ECI::TLE' )
278             or next;
279 8         205 push @rslt, $self->clone( data => $evt );
280             }
281 2         42 return \@rslt;
282             }
283              
284             sub __raw_events {
285 36     36   92 my ( $self ) = @_;
286              
287 36 50       117 my $events = $self->_get( data => 'events' )
288             or return;
289              
290 36 50       184 ARRAY_REF eq ref $events
291             or return;
292              
293 36         94 return @{ $events };
  36         158  
294             }
295              
296             sub reflections {
297 20     20 1 400 my ( $self ) = @_;
298              
299 20 100       60 my $body = $self->_get_tle( 'body' )
300             or return;
301              
302 6 50       228 my $sta = $self->_get_eci( 'station' )
303             or return;
304              
305 6         139 my $time = $self->_get( data => 'time' );
306 6 50       19 defined $time
307             or $time = $body->universal();
308 6 50       35 defined $time or return;
309              
310 6         19 my $illum = $self->_get( data => 'illumination' );
311 6 50       36 defined $illum
312             or $illum = _illumination(
313             body => $body,
314             station => $sta,
315             time => $time,
316             );
317              
318 6 50 33     65 $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 28656 my ( $self ) = @_;
335 39         108 my $station = $self->_variant();
336             ( $station->{data}{body}, $station->{data}{station} ) = (
337 39         99 map { $station->_get( data => $_ ) } qw{ station body } );
  78         188  
338 39         102 return $station;
339             }
340              
341             # Formatters
342              
343             sub list {
344 23     23 1 3697 my ( $self, %arg ) = _arguments( @_ );
345 23         131 return $self->{list_formatter}->( $self, %arg );
346             }
347              
348             sub __list_formatter {
349 3     3   13 my ( $self, @arg ) = _arguments( @_ );
350 3         8 my $body;
351 3 100       12 my $type = ( $body = $self->body() ) ?
352             $body->__list_type() :
353             'inertial';
354 3         89 my $code;
355 3 50       32 $code = $self->can( "__list_formatter_$type" )
356             and return $code->( $self, @arg );
357 3   33     19 $code = $self->can( "__list_formatter_args_$type" ) ||
358             $self->can( '__list_formatter_args_inertial' );
359 3         12 my $rslt = join ' ', map { $self->$_( @arg ) } $code->( $self );
  13         59  
360 3         22 $rslt =~ s/ \s+ \z //smx;
361 3         12 return $rslt;
362             }
363              
364             sub __list_formatter_args_fixed {
365 1     1   7 return ( qw{ oid name latitude longitude altitude } );
366             }
367              
368             sub __list_formatter_args_inertial {
369 2     2   7 return ( qw{ oid name epoch period } );
370             }
371              
372             sub local_coord {
373 205     205 1 8742 my ( $self, %arg ) = _arguments( @_ );
374 205         1077 return $self->{local_coordinates}->( $self, %arg );
375             }
376              
377             sub __local_coord_az_rng {
378 1     1   4 my ( $self, @arg ) = _arguments( @_ );
379 1         6 return join ' ', $self->azimuth( @arg, { bearing => 2 } ),
380             $self->range( @arg );
381             }
382              
383             sub __local_coord_azel {
384 1     1   6 my ( $self, @arg ) = _arguments( @_ );
385 1         50 return join ' ', $self->elevation( @arg ),
386             $self->azimuth( @arg, { bearing => 2 } );
387             }
388              
389             sub __local_coord_azel_rng {
390 3     3   10 my ( $self, @arg ) = _arguments( @_ );
391 3         13 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   5 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         7 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   101 my ( $from, $to ) = @_;
1482 44         912 %{ $formatter_data{$to} } = %{ $formatter_data{$from} };
  44         189  
  44         134  
1483 44         104 $formatter_data{$to}{name} = $to;
1484 44         89 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   4402 my ( $self, $info, $name, $arg ) = @_;
1494              
1495 1868 100       5042 if ( ! $self->{internal}{time_set} ) {
1496 356 100       921 if ( defined( my $time = $self->_get( data => 'time' ) ) ) {
1497 329         775 foreach my $key ( qw{ body station } ) {
1498 658 100       75848 my $obj = $self->_get_eci( $key )
1499             or next;
1500 606         17064 $obj->universal( $time );
1501             }
1502             }
1503 356         8212 $self->{internal}{time_set} = 1;
1504             }
1505 1868         6172 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   28 my ( undef, $name ) = @_; # Invocant unused
1514 11 50       112 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 1000 my ( $self, @formatters ) = @_;
1539 449         993 foreach my $fmtr_obj ( @formatters ) {
1540             instance( $fmtr_obj, $fmtr_class )
1541             or $self->{warner}->wail(
1542 3 50       9 "Formatters must be instances of $fmtr_class" );
1543 3         12 my $name = $fmtr_obj->name();
1544             $self->can( $name )
1545             and not $defined_here{$name}
1546             and $self->{warner}->wail(
1547 3 50 66     20 "Formatter $name can not override built-in format" );
1548             $self->{formatter_method}{$name}
1549             and $self->{warner}->wail(
1550 3 50       15 "Formatter $name can not replace previously-set formatter of same name" );
1551 3         8 $self->{formatter_method}{$name} = $fmtr_obj;
1552 3 100       11 unless ( $defined_here{$name} ) {
1553 1         3 $defined_here{$name} = 1;
1554 11     11   99 no strict qw{ refs };
  11         91  
  11         9440  
1555             *$name = sub {
1556 1     1   2170 my ( $self ) = @_;
1557             my $obj = $self->{formatter_method}{$name}
1558 1 50       15 or $self->{warner}->wail( "No such formatter as '$name'" );
1559 1         3 goto &{ $obj->code() };
  1         8  
1560 1         11 };
1561             }
1562             }
1563 449         957 return $self;
1564             }
1565             }
1566              
1567             sub __make_formatter_code {
1568 540     540   931 my ( $class, $fmtr ) = @_;
1569              
1570 540 50       1151 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       1139 or _confess( 'The {name} must be defined' );
1574              
1575             # Validate the dimension information
1576             $fmtr->{dimension}
1577 540 50       995 or _confess(
1578             "'$fmtr_name' does not specify a {dimension} hash" );
1579             defined( my $dim_name = $fmtr->{dimension}{dimension} )
1580 540 50       1064 or _confess(
1581             "'$fmtr_name' does not specify the dimension" );
1582 540 50       1049 $dimensions{$dim_name}
1583             or _confess( "'$fmtr_name' specifies invalid dimension '$dim_name'" );
1584 540 50       1044 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       990 if ( 'time_units' eq $dim_name ) {
1592 44 50       139 if ( ARRAY_REF eq ref $fmtr->{dimension}{format} ) {
1593 44         71 foreach my $entry ( @{ $fmtr->{dimension}{format} } ) {
  44         111  
1594 66 50       149 $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   790 my ( $self ) = @_;
1600 403         1072 return $self->_get_date_format_data( $fmtr_name, format => $fmtr );
1601 44         290 };
1602             $fmtr->{default}{width} = sub {
1603 377     377   776 my ( $self ) = @_;
1604 377         1039 return $self->_get_date_format_data( $fmtr_name, width => $fmtr );
1605 44         192 };
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   847 my ( $self ) = @_;
1612 408         1213 return $self->{round_time};
1613 44         143 };
1614             }
1615              
1616             # Validate the fetch information
1617             CODE_REF eq ref $fmtr->{fetch}
1618 540 50       1095 or _confess(
1619             "In '$fmtr_name', {fetch} is not a code reference" );
1620              
1621             return sub {
1622 2526     2526   2302870 my ( $self, %arg ) = _arguments( @_ );
1623              
1624 2526         8398 $self->_apply_defaults( \%arg, $fmtr );
1625              
1626 2526 100 66     11960 my $value = ( $self->{title} || defined $arg{literal} ) ?
1627             NONE :
1628             $self->_fetch( $fmtr, $fmtr_name, \%arg );
1629              
1630 2526         218887 my @rslt;
1631 2526 100       7027 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       8501 $self->_format_string( $arg{literal}, \%arg, $fmtr ) :
1637             $self->_apply_dimension( $value, $parm, $fmtr );
1638              
1639             }
1640              
1641 2526         15909 return join ' ', @rslt;
1642 540         3944 };
1643             }
1644              
1645             sub __make_formatter_methods {
1646 11     11   37 my ( $class ) = @_;
1647              
1648 11         38 foreach my $fmtr ( $class->__get_formatter_data() ) {
1649 539         1125 my $fmtr_name = $fmtr->{name};
1650              
1651 539 50       3218 $class->can( $fmtr_name )
1652             and next;
1653              
1654 539         1197 my $fq = "${class}::$fmtr_name";
1655              
1656 11     11   93 no strict qw{ refs };
  11         54  
  11         55374  
1657              
1658 539         976 *$fq = __PACKAGE__->__make_formatter_code( $fmtr );
1659              
1660             }
1661 11         50 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 2144 my ( $self ) = @_;
1674             exists $self->{internal}{_title_info}
1675 116 100       556 or return 1;
1676 79         183 my $more;
1677 79 100       331 if ( $more = delete $self->{internal}{_title_info}{more} ) {
1678 42         96 $self->{internal}{_title_info}{inx}++
1679             } else {
1680 37         260 $self->reset_title_lines();
1681             }
1682 79         234 return $more;
1683             }
1684              
1685             sub reset_title_lines {
1686 37     37 1 103 my ( $self ) = @_;
1687 37         144 delete $self->{internal}{_title_info};
1688 37         97 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   5214 my ( $self, $arg, $fmtr ) = @_;
1699              
1700 2526         5668 my $fmtr_name = $fmtr->{name};
1701 2526   50     6603 my $dflt = $fmtr->{default} || {};
1702              
1703             defined $arg->{width}
1704             or $self->{fixed_width}
1705 2526 100 100     9907 or $arg->{width} = '';
1706              
1707 2526 100 66     6329 if ( defined $arg->{format} && ! defined $arg->{width} ) {
1708             $arg->{width} = $self->{time_formatter}->
1709 5         27 format_datetime_width( $arg->{format} );
1710             }
1711              
1712             # TODO maybe apply locale here? But see also _do_title.
1713             APPLY_DEFAULT_LOOP:
1714 2526         3763 foreach my $key ( keys %{ $dflt }, @always ) {
  2526         7517  
1715              
1716 13560 100       27843 defined $arg->{$key} and next;
1717              
1718 12625         19548 foreach my $source ( qw{ default internal } ) {
1719 25231 100       46654 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       43497 ) )
1728             and next;
1729              
1730 10123         22655 my $default = $dflt->{$key};
1731 10123 100       25673 $arg->{$key} = CODE_REF eq ref $default ?
1732             $default->( $self, $fmtr_name, $arg ) : $default
1733              
1734             }
1735              
1736             defined $arg->{width}
1737 2526 100       6332 or $arg->{width} = '';
1738             $arg->{width} =~ m/ \D /sxm
1739 2526 50       8684 and $arg->{width} = '';
1740              
1741 2526 100       5562 if ( $self->{report} ) {
1742 2145         4807 my $report = "-$self->{report}";
1743 2145         3700 foreach my $key ( qw{ literal missing title } ) {
1744 6435 100       12484 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         7955 );
1751             }
1752              
1753             }
1754              
1755 2526         5046 return;
1756             }
1757              
1758             }
1759              
1760             sub _apply_dimension {
1761 2662     2662   5620 my ( $self, $value, $arg, $fmtr ) = @_;
1762              
1763 2662         5058 my $fmtr_name = $fmtr->{name};
1764             defined( my $dim_name = $fmtr->{dimension}{dimension} )
1765 2662 50       7162 or $self->weep( 'No dimension specified' );
1766              
1767 2662         3797 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     12285 $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       9202 "Units $unit_name not valid for $dim_name" );
1777              
1778 2662 100       5777 if ( defined $unit->{alias} ) {
1779             my $alias = $dim->{define}{$unit->{alias}}
1780 13 50       79 or $self->weep( "Undefined alias '$unit->{alias}'" );
1781 13         28 $unit_name = $unit->{alias};
1782 13         27 $unit = $alias;
1783             }
1784              
1785             defined $arg->{align_left}
1786             or $arg->{align_left} = _dor( $unit->{align_left},
1787 2662 100       8391 $dim->{align_left} );
1788              
1789             $self->{title}
1790 2662 100       7323 and return $self->_do_title( $arg, $fmtr );
1791              
1792 2004 100       4335 defined $value
1793             or return $self->_format_undef( undef, $arg, $fmtr );
1794              
1795             defined $unit->{method}
1796 1846 100       4249 and do {
1797 8         31 my $method = $unit->{method};
1798 8 100       38 defined( $value = $self->$method( $value ) )
1799             or return $self->_format_undef( undef, $arg, $fmtr );
1800             };
1801              
1802             defined $unit->{factor}
1803 1845 100       3858 and $value *= $unit->{factor};
1804              
1805             defined $unit->{gmt}
1806             and not defined $arg->{gmt}
1807 1845 100 66     4324 and $arg->{gmt} = $unit->{gmt};
1808              
1809 1845         3378 $arg->{units} = $unit_name;
1810              
1811             $value = __localize(
1812             text => [ $fmtr_name, 'localize_value', $value ],
1813             default => $value,
1814             locale => $fmtr->{locale},
1815 1845         7660 );
1816              
1817             defined( my $formatter = _dor( $unit->{formatter},
1818             $fmtr->{dimension}{formatter},
1819             $dim->{formatter},
1820 1845 50       7237 ) )
1821             or $self->weep( "No formatter for $dim_name $unit_name" );
1822              
1823 1845         7253 return $self->$formatter( $value, $arg, $fmtr );
1824             }
1825              
1826             sub _arguments {
1827 2764     2764   6545 my @arg = @_;
1828              
1829 2764         4877 my $obj = shift @arg;
1830 2764 100       8275 my $hash = HASH_REF eq ref $arg[-1] ? pop @arg : {};
1831              
1832 2764         4839 my ( @clean, @append );
1833 2764         5684 foreach my $item ( @arg ) {
1834 1178 100       3617 if ( has_method( $item, 'dereference' ) ) {
1835 637         1897 push @append, $item->dereference();
1836             } else {
1837 541         1100 push @clean, $item;
1838             }
1839             }
1840              
1841 2764 100       7217 @clean % 2 and splice @clean, 0, 0, 'title';
1842              
1843 2764         4495 return ( $obj, %{ $hash }, @clean, @append );
  2764         12802  
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   1354 my ( $self, $arg, $fmtr ) = @_;
1913 658         1176 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       1671 or $arg->{title} = '';
1918 658         1166 my $title = $arg->{title};
1919             my $wrapped = $self->{internal}{$fmtr_name}{_title}{$title}{$arg->{width}}
1920 658   66     3926 ||= $self->_do_title_wrap( $arg, $fmtr );
1921              
1922 658         1974 return $do_title{$self->{title_gravity}}->( $self, $wrapped,
1923             $arg, $fmtr );
1924             }
1925              
1926             sub is_valid_title_gravity {
1927 786     786 1 1679 my ( @args ) = @_;
1928 786 50       1848 defined( my $value = pop @args )
1929             or return 0;
1930 786 50       2804 return $do_title{$value} ? 1 : 0;
1931             }
1932              
1933             }
1934              
1935             sub _do_title_wrap {
1936 341     341   743 my ( $self, $arg, $fmtr ) = @_;
1937 341         600 my $title = $arg->{title};
1938 341 100       1007 $arg->{width} eq ''
1939             and return [ $title ];
1940             $arg->{width}
1941 277 50       663 or return [ '' ];
1942 277         636 local $Text::Wrap::columns = $arg->{width} + 1;
1943 277         501 local $Text::Wrap::huge = 'overflow';
1944 277         866 my $wrap = Text::Wrap::wrap( '', '', $title );
1945 277         49620 my @lines = split qr{ \n }sxm, $wrap;
1946 277         854 return [ map { $self->_format_string( $_, $arg, $fmtr ) } @lines ];
  281         817  
1947             }
1948              
1949             sub __chain_bearing {
1950 188     188   603 my ( undef, undef, $value, $arg ) = @_; # Invocant, $name unused
1951             $arg->{bearing}
1952             and $arg->{bearing} =~ RE_ALL_DIGITS
1953 188 100 66     1569 or $arg->{bearing} = 0;
1954              
1955 188 100       537 $arg->{bearing} or return $arg;
1956              
1957 185 100       741 if ( defined $value ) {
1958 136         270 my $ab = { %{ $arg } }; # Shallow clone
  136         814  
1959 136 100       490 $ab->{width} and $ab->{width} = $ab->{bearing};
1960 136         310 $ab->{units} = 'bearing';
1961 136         388 return ( $arg, $ab );
1962             } else {
1963             $arg->{width}
1964 49 50       178 and $arg->{width} += $arg->{bearing} + 1;
1965 49         140 return $arg;
1966             }
1967             }
1968              
1969             sub _dor {
1970 7694     7694   14715 foreach ( @_ ) {
1971 20923 100       51336 defined $_ and return $_;
1972             }
1973 1100         2669 return $_[-1];
1974             }
1975              
1976             sub _get {
1977 32327     32327   67405 my ( $self, @arg ) = @_;
1978 32327         46384 my $hash = $self;
1979 32327         49821 foreach my $key ( @arg ) {
1980 91883 100       213788 ref $hash or return NONE;
1981 65240 50       111376 defined $key
1982             or $self->weep( 'Undefined key' );
1983 65240         120544 my $ref = reftype( $hash );
1984 65240 50       108927 if ( HASH_REF eq $ref ) {
    0          
    0          
1985 65240         119204 $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         16463 return $hash;
1995             }
1996              
1997             sub _get_eci {
1998 2154     2154   4684 my ( $self, @arg ) = @_;
1999 2154         4416 my $eci = $self->_get( data => @arg );
2000 2154 100       6327 embodies( $eci, 'Astro::Coord::ECI' )
2001             and return $eci;
2002 85         1589 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   100 my ( $self, $method, $body, $station ) = @_;
2016              
2017 41         86 foreach my $thing ( $body, $station ) {
2018 82 50       927 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       832 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         201 return $station->$method( $body );
2032             }
2033              
2034             sub _get_tle {
2035 20     20   47 my ( $self, @arg ) = @_;
2036 20         47 my $tle = $self->_get( data => @arg );
2037 20 100       106 embodies( $tle, 'Astro::Coord::ECI::TLE' )
2038             and return $tle;
2039 14         408 return NONE;
2040             }
2041              
2042             sub _get_tle_attr {
2043 132     132   403 my ( $self, @arg ) = @_;
2044 132         272 my $attr = pop @arg;
2045 132         331 my $tle = $self->_get( data => @arg );
2046 132 100 100     466 embodies( $tle, 'Astro::Coord::ECI::TLE' )
2047             and $tle->attribute( $attr )
2048             or return NONE;
2049 78         2834 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   365 my ( $self, $value, $arg, $fmtr ) = @_;
2064 139 50       354 defined $value
2065             or goto &_format_undef;
2066              
2067 139         196 my $table;
2068              
2069 139         249 foreach my $source ( qw{ default } ) {
2070 139 100       330 $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     868 );
2079              
2080             $arg->{bearing}
2081 139 100 50     477 or $arg->{bearing} = ( $arg->{width} || 2 );
2082             $arg->{width}
2083             and $arg->{bearing} > $arg->{width}
2084 139 50 66     624 and $arg->{bearing} = $arg->{width};
2085              
2086 139   50     338 my $inx = min( $arg->{bearing} || 2, scalar @{ $table } ) - 1;
  139         471  
2087 139         346 my $tags = $table->[$inx];
2088 139         225 my $bins = @{ $tags };
  139         256  
2089 139         635 $inx = floor ($value / TWOPI * $bins + .5) % $bins;
2090 139         452 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   57 my ( $self, $value, $arg, $fmtr ) = @_;
2096              
2097             defined $arg->{align_left}
2098 12 100       61 or $arg->{align_left} = 0;
2099              
2100 12 50       76 defined $value
2101             or goto &_format_undef;
2102              
2103 12         49 my $secs = floor ($value + .5);
2104 12         64 my $mins = floor ($secs / 60);
2105 12         35 $secs %= 60;
2106 12         44 my $hrs = floor ($mins / 60);
2107 12         28 $mins %= 60;
2108 12         43 my $days = floor ($hrs / 24);
2109 12         25 $hrs %= 24;
2110              
2111 12         24 my $buffer;
2112 12 100       51 if ($days > 0) {
2113 1         6 $buffer = sprintf '%d %02d:%02d:%02d', $days, $hrs, $mins, $secs;
2114             } else {
2115 11         73 $buffer = sprintf '%02d:%02d:%02d', $hrs, $mins, $secs;
2116             }
2117              
2118             '' eq $arg->{width}
2119 12 100       66 and return $buffer;
2120              
2121             length $buffer <= $arg->{width}
2122             or $self->{overflow}
2123 10 0 33     42 or return '*' x $arg->{width};
2124              
2125 10 50       49 $arg->{width} - length $buffer
2126             or return $buffer;
2127              
2128 10         39 return $self->_format_string( $buffer, $arg, $fmtr );
2129             }
2130              
2131             # Called as $self->$method()
2132             sub _format_event { ## no critic (ProhibitUnusedPrivateSubroutines)
2133 380     380   890 my ( $self, $value, $arg, $fmtr ) = @_;
2134              
2135 380 50       867 defined $value
2136             or goto &_format_undef;
2137              
2138 380 100 100     1424 isdual( $value )
2139             or $value !~ m/ \D /sxm
2140             or goto &_format_string;
2141              
2142 368         484 my $table;
2143 368 100       886 if ( 'string' ne $arg->{units} ) {
2144 367         666 foreach my $source ( qw{ default } ) {
2145 367 100       738 $table = $self->_get( $source => event => 'table' )
2146             and last;
2147             }
2148             }
2149 368   66     1772 $table ||= __localize(
2150             text => [ event => 'table' ],
2151             default => [],
2152             );
2153              
2154 368   100     1525 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   18 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     45 and $arg->{width} =~ RE_ALL_DIGITS
2165             or return sprintf '%d', $value;
2166              
2167 4         19 my $buffer = sprintf '%*d', $arg->{width}, $value;
2168              
2169             length $buffer <= $arg->{width}
2170             or $self->{overflow}
2171 4 0 33     15 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   1685 my ( $self, $value, $arg, $fmtr ) = @_;
2188 813 50 33     6508 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     5194 ? $arg->{width} : '';
2194 813         1888 my $tplt = "%$width";
2195             defined $arg->{places}
2196 813 50 33     4322 and $arg->{places} =~ RE_ALL_DIGITS
2197             and $tplt .= ".$arg->{places}";
2198              
2199 813 50       1857 '%' eq $tplt
2200             and return "$value";
2201              
2202 813         5045 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         2705 $buffer =~ s/ \A ( \s* ) - ( 0* [.]? 0* \s* ) \z /$1 $2/smx;
2208              
2209 813 100       1904 $width or return $buffer;
2210              
2211 767 100 66     2941 if ($width && length $buffer > $width && $width >= 7) {
      66        
2212 6         24 $arg->{places} = $width - 7;
2213 6         33 return $self->_format_number_scientific( $value, $arg, $fmtr );
2214             }
2215              
2216             length $buffer <= $width
2217             or $self->{overflow}
2218 761 0 33     1590 or return '*' x $width;
2219              
2220 761         2963 return $buffer;
2221             }
2222              
2223             sub _format_number_scientific {
2224 15     15   56 my ( $self, $value, $arg ) = @_; # $fmtr unused
2225 15 50 33     143 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     115 ? $arg->{width} : '';
2231 15         48 my $tplt = "%$width";
2232             defined $arg->{places}
2233 15 50 33     141 and $arg->{places} =~ RE_ALL_DIGITS
2234             and $tplt .= ".$arg->{places}";
2235 15         41 $tplt .= 'e';
2236              
2237 15         95 my $buffer = sprintf $tplt, $value;
2238 15 0 33     71 $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       66 $width
2244             or return $buffer;
2245              
2246             length $buffer <= $width
2247             or $self->{overflow}
2248 9 0 33     29 or return '*' x $width;
2249              
2250 9         45 return $buffer;
2251             }
2252              
2253             # Called as $self->$method()
2254             sub _format_phase { ## no critic (ProhibitUnusedPrivateSubroutines)
2255 5     5   20 my ( $self, $value, $arg, $fmtr ) = @_;
2256 5 50       22 defined $value
2257             or goto &_format_undef;
2258 5         25 my $angle = rad2deg( $value );
2259              
2260 5         29 my $table;
2261 5         20 foreach my $source ( qw{ default } ) {
2262 5 100       19 $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         22 foreach my $entry ( @{ $table } ) {
  5         17  
2271 20 100       59 $entry->[0] > $angle or next;
2272 5         25 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   82 my ( $self, $value, $arg, $fmtr ) = @_;
2280 25 50       68 defined $value
2281             or goto &_format_undef;
2282 25         67 my $sec = $value / PI * 12;
2283 25         85 my $hr = floor($sec);
2284 25         68 $sec = ($sec - $hr) * 60;
2285 25         58 my $min = floor($sec);
2286 25         49 $sec = ($sec - $min) * 60;
2287 25         44 my ( $ps, $wid );
2288 25 50 33     233 if ( defined $arg->{places} && $arg->{places} =~ RE_ALL_DIGITS )
2289             {
2290 25         71 $ps = ".$arg->{places}";
2291 25 100       65 $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       70 or $arg->{align_left} = 0;
2298 25         347 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   3717 my ( $self, $value, $arg ) = @_; # $fmtr unused
2305              
2306 1613 50       3307 defined $value
2307             or goto &_format_undef;
2308              
2309             defined $arg->{width}
2310 1613 100 66     9392 and $arg->{width} =~ RE_ALL_DIGITS
2311             or return "$value";
2312              
2313 1389 100       3529 my $left = defined $arg->{align_left} ? $arg->{align_left} : 1;
2314 1389 100       3187 $left = $left ? '-' : '';
2315              
2316 1389         5806 my $buffer = sprintf "%$left*s", $arg->{width}, $value;
2317              
2318             length $buffer <= $arg->{width}
2319             or $self->{overflow}
2320 1389 50 66     4394 or return substr $buffer, 0, $arg->{width};
2321              
2322 1306         6154 return $buffer;
2323             }
2324              
2325             # Called as $self->$method()
2326             sub _format_time { ## no critic (ProhibitUnusedPrivateSubroutines)
2327 285     285   792 my ( $self, $value, $arg, $fmtr ) = @_;
2328 285 50       661 defined $value
2329             or goto &_format_undef;
2330              
2331 285         612 my $time_fmtr = $self->{time_formatter};
2332 285         1055 $time_fmtr->round_time( $arg->{round_time} );
2333 285         574 my $fmt = $arg->{format};
2334 285 50       671 defined $fmt
2335             or $self->weep( 'No time format' );
2336              
2337             my $buffer = $time_fmtr->format_datetime(
2338 285         1073 $fmt, $value, $arg->{gmt} );
2339 285         1481 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   514 sub _format_undef {
2356             my ( $self, undef, $arg, $fmtr ) = @_; # $value unused
2357              
2358             $self->{title}
2359 159 50 33     526 and defined $arg->{title}
2360             and return $self->_format_string( $arg->{title}, $arg, $fmtr );
2361              
2362 159 100       457 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     1356 and $arg->{width}
      66        
2368             or return '';
2369 125         616  
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   13 sub _julian_day { ## no critic (ProhibitUnusedPrivateSubroutines)
2384 4         17 my ( undef, $value ) = @_; # Invocant unused
2385             return julianday( $value );
2386             }
2387              
2388 780     780   1820 sub _get_date_format_data {
2389 780   66     3433 my ( $self, $name, $datum, $info ) = @_;
2390             $self->{internal}{_date_format}{$name} ||=
2391 780         2329 $self->_manufacture_date_format( $name, $info );
2392             return $self->{internal}{_date_format}{$name}{$datum};
2393             }
2394              
2395 347     347   728 sub _manufacture_date_format {
2396 405 50       2099 my ( $self, undef, $info ) = @_; # $name unused
2397 347         580 my $fmt = join ' ', grep { defined $_ && '' ne $_ }
  405         1327  
  347         967  
2398             map { $self->{$_} } @{ $info->{dimension}{format} };
2399 347         1463 my $wid =
2400 347         1834 $self->{time_formatter}->format_datetime_width( $fmt );
2401             return { format => $fmt, width => $wid };
2402             }
2403              
2404             {
2405              
2406             my %fmt;
2407              
2408 11     11   70 BEGIN {
  22         3724  
2409             %fmt = map { $_ => 1 } qw{ date_format time_format };
2410             }
2411              
2412 66     66   119 sub _valid_time_format_name {
2413 66         192 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   14 sub _subtract_epoch { ## no critic (ProhibitUnusedPrivateSubroutines)
2442 4         10 my ( $self, $value ) = @_;
2443 4 100       115 my $epoch = $self->_get_tle_attr( body => 'epoch' );
2444             defined $epoch
2445 3         10 or return $epoch;
2446             return $value - $epoch;
2447             }
2448              
2449 149     149   353 sub _variant {
2450             my ( $self, $variant ) = @_;
2451 149         250  
2452 149 100       384 my $data;
2453             if ( defined $variant ) {
2454 105 100       273 $data = HASH_REF eq ref $variant ? $variant : { # Shallow clone
  85 100       235  
2455             %{ $self->_get( data => $variant ) || {} }
2456 105         322 };
2457 210         444 foreach my $key ( qw{ station time } ) {
2458             $data->{$key} = $self->_get( data => $key );
2459             }
2460 44         73 } else {
  44         103  
2461             $data = { %{ $self->_get( 'data' ) } }; # Shallow clone
2462             }
2463 149         459  
2464             return $self->clone( data => $data );
2465             }
2466              
2467             1;
2468              
2469             __END__