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   2725 use strict;
  11         22  
  11         389  
4 11     11   52 use warnings;
  11         16  
  11         566  
5              
6 11     11   46 use parent qw{ Astro::App::Satpass2::Copier };
  11         18  
  11         57  
7              
8 11     11   658 use Astro::App::Satpass2::FormatTime;
  11         21  
  11         247  
9 11     11   1787 use Astro::App::Satpass2::FormatValue::Formatter;
  11         21  
  11         311  
10 11     11   46 use Astro::App::Satpass2::Locale qw{ __localize };
  11         17  
  11         577  
11 11         1076 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   41 };
  11         16  
16 11     11   67 use Astro::App::Satpass2::Warner;
  11         17  
  11         287  
17 11     11   481 use Astro::Coord::ECI::Sun 0.059;
  11         21459  
  11         350  
18 11     11   1389 use Astro::Coord::ECI::TLE 0.059 qw{ :constants };
  11         62311  
  11         2089  
19 11     11   79 use Astro::Coord::ECI::Utils 0.059 qw{ deg2rad embodies julianday PI rad2deg TWOPI };
  11         198  
  11         721  
20 11     11   50 use Clone ();
  11         16  
  11         241  
21 11     11   39 use List::Util qw{ max min };
  11         40  
  11         668  
22 11     11   86 use POSIX qw{ floor };
  11         17  
  11         87  
23 11     11   842 use Scalar::Util 1.26 qw{ isdual reftype };
  11         150  
  11         420  
24 11     11   4058 use Text::Wrap ();
  11         23475  
  11         445  
25              
26             our $VERSION = '0.058';
27              
28 11     11   65 use constant NONE => undef;
  11         21  
  11         945  
29 11     11   53 use constant RE_ALL_DIGITS => qr{ \A [0-9]+ \z }smx;
  11         15  
  11         446  
30 11     11   42 use constant TITLE_GRAVITY_BOTTOM => 'bottom';
  11         18  
  11         358  
31 11     11   38 use constant TITLE_GRAVITY_TOP => 'top';
  11         17  
  11         51509  
32              
33             # Instantiator
34              
35             {
36              
37             sub new {
38 749     749 1 10174 my ( $class, %args ) = @_;
39 749 100       2055 ref $class and $class = ref $class;
40 749         1077 my $self = {};
41 749         1566 bless $self, $class;
42              
43 749         2387 $self->warner( delete $args{warner} );
44              
45 749         1301 foreach my $name ( qw{ data default } ) {
46 1498   100     3292 $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     5381 "Argument '$name' must be a hash reference" );
51             }
52              
53             $self->{desired_equinox_dynamical} =
54 749   50     2290 $args{desired_equinox_dynamical} || 0;
55              
56             $self->{fixed_width} = exists $args{fixed_width} ?
57             $args{fixed_width} :
58 749 100       1544 1;
59              
60 749   50     2563 $self->{overflow} = $args{overflow} || 0;
61              
62             defined( $self->{local_coordinates} = $args{local_coordinates} )
63 749 100       1605 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     1661 "Unknown local_coordinates $self->{local_coordinates}" );
69             CODE_REF eq ref $self->{local_coordinates}
70             or $self->{warner}->wail(
71 749 50       1633 '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       1531 or $self->{list_formatter} = $self->can( '__list_formatter' );
77             CODE_REF eq ref $self->{list_formatter}
78             or $self->{warner}->wail(
79 749 50       1515 'Argument list_formatter must be a code reference ',
80             'or the name of a known coordinate system'
81             );
82              
83 749         1331 $self->{title} = $args{title};
84              
85             $self->title_gravity( _dor( $args{title_gravity},
86 749         2011 TITLE_GRAVITY_TOP ) );
87              
88             $self->{time_formatter} = $args{time_formatter} ||
89 749   100     2401 'Astro::App::Satpass2::FormatTime';
90             ref $self->{time_formatter}
91 749 100       1681 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       1944 'Argument time_formatter must be an Astro::App::Satpass2::FormatTime'
96             );
97 749         1491 $self->{date_format} = $args{date_format};
98             defined $self->{date_format}
99 749 100       1498 or $self->{date_format} = $self->{time_formatter}->DATE_FORMAT();
100 749         1422 $self->{time_format} = $args{time_format};
101             defined $self->{time_format}
102 749 100       1296 or $self->{time_format} = $self->{time_formatter}->TIME_FORMAT();
103 749 100       1169 if ( exists $args{round_time} ) {
104 731         1668 $self->{round_time} = $args{round_time};
105             } else {
106 18         201 $self->{round_time} = $self->{time_formatter}->ROUND_TIME();
107             }
108              
109 749         1259 $self->{report} = $args{report};
110              
111 749         3835 return $self;
112             }
113              
114             }
115              
116             # Overrides
117              
118             sub clone {
119 282     282 1 3400 my ( $self, @args ) = @_;
120 282         440 my %arg;
121 282 50 33     756 if ( @args == 1 && HASH_REF eq ref $args[0] ) {
122 0         0 %arg = %{ $args[0] };
  0         0  
123             } else {
124 282         630 %arg = @args;
125             }
126 282         428 foreach my $name ( keys %{ $self } ) {
  282         1164  
127             defined $arg{$name}
128 4383 100       9143 or $arg{$name} = $self->{ $name };
129             }
130 282         716 delete $arg{internal};
131 282         1037 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 510 my ( $self ) = @_;
139 53         131 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 58801 my ( $self, @args ) = @_;
151 14 50       24 if ( @args ) {
152 14         20 $self->{fixed_width} = $args[0];
153 14         28 return $self;
154             } else {
155 0         0 return $self->{fixed_width};
156             }
157             }
158              
159             sub title_gravity {
160 846     846 1 403761 my ( $self, @args ) = @_;
161 846 100       1621 if ( @args ) {
162             is_valid_title_gravity( $args[0] )
163             or $self->{warner}->wail(
164 786 50       2207 "Attribute title_gravity value '$args[0]' invalid"
165             );
166 786         1603 $self->{title_gravity} = $args[0];
167 786         1295 return $self;
168             } else {
169 60         353 return $self->{title_gravity};
170             }
171             }
172              
173             # Transformations
174              
175             sub appulse {
176 49     49 1 18185 my ( $self ) = @_;
177 49         151 return $self->_variant( 'appulse' );
178             }
179              
180             sub bodies {
181 9     9 1 126 my ( $self ) = @_;
182 9 50       25 my $bodies = $self->_get( data => 'bodies' )
183             or return;
184              
185 9         27 my $questionable = $self->_get( data => 'questionable' );
186 9         21 my $sta = $self->_get( data => 'station' );
187 9         24 my $time = $self->_get( data => 'time' );
188 9         22 my $twilight = $self->_get( data => 'twilight' );
189 9 100       43 defined $twilight
190             or $twilight = deg2rad( -6 ); # Civil
191              
192 9         35 my @rslt;
193 9         17 foreach my $body ( @{ $bodies } ) {
  9         27  
194 20 50       51 embodies( $body, 'Astro::Coord::ECI' )
195             or next;
196              
197 20         489 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         270 push @rslt, $self->_variant( $data );
209             }
210              
211 9         44 return \@rslt;
212             }
213              
214             sub _elevation {
215 6     6   17 my ( $station, $body, $time ) = @_;
216 6 50       25 defined $time and $body->universal( $time );
217 6         70 return ( $station->azel( $body ) )[1];
218             }
219              
220             sub _illumination {
221 20     20   74 my %arg = @_;
222              
223 20 100       41 embodies( $arg{body}, 'Astro::Coord::ECI::TLE' )
224             or return PASS_EVENT_NONE;
225              
226             defined $arg{time}
227 6 50       94 or $arg{time} = $arg{body}->universal();
228              
229             embodies( $arg{sun}, 'Astro::Coord::ECI' )
230 6 50       19 or $arg{sun} = $arg{body}->get( 'sun' );
231             embodies( $arg{sun}, 'Astro::Coord::ECI' )
232 6 50       324 or $arg{sun} = Astro::Coord::ECI::Sun->new();
233              
234             defined $arg{twilight}
235             or $arg{twilight} = _dor(
236 6 50       100 $arg{body}->get( 'twilight' ),
237             deg2rad( -6 ), # Civil
238             );
239              
240             defined $arg{time}
241 6 50 33     24 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       128 or return PASS_EVENT_SHADOWED;
247              
248             _elevation( $arg{station}, $arg{sun}, $arg{time} ) > $arg{twilight}
249 6 50 33     3608 and _elevation( $arg{station}, $arg{body}, $arg{time} ) >= 0
250             and return PASS_EVENT_DAY;
251              
252 6         1197 return PASS_EVENT_LIT;
253             }
254              
255             sub center {
256 36     36 1 14095 my ( $self ) = @_;
257 36         115 return $self->_variant( 'center' );
258             }
259              
260             sub earth {
261 5     5 1 1148 my ( $self ) = @_;
262 5         14 my $earth = $self->_variant();
263 5         24 $earth->{data}{station} = Astro::Coord::ECI->new()->ecef( 0, 0, 0 );
264 5         496 return $earth;
265             }
266              
267             sub events {
268 31     31 1 24855 my ( $self ) = @_;
269 31         112 return [ map { $self->clone( data => $_ ) } $self->__raw_events() ];
  118         237  
270             }
271              
272             sub tle_events {
273 2     2 1 6 my ( $self ) = @_;
274 2         3 my @rslt;
275              
276 2         4 foreach my $evt ( $self->__raw_events() ) {
277 10 100       39 embodies( $evt->{body}, 'Astro::Coord::ECI::TLE' )
278             or next;
279 8         179 push @rslt, $self->clone( data => $evt );
280             }
281 2         27 return \@rslt;
282             }
283              
284             sub __raw_events {
285 36     36   71 my ( $self ) = @_;
286              
287 36 50       102 my $events = $self->_get( data => 'events' )
288             or return;
289              
290 36 50       141 ARRAY_REF eq ref $events
291             or return;
292              
293 36         61 return @{ $events };
  36         115  
294             }
295              
296             sub reflections {
297 20     20 1 330 my ( $self ) = @_;
298              
299 20 100       45 my $body = $self->_get_tle( 'body' )
300             or return;
301              
302 6 50       158 my $sta = $self->_get_eci( 'station' )
303             or return;
304              
305 6         97 my $time = $self->_get( data => 'time' );
306 6 50       15 defined $time
307             or $time = $body->universal();
308 6 50       14 defined $time or return;
309              
310 6         15 my $illum = $self->_get( data => 'illumination' );
311 6 50       13 defined $illum
312             or $illum = _illumination(
313             body => $body,
314             station => $sta,
315             time => $time,
316             );
317              
318 6 50 33     38 $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 15353 my ( $self ) = @_;
335 39         127 my $station = $self->_variant();
336             ( $station->{data}{body}, $station->{data}{station} ) = (
337 39         105 map { $station->_get( data => $_ ) } qw{ station body } );
  78         174  
338 39         137 return $station;
339             }
340              
341             # Formatters
342              
343             sub list {
344 23     23 1 2242 my ( $self, %arg ) = _arguments( @_ );
345 23         82 return $self->{list_formatter}->( $self, %arg );
346             }
347              
348             sub __list_formatter {
349 3     3   8 my ( $self, @arg ) = _arguments( @_ );
350 3         6 my $body;
351 3 100       11 my $type = ( $body = $self->body() ) ?
352             $body->__list_type() :
353             'inertial';
354 3         99 my $code;
355 3 50       37 $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         27 my $rslt = join ' ', map { $self->$_( @arg ) } $code->( $self );
  13         35  
360 3         16 $rslt =~ s/ \s+ \z //smx;
361 3         11 return $rslt;
362             }
363              
364             sub __list_formatter_args_fixed {
365 1     1   6 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 6425 my ( $self, %arg ) = _arguments( @_ );
374 205         866 return $self->{local_coordinates}->( $self, %arg );
375             }
376              
377             sub __local_coord_az_rng {
378 1     1   5 my ( $self, @arg ) = _arguments( @_ );
379 1         7 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         6 return join ' ', $self->elevation( @arg ),
386             $self->azimuth( @arg, { bearing => 2 } );
387             }
388              
389             sub __local_coord_azel_rng {
390 3     3   8 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   4 my ( $self, @arg ) = _arguments( @_ );
398 1         5 return join ' ', $self->right_ascension( @arg ),
399             $self->declination( @arg );
400             }
401              
402             sub __local_coord_equatorial_rng {
403 1     1   4 my ( $self, @arg ) = _arguments( @_ );
404 1         4 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   78 my ( $from, $to ) = @_;
1482 44         57 %{ $formatter_data{$to} } = %{ $formatter_data{$from} };
  44         120  
  44         84  
1483 44         81 $formatter_data{$to}{name} = $to;
1484 44         260 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   3150 my ( $self, $info, $name, $arg ) = @_;
1494              
1495 1868 100       3862 if ( ! $self->{internal}{time_set} ) {
1496 356 100       598 if ( defined( my $time = $self->_get( data => 'time' ) ) ) {
1497 329         519 foreach my $key ( qw{ body station } ) {
1498 658 100       65098 my $obj = $self->_get_eci( $key )
1499             or next;
1500 606         13883 $obj->universal( $time );
1501             }
1502             }
1503 356         6609 $self->{internal}{time_set} = 1;
1504             }
1505 1868         4148 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   25 my ( undef, $name ) = @_; # Invocant unused
1514 11 50       96 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 758 my ( $self, @formatters ) = @_;
1539 449         783 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         16 my $name = $fmtr_obj->name();
1544             $self->can( $name )
1545             and not $defined_here{$name}
1546             and $self->{warner}->wail(
1547 3 50 66     21 "Formatter $name can not override built-in format" );
1548             $self->{formatter_method}{$name}
1549             and $self->{warner}->wail(
1550 3 50       12 "Formatter $name can not replace previously-set formatter of same name" );
1551 3         7 $self->{formatter_method}{$name} = $fmtr_obj;
1552 3 100       11 unless ( $defined_here{$name} ) {
1553 1         2 $defined_here{$name} = 1;
1554 11     11   115 no strict qw{ refs };
  11         20  
  11         7698  
1555             *$name = sub {
1556 1     1   1671 my ( $self ) = @_;
1557             my $obj = $self->{formatter_method}{$name}
1558 1 50       6 or $self->{warner}->wail( "No such formatter as '$name'" );
1559 1         2 goto &{ $obj->code() };
  1         7  
1560 1         11 };
1561             }
1562             }
1563 449         722 return $self;
1564             }
1565             }
1566              
1567             sub __make_formatter_code {
1568 540     540   638 my ( $class, $fmtr ) = @_;
1569              
1570 540 50       786 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       836 or _confess( 'The {name} must be defined' );
1574              
1575             # Validate the dimension information
1576             $fmtr->{dimension}
1577 540 50       711 or _confess(
1578             "'$fmtr_name' does not specify a {dimension} hash" );
1579             defined( my $dim_name = $fmtr->{dimension}{dimension} )
1580 540 50       777 or _confess(
1581             "'$fmtr_name' does not specify the dimension" );
1582 540 50       792 $dimensions{$dim_name}
1583             or _confess( "'$fmtr_name' specifies invalid dimension '$dim_name'" );
1584 540 50       739 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       756 if ( 'time_units' eq $dim_name ) {
1592 44 50       480 if ( ARRAY_REF eq ref $fmtr->{dimension}{format} ) {
1593 44         58 foreach my $entry ( @{ $fmtr->{dimension}{format} } ) {
  44         98  
1594 66 50       109 $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   713 my ( $self ) = @_;
1600 403         821 return $self->_get_date_format_data( $fmtr_name, format => $fmtr );
1601 44         165 };
1602             $fmtr->{default}{width} = sub {
1603 377     377   637 my ( $self ) = @_;
1604 377         880 return $self->_get_date_format_data( $fmtr_name, width => $fmtr );
1605 44         304 };
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   667 my ( $self ) = @_;
1612 408         1069 return $self->{round_time};
1613 44         112 };
1614             }
1615              
1616             # Validate the fetch information
1617             CODE_REF eq ref $fmtr->{fetch}
1618 540 50       801 or _confess(
1619             "In '$fmtr_name', {fetch} is not a code reference" );
1620              
1621             return sub {
1622 2526     2526   1655868 my ( $self, %arg ) = _arguments( @_ );
1623              
1624 2526         6918 $self->_apply_defaults( \%arg, $fmtr );
1625              
1626 2526 100 66     9363 my $value = ( $self->{title} || defined $arg{literal} ) ?
1627             NONE :
1628             $self->_fetch( $fmtr, $fmtr_name, \%arg );
1629              
1630 2526         186798 my @rslt;
1631 2526 100       5830 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       6576 $self->_format_string( $arg{literal}, \%arg, $fmtr ) :
1637             $self->_apply_dimension( $value, $parm, $fmtr );
1638              
1639             }
1640              
1641 2526         15113 return join ' ', @rslt;
1642 540         2523 };
1643             }
1644              
1645             sub __make_formatter_methods {
1646 11     11   24 my ( $class ) = @_;
1647              
1648 11         77 foreach my $fmtr ( $class->__get_formatter_data() ) {
1649 539         705 my $fmtr_name = $fmtr->{name};
1650              
1651 539 50       2164 $class->can( $fmtr_name )
1652             and next;
1653              
1654 539         619 my $fq = "${class}::$fmtr_name";
1655              
1656 11     11   71 no strict qw{ refs };
  11         25  
  11         48380  
1657              
1658 539         707 *$fq = __PACKAGE__->__make_formatter_code( $fmtr );
1659              
1660             }
1661 11         43 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 1479 my ( $self ) = @_;
1674             exists $self->{internal}{_title_info}
1675 116 100       435 or return 1;
1676 79         133 my $more;
1677 79 100       296 if ( $more = delete $self->{internal}{_title_info}{more} ) {
1678 42         109 $self->{internal}{_title_info}{inx}++
1679             } else {
1680 37         110 $self->reset_title_lines();
1681             }
1682 79         230 return $more;
1683             }
1684              
1685             sub reset_title_lines {
1686 37     37 1 78 my ( $self ) = @_;
1687 37         103 delete $self->{internal}{_title_info};
1688 37         60 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   3999 my ( $self, $arg, $fmtr ) = @_;
1699              
1700 2526         4509 my $fmtr_name = $fmtr->{name};
1701 2526   50     5244 my $dflt = $fmtr->{default} || {};
1702              
1703             defined $arg->{width}
1704             or $self->{fixed_width}
1705 2526 100 100     8220 or $arg->{width} = '';
1706              
1707 2526 100 66     5671 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         2858 foreach my $key ( keys %{ $dflt }, @always ) {
  2526         6626  
1715              
1716 13560 100       22396 defined $arg->{$key} and next;
1717              
1718 12623         15269 foreach my $source ( qw{ default internal } ) {
1719 25227 100       34691 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 12604 100       31025 ) )
1728             and next;
1729              
1730 10121         17231 my $default = $dflt->{$key};
1731 10121 100       18438 $arg->{$key} = CODE_REF eq ref $default ?
1732             $default->( $self, $fmtr_name, $arg ) : $default
1733              
1734             }
1735              
1736             defined $arg->{width}
1737 2526 100       4934 or $arg->{width} = '';
1738             $arg->{width} =~ m/ \D /sxm
1739 2526 50       13662 and $arg->{width} = '';
1740              
1741 2526 100       4568 if ( $self->{report} ) {
1742 2145         3208 my $report = "-$self->{report}";
1743 2145         2846 foreach my $key ( qw{ literal missing title } ) {
1744 6435 100       10162 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         6360 );
1751             }
1752              
1753             }
1754              
1755 2526         3820 return;
1756             }
1757              
1758             }
1759              
1760             sub _apply_dimension {
1761 2662     2662   4452 my ( $self, $value, $arg, $fmtr ) = @_;
1762              
1763 2662         3937 my $fmtr_name = $fmtr->{name};
1764             defined( my $dim_name = $fmtr->{dimension}{dimension} )
1765 2662 50       5279 or $self->weep( 'No dimension specified' );
1766              
1767 2662         2962 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     11210 $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       7723 "Units $unit_name not valid for $dim_name" );
1777              
1778 2662 100       4561 if ( defined $unit->{alias} ) {
1779             my $alias = $dim->{define}{$unit->{alias}}
1780 13 50       43 or $self->weep( "Undefined alias '$unit->{alias}'" );
1781 13         31 $unit_name = $unit->{alias};
1782 13         20 $unit = $alias;
1783             }
1784              
1785             defined $arg->{align_left}
1786             or $arg->{align_left} = _dor( $unit->{align_left},
1787 2662 100       7052 $dim->{align_left} );
1788              
1789             $self->{title}
1790 2662 100       6105 and return $self->_do_title( $arg, $fmtr );
1791              
1792 2004 100       3037 defined $value
1793             or return $self->_format_undef( undef, $arg, $fmtr );
1794              
1795             defined $unit->{method}
1796 1846 100       2993 and do {
1797 8         15 my $method = $unit->{method};
1798 8 100       24 defined( $value = $self->$method( $value ) )
1799             or return $self->_format_undef( undef, $arg, $fmtr );
1800             };
1801              
1802             defined $unit->{factor}
1803 1845 100       3210 and $value *= $unit->{factor};
1804              
1805             defined $unit->{gmt}
1806             and not defined $arg->{gmt}
1807 1845 100 66     3408 and $arg->{gmt} = $unit->{gmt};
1808              
1809 1845         2836 $arg->{units} = $unit_name;
1810              
1811             $value = __localize(
1812             text => [ $fmtr_name, 'localize_value', $value ],
1813             default => $value,
1814             locale => $fmtr->{locale},
1815 1845         6091 );
1816              
1817             defined( my $formatter = _dor( $unit->{formatter},
1818             $fmtr->{dimension}{formatter},
1819             $dim->{formatter},
1820 1845 50       5428 ) )
1821             or $self->weep( "No formatter for $dim_name $unit_name" );
1822              
1823 1845         6075 return $self->$formatter( $value, $arg, $fmtr );
1824             }
1825              
1826             sub _arguments {
1827 2764     2764   5672 my @arg = @_;
1828              
1829 2764         4251 my $obj = shift @arg;
1830 2764 100       6804 my $hash = HASH_REF eq ref $arg[-1] ? pop @arg : {};
1831              
1832 2764         3723 my ( @clean, @append );
1833 2764         4064 foreach my $item ( @arg ) {
1834 1178 100       3154 if ( has_method( $item, 'dereference' ) ) {
1835 637         1894 push @append, $item->dereference();
1836             } else {
1837 541         964 push @clean, $item;
1838             }
1839             }
1840              
1841 2764 100       5927 @clean % 2 and splice @clean, 0, 0, 'title';
1842              
1843 2764         3243 return ( $obj, %{ $hash }, @clean, @append );
  2764         9735  
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   1044 my ( $self, $arg, $fmtr ) = @_;
1913 658         1030 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       1065 or $arg->{title} = '';
1918 658         830 my $title = $arg->{title};
1919             my $wrapped = $self->{internal}{$fmtr_name}{_title}{$title}{$arg->{width}}
1920 658   66     2988 ||= $self->_do_title_wrap( $arg, $fmtr );
1921              
1922 658         1606 return $do_title{$self->{title_gravity}}->( $self, $wrapped,
1923             $arg, $fmtr );
1924             }
1925              
1926             sub is_valid_title_gravity {
1927 786     786 1 1305 my ( @args ) = @_;
1928 786 50       1658 defined( my $value = pop @args )
1929             or return 0;
1930 786 50       2860 return $do_title{$value} ? 1 : 0;
1931             }
1932              
1933             }
1934              
1935             sub _do_title_wrap {
1936 341     341   513 my ( $self, $arg, $fmtr ) = @_;
1937 341         453 my $title = $arg->{title};
1938 341 100       733 $arg->{width} eq ''
1939             and return [ $title ];
1940             $arg->{width}
1941 277 50       481 or return [ '' ];
1942 277         465 local $Text::Wrap::columns = $arg->{width} + 1;
1943 277         410 local $Text::Wrap::huge = 'overflow';
1944 277         819 my $wrap = Text::Wrap::wrap( '', '', $title );
1945 277         73588 my @lines = split qr{ \n }sxm, $wrap;
1946 277         705 return [ map { $self->_format_string( $_, $arg, $fmtr ) } @lines ];
  281         783  
1947             }
1948              
1949             sub __chain_bearing {
1950 188     188   457 my ( undef, undef, $value, $arg ) = @_; # Invocant, $name unused
1951             $arg->{bearing}
1952             and $arg->{bearing} =~ RE_ALL_DIGITS
1953 188 100 66     1381 or $arg->{bearing} = 0;
1954              
1955 188 100       409 $arg->{bearing} or return $arg;
1956              
1957 185 100       462 if ( defined $value ) {
1958 136         206 my $ab = { %{ $arg } }; # Shallow clone
  136         818  
1959 136 100       436 $ab->{width} and $ab->{width} = $ab->{bearing};
1960 136         330 $ab->{units} = 'bearing';
1961 136         342 return ( $arg, $ab );
1962             } else {
1963             $arg->{width}
1964 49 50       161 and $arg->{width} += $arg->{bearing} + 1;
1965 49         103 return $arg;
1966             }
1967             }
1968              
1969             sub _dor {
1970 7694     7694   10589 foreach ( @_ ) {
1971 20923 100       39403 defined $_ and return $_;
1972             }
1973 1100         2141 return $_[-1];
1974             }
1975              
1976             sub _get {
1977 32323     32323   49015 my ( $self, @arg ) = @_;
1978 32323         34158 my $hash = $self;
1979 32323         36159 foreach my $key ( @arg ) {
1980 91813 100       151702 ref $hash or return NONE;
1981 65174 50       82401 defined $key
1982             or $self->weep( 'Undefined key' );
1983 65174         70654 my $ref = reftype( $hash );
1984 65174 50       78545 if ( HASH_REF eq $ref ) {
    0          
    0          
1985 65174         86341 $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         12549 return $hash;
1995             }
1996              
1997             sub _get_eci {
1998 2154     2154   3514 my ( $self, @arg ) = @_;
1999 2154         3208 my $eci = $self->_get( data => @arg );
2000 2154 100       5096 embodies( $eci, 'Astro::Coord::ECI' )
2001             and return $eci;
2002 85         1190 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   77 my ( $self, $method, $body, $station ) = @_;
2016              
2017 41         86 foreach my $thing ( $body, $station ) {
2018 82 50       602 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       510 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         174 return $station->$method( $body );
2032             }
2033              
2034             sub _get_tle {
2035 20     20   54 my ( $self, @arg ) = @_;
2036 20         46 my $tle = $self->_get( data => @arg );
2037 20 100       55 embodies( $tle, 'Astro::Coord::ECI::TLE' )
2038             and return $tle;
2039 14         357 return NONE;
2040             }
2041              
2042             sub _get_tle_attr {
2043 132     132   256 my ( $self, @arg ) = @_;
2044 132         206 my $attr = pop @arg;
2045 132         225 my $tle = $self->_get( data => @arg );
2046 132 100 100     324 embodies( $tle, 'Astro::Coord::ECI::TLE' )
2047             and $tle->attribute( $attr )
2048             or return NONE;
2049 78         2308 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   361 my ( $self, $value, $arg, $fmtr ) = @_;
2064 139 50       338 defined $value
2065             or goto &_format_undef;
2066              
2067 139         184 my $table;
2068              
2069 139         210 foreach my $source ( qw{ default } ) {
2070 139 100       272 $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     690 );
2079              
2080             $arg->{bearing}
2081 139 100 50     408 or $arg->{bearing} = ( $arg->{width} || 2 );
2082             $arg->{width}
2083             and $arg->{bearing} > $arg->{width}
2084 139 50 66     544 and $arg->{bearing} = $arg->{width};
2085              
2086 139   50     363 my $inx = min( $arg->{bearing} || 2, scalar @{ $table } ) - 1;
  139         468  
2087 139         244 my $tags = $table->[$inx];
2088 139         163 my $bins = @{ $tags };
  139         228  
2089 139         518 $inx = floor ($value / TWOPI * $bins + .5) % $bins;
2090 139         465 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   30 my ( $self, $value, $arg, $fmtr ) = @_;
2096              
2097             defined $arg->{align_left}
2098 12 100       32 or $arg->{align_left} = 0;
2099              
2100 12 50       26 defined $value
2101             or goto &_format_undef;
2102              
2103 12         39 my $secs = floor ($value + .5);
2104 12         34 my $mins = floor ($secs / 60);
2105 12         23 $secs %= 60;
2106 12         27 my $hrs = floor ($mins / 60);
2107 12         16 $mins %= 60;
2108 12         23 my $days = floor ($hrs / 24);
2109 12         16 $hrs %= 24;
2110              
2111 12         15 my $buffer;
2112 12 100       41 if ($days > 0) {
2113 1         5 $buffer = sprintf '%d %02d:%02d:%02d', $days, $hrs, $mins, $secs;
2114             } else {
2115 11         46 $buffer = sprintf '%02d:%02d:%02d', $hrs, $mins, $secs;
2116             }
2117              
2118             '' eq $arg->{width}
2119 12 100       40 and return $buffer;
2120              
2121             length $buffer <= $arg->{width}
2122             or $self->{overflow}
2123 10 0 33     23 or return '*' x $arg->{width};
2124              
2125 10 50       33 $arg->{width} - length $buffer
2126             or return $buffer;
2127              
2128 10         28 return $self->_format_string( $buffer, $arg, $fmtr );
2129             }
2130              
2131             # Called as $self->$method()
2132             sub _format_event { ## no critic (ProhibitUnusedPrivateSubroutines)
2133 380     380   732 my ( $self, $value, $arg, $fmtr ) = @_;
2134              
2135 380 50       683 defined $value
2136             or goto &_format_undef;
2137              
2138 380 100 100     1378 isdual( $value )
2139             or $value !~ m/ \D /sxm
2140             or goto &_format_string;
2141              
2142 368         430 my $table;
2143 368 100       797 if ( 'string' ne $arg->{units} ) {
2144 367         569 foreach my $source ( qw{ default } ) {
2145 367 100       587 $table = $self->_get( $source => event => 'table' )
2146             and last;
2147             }
2148             }
2149 368   66     1270 $table ||= __localize(
2150             text => [ event => 'table' ],
2151             default => [],
2152             );
2153              
2154 368   100     1424 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   22 my ( $self, $value, $arg ) = @_; # $fmtr unused
2160 5 50       9 defined $value
2161             or goto &_format_undef;
2162              
2163             $arg->{width}
2164 5 100 66     32 and $arg->{width} =~ RE_ALL_DIGITS
2165             or return sprintf '%d', $value;
2166              
2167 4         15 my $buffer = sprintf '%*d', $arg->{width}, $value;
2168              
2169             length $buffer <= $arg->{width}
2170             or $self->{overflow}
2171 4 0 33     10 or return '*' x $arg->{width};
2172              
2173 4         11 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   1454 my ( $self, $value, $arg, $fmtr ) = @_;
2188 813 50 33     5347 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     4407 ? $arg->{width} : '';
2194 813         1222 my $tplt = "%$width";
2195             defined $arg->{places}
2196 813 50 33     3647 and $arg->{places} =~ RE_ALL_DIGITS
2197             and $tplt .= ".$arg->{places}";
2198              
2199 813 50       1374 '%' eq $tplt
2200             and return "$value";
2201              
2202 813         3956 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         2211 $buffer =~ s/ \A ( \s* ) - ( 0* [.]? 0* \s* ) \z /$1 $2/smx;
2208              
2209 813 100       1619 $width or return $buffer;
2210              
2211 767 100 66     2593 if ($width && length $buffer > $width && $width >= 7) {
      66        
2212 6         16 $arg->{places} = $width - 7;
2213 6         20 return $self->_format_number_scientific( $value, $arg, $fmtr );
2214             }
2215              
2216             length $buffer <= $width
2217             or $self->{overflow}
2218 761 0 33     1326 or return '*' x $width;
2219              
2220 761         2393 return $buffer;
2221             }
2222              
2223             sub _format_number_scientific {
2224 15     15   37 my ( $self, $value, $arg ) = @_; # $fmtr unused
2225 15 50 33     109 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     102 ? $arg->{width} : '';
2231 15         28 my $tplt = "%$width";
2232             defined $arg->{places}
2233 15 50 33     84 and $arg->{places} =~ RE_ALL_DIGITS
2234             and $tplt .= ".$arg->{places}";
2235 15         26 $tplt .= 'e';
2236              
2237 15         92 my $buffer = sprintf $tplt, $value;
2238 15 0 33     50 $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       47 $width
2244             or return $buffer;
2245              
2246             length $buffer <= $width
2247             or $self->{overflow}
2248 9 0 33     25 or return '*' x $width;
2249              
2250 9         31 return $buffer;
2251             }
2252              
2253             # Called as $self->$method()
2254             sub _format_phase { ## no critic (ProhibitUnusedPrivateSubroutines)
2255 5     5   14 my ( $self, $value, $arg, $fmtr ) = @_;
2256 5 50       17 defined $value
2257             or goto &_format_undef;
2258 5         18 my $angle = rad2deg( $value );
2259              
2260 5         24 my $table;
2261 5         17 foreach my $source ( qw{ default } ) {
2262 5 100       29 $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     30 );
2270 5         9 foreach my $entry ( @{ $table } ) {
  5         10  
2271 20 100       35 $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   50 my ( $self, $value, $arg, $fmtr ) = @_;
2280 25 50       82 defined $value
2281             or goto &_format_undef;
2282 25         53 my $sec = $value / PI * 12;
2283 25         57 my $hr = floor($sec);
2284 25         42 $sec = ($sec - $hr) * 60;
2285 25         43 my $min = floor($sec);
2286 25         89 $sec = ($sec - $min) * 60;
2287 25         35 my ( $ps, $wid );
2288 25 50 33     155 if ( defined $arg->{places} && $arg->{places} =~ RE_ALL_DIGITS )
2289             {
2290 25         44 $ps = ".$arg->{places}";
2291 25 100       53 $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       54 or $arg->{align_left} = 0;
2298 25         294 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   3015 my ( $self, $value, $arg ) = @_; # $fmtr unused
2305              
2306 1613 50       2598 defined $value
2307             or goto &_format_undef;
2308              
2309             defined $arg->{width}
2310 1613 100 66     7566 and $arg->{width} =~ RE_ALL_DIGITS
2311             or return "$value";
2312              
2313 1389 100       2606 my $left = defined $arg->{align_left} ? $arg->{align_left} : 1;
2314 1389 100       2303 $left = $left ? '-' : '';
2315              
2316 1389         3978 my $buffer = sprintf "%$left*s", $arg->{width}, $value;
2317              
2318             length $buffer <= $arg->{width}
2319             or $self->{overflow}
2320 1389 50 66     3111 or return substr $buffer, 0, $arg->{width};
2321              
2322 1306         5154 return $buffer;
2323             }
2324              
2325             # Called as $self->$method()
2326             sub _format_time { ## no critic (ProhibitUnusedPrivateSubroutines)
2327 285     285   564 my ( $self, $value, $arg, $fmtr ) = @_;
2328 285 50       541 defined $value
2329             or goto &_format_undef;
2330              
2331 285         438 my $time_fmtr = $self->{time_formatter};
2332 285         1023 $time_fmtr->round_time( $arg->{round_time} );
2333 285         460 my $fmt = $arg->{format};
2334 285 50       531 defined $fmt
2335             or $self->weep( 'No time format' );
2336              
2337             my $buffer = $time_fmtr->format_datetime(
2338 285         1205 $fmt, $value, $arg->{gmt} );
2339 285         18080 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* ) }
2351 0         0 { ucfirst lc $1 }sxmge;
2352 0         0 return $self->_format_string( $value, $arg, $fmtr );
2353             }
2354              
2355             sub _format_undef {
2356 159     159   317 my ( $self, undef, $arg, $fmtr ) = @_; # $value unused
2357              
2358             $self->{title}
2359             and defined $arg->{title}
2360 159 50 33     333 and return $self->_format_string( $arg->{title}, $arg, $fmtr );
2361              
2362             defined $arg->{missing}
2363 159 100       327 and return $self->_format_string( $arg->{missing}, $arg, $fmtr );
2364              
2365             defined $arg->{width}
2366             and $arg->{width} =~ RE_ALL_DIGITS
2367             and $arg->{width}
2368 156 100 66     1076 or return '';
      66        
2369              
2370 123         498 return ' ' x $arg->{width};
2371             }
2372              
2373             # Called as $self->$method()
2374             sub _format_upper_case { ## no critic (ProhibitUnusedPrivateSubroutines)
2375 0     0   0 my ( $self, $value, $arg, $fmtr ) = @_;
2376 0 0       0 defined $value
2377             or goto &_format_undef;
2378              
2379 0         0 return $self->_format_string( uc $value, $arg, $fmtr );
2380             }
2381              
2382             # Called as $self->$method()
2383             sub _julian_day { ## no critic (ProhibitUnusedPrivateSubroutines)
2384 4     4   10 my ( undef, $value ) = @_; # Invocant unused
2385 4         13 return julianday( $value );
2386             }
2387              
2388             sub _get_date_format_data {
2389 780     780   1455 my ( $self, $name, $datum, $info ) = @_;
2390 780   66     2401 $self->{internal}{_date_format}{$name} ||=
2391             $self->_manufacture_date_format( $name, $info );
2392 780         2005 return $self->{internal}{_date_format}{$name}{$datum};
2393             }
2394              
2395             sub _manufacture_date_format {
2396 347     347   656 my ( $self, undef, $info ) = @_; # $name unused
2397 405 50       1729 my $fmt = join ' ', grep { defined $_ && '' ne $_ }
2398 347         498 map { $self->{$_} } @{ $info->{dimension}{format} };
  405         1119  
  347         913  
2399             my $wid =
2400 347         1601 $self->{time_formatter}->format_datetime_width( $fmt );
2401 347         1476 return { format => $fmt, width => $wid };
2402             }
2403              
2404             {
2405              
2406             my %fmt;
2407              
2408             BEGIN {
2409 11     11   50 %fmt = map { $_ => 1 } qw{ date_format time_format };
  22         2959  
2410             }
2411              
2412             sub _valid_time_format_name {
2413 66     66   100 my ( undef, $name ) = @_;
2414 66         648 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             sub _subtract_epoch { ## no critic (ProhibitUnusedPrivateSubroutines)
2442 4     4   8 my ( $self, $value ) = @_;
2443 4         9 my $epoch = $self->_get_tle_attr( body => 'epoch' );
2444 4 100       45 defined $epoch
2445             or return $epoch;
2446 3         8 return $value - $epoch;
2447             }
2448              
2449             sub _variant {
2450 149     149   352 my ( $self, $variant ) = @_;
2451              
2452 149         308 my $data;
2453 149 100       345 if ( defined $variant ) {
2454             $data = HASH_REF eq ref $variant ? $variant : { # Shallow clone
2455 105 100       264 %{ $self->_get( data => $variant ) || {} }
  85 100       263  
2456             };
2457 105         239 foreach my $key ( qw{ station time } ) {
2458 210         379 $data->{$key} = $self->_get( data => $key );
2459             }
2460             } else {
2461 44         83 $data = { %{ $self->_get( 'data' ) } }; # Shallow clone
  44         122  
2462             }
2463              
2464 149         486 return $self->clone( data => $data );
2465             }
2466              
2467             1;
2468              
2469             __END__