Astro-satpass
view release on metacpan or search on metacpan
lib/Astro/Coord/ECI/TLE.pm view on Meta::CPAN
my ($lat, $long, $alt) = $body->geodetic ($time);
It is also possible to run the desired model (as specified by the
C<model> attribute) simply by setting the time represented
by the object.
As of release 0.016, the recommended model to use is SGP4R, which was
added in that release. The SGP4R model, described in "Revisiting
Spacetrack Report #3"
(L<http://celestrak.org/publications/AIAA/2006-6753/>), combines SGP4
and SDP4, and updates them. For the details of the changes, see the
report.
Prior to release 0.016, the recommended model to use was either SGP4 or
SDP4, depending on whether the orbital elements are for a near-earth or
deep-space body. For the purpose of these models, any body with a period
of at least 225 minutes is considered to be a deep-space body.
The NORAD report claims accuracy of 5 or 6 places a day after the epoch
of an element set for the original FORTRAN IV, which used (mostly) 8
place single-precision calculations. Perl typically uses many more
places, but it does not follow that the models are correspondingly more
accurate when implemented in Perl. My understanding is that in general
(i.e. disregarding the characteristics of a particular implementation of
the models involved) the total error of the predictions (including error
in measuring the position of the satellite) runs from a few hundred
meters to as much as a kilometer.
I have no information on the accuracy claims of SGP4R.
This module is a computer-assisted translation of the FORTRAN reference
implementations in "SPACETRACK REPORT NO. 3" and "Revisiting Spacetrack
Report #3." That means, basically, that I ran the FORTRAN through a Perl
script that handled the translation of the assignment statements into
Perl, and then fixed up the logic by hand. Dominik Borkowski's SGP C-lib
was used as a reference implementation for testing purposes, because I
didn't have a Pascal compiler, and I have yet to get any model but SGP
to run correctly under g77.
=head2 Methods
The following methods should be considered public:
=over 4
=cut
package Astro::Coord::ECI::TLE;
use strict;
use warnings;
our $VERSION = '0.134';
use base qw{ Astro::Coord::ECI Exporter };
use Astro::Coord::ECI::Utils qw{ :params :ref :greg_time deg2rad distsq
dynamical_delta embodies find_first_true fold_case
__format_epoch_time_usec
format_space_track_json_time gm_strftime load_module local_strftime
looks_like_number max min
mod2pi PI PIOVER2 rad2deg SECSPERDAY TWOPI thetag
__default_station
@CARP_NOT
};
use Carp qw{carp croak confess};
use Data::Dumper;
use IO::File;
use POSIX qw{ ceil floor fmod modf };
use Scalar::Util ();
BEGIN {
local $@;
eval {require Scalar::Util; Scalar::Util->import ('dualvar'); 1}
or *dualvar = sub {$_[0]};
}
{ # Local symbol block.
my @const = qw{
PASS_EVENT_NONE
PASS_EVENT_SHADOWED
PASS_EVENT_LIT
PASS_EVENT_DAY
PASS_EVENT_RISE
PASS_EVENT_MAX
PASS_EVENT_SET
PASS_EVENT_APPULSE
PASS_EVENT_START
PASS_EVENT_END
PASS_EVENT_BRIGHTEST
PASS_VARIANT_VISIBLE_EVENTS
PASS_VARIANT_FAKE_MAX
PASS_VARIANT_NO_ILLUMINATION
PASS_VARIANT_START_END
PASS_VARIANT_BRIGHTEST
PASS_VARIANT_TRUNCATE
PASS_VARIANT_NONE
BODY_TYPE_UNKNOWN
BODY_TYPE_DEBRIS
BODY_TYPE_ROCKET_BODY
BODY_TYPE_PAYLOAD
};
our @EXPORT_OK = @const;
our %EXPORT_TAGS = (
all => \@EXPORT_OK,
constants => \@const
);
}
use constant RE_ALL_DIGITS => qr{ \A [0-9]+ \z }smx;
# The following constants are from section 12 (Users Guide, Constants,
# and Symbols) of SpaceTrack Report No. 3, Models for Propagation of
# NORAD Element Sets by Felix R. Hoots and Ronald L. Roehrich, December
# 1980, compiled by T. S. Kelso 31 December 1988. The FORTRAN variables
# in the original are defined without the "SGP_" prefix. Were there
# are duplicates (with one commented out), the commented-out version is
# the one in the NORAD report, and the replacement has greater
# precision. If there are two commented out, the second was a greater
# precision constant, and the third is (ultimately) calculated based
lib/Astro/Coord/ECI/TLE.pm view on Meta::CPAN
use constant SGP_CK4 => .62098875E-6;
use constant SGP_E6A => 1.0E-6;
use constant SGP_QOMS2T => 1.88027916E-9;
use constant SGP_S => 1.01222928;
## use constant SGP_TOTHRD => .66666667;
use constant SGP_TOTHRD => 2 / 3;
use constant SGP_XJ3 => -.253881E-5;
use constant SGP_XKE => .743669161E-1;
use constant SGP_XKMPER => 6378.135; # Earth radius, KM.
use constant SGP_XMNPDA => 1440.0; # Time units per day.
use constant SGP_XSCPMN => 60; # Seconds per time unit.
use constant SGP_AE => 1.0; # Distance units / earth radii.
## use constant SGP_DE2RA => .174532925E-1; # radians/degree.
## use constant SGP_DE2RA => 0.0174532925199433; # radians/degree.
use constant SGP_DE2RA => PI / 180; # radians/degree.
## use constant SGP_PI => 3.14159265; # Pi.
## use constant SGP_PI => 3.14159265358979; # Pi.
use constant SGP_PI => PI; # Pi.
## use constant SGP_PIO2 => 1.57079633; # Pi/2.
## use constant SGP_PIO2 => 1.5707963267949; # Pi/2.
use constant SGP_PIO2 => PIOVER2; # Pi/2.
## use constant SGP_TWOPI => 6.2831853; # 2 * Pi.
## use constant SGP_TWOPI => 6.28318530717959; # 2 * Pi.
use constant SGP_TWOPI => TWOPI; # 2 * Pi.
## use constant SGP_X3PIO2 => 4.71238898; # 3 * Pi / 2.
## use constant SGP_X3PIO2 => 4.71238898038469; # 3 * Pi / 2.
use constant SGP_X3PIO2 => 3 * PIOVER2;
use constant SGP_RHO => .15696615;
# FORTRAN variable glossary, read from same source, and stated in
# terms of the output produced by the parse method.
#
# EPOCH => epoch
# XNDT20 => firstderivative
# XNDD60 => secondderivative
# BSTAR => bstardrag
# XINCL => inclination
# XNODE0 => ascendingnode
# E0 => eccentricity
# OMEGA0 => argumentofperigee
# XM0 => meananomaly
# XNO => meanmotion
# List all the legitimate attributes for the purposes of the
# get and set methods. Possible values of the hash are:
# undef => read-only attribute
# 0 => no model re-initializing necessary
# 1 => at least one model needs re-initializing
# code reference - the reference is called with the
# object unmodified, with the arguments
# being the object, the name of the attribute,
# and the new value of the attribute. The code
# must make the needed changes to the attribute, and
# return 0 or 1, interpreted as above.
my %attrib = (
backdate => 0,
effective => sub {
my ($self, $name, $value) = @_;
if ( defined $value && ! looks_like_number( $value ) ) {
if ( $value =~ m{ \A ([0-9]+) / ([0-9]+) / ([0-9]+) : ([0-9]+) :
([0-9]+ (?: [.] [0-9]* )? ) \z }smx ) {
$value = greg_time_gm( 0, 0, 0, 1, 0,
__tle_year_to_Gregorian_year( $1 + 0 ) ) + (
(($2 - 1) * 24 + $3) * 60 + $4) * 60 + $5;
} else {
carp "Invalid effective date '$value'";
$value = undef;
}
}
$self->{$name} = $value;
return 0;
},
classification => 0,
international => \&_set_intldes,
epoch => sub {
$_[0]{$_[1]} = $_[2];
$_[0]{ds50} = $_[0]->ds50 ();
$_[0]{epoch_dynamical} = $_[2] + dynamical_delta ($_[2]);
return 1;
},
firstderivative => 1,
gravconst_r => sub {
($_[2] == 72 || $_[2] == 721 || $_[2] == 84)
or croak "Error - Illegal gravconst_r; must be 72, 721, or 84";
$_[0]{$_[1]} = $_[2];
return 1; # sgp4r needs reinit if this changes.
},
secondderivative => 1,
bstardrag => 1,
ephemeristype => 0,
elementnumber => 0,
inclination => 1,
model => sub {
$_[0]->is_valid_model ($_[2]) || croak <<eod;
Error - Illegal model name '$_[2]'.
eod
$_[0]{$_[1]} = $_[2];
return 0;
},
model_error => 0,
ascendingnode => 1,
eccentricity => 1,
argumentofperigee => 1,
meananomaly => 1,
meanmotion => 1,
revolutionsatepoch => 0,
debug => 0,
geometric => 0, # Use geometric horizon for pass rise/set.
visible => 0, # Pass() reports only illuminated passes.
appulse => 0, # Maximum appulse to report.
interval => 0, # Interval for pass() positions, if positive.
lazy_pass_position => 0, # Position optional if true.
pass_variant => sub {
my ( $self, $name, $val ) = @_;
$val =~ RE_ALL_DIGITS
or croak 'The pass_variant attribute must be an unsigned number';
$self->{$name} = $val;
return 0;
},
ds50 => undef, # Read-only
epoch_dynamical => undef, # Read-only
rcs => 0, # Radar cross-section
tle => undef, # Read-only
file => \&_set_optional_unsigned_integer_no_reinit,
illum => \&_set_illum,
launch_year => \&_set_intldes_part,
launch_num => \&_set_intldes_part,
launch_piece => \&_set_intldes_part,
object_type => \&_set_object_type,
ordinal => \&_set_optional_unsigned_integer_no_reinit,
originator => 0,
pass_threshold => sub {
my ($self, $name, $value) = @_;
not defined $value
or looks_like_number( $value )
or carp "Invalid $name '$value'";
$self->{$name} = $value;
return 0;
},
reblessable => sub {
my $doit = !$_[0]{$_[1]} && $_[2] && $_[0]->get ('id');
$_[0]{$_[1]} = $_[2];
$doit and $_[0]->rebless ();
return 0;
},
intrinsic_magnitude => \&_set_optional_float_no_reinit,
);
my %static = (
appulse => deg2rad (10), # Report appulses < 10 degrees.
backdate => 1, # Use object in pass before its epoch.
geometric => 0, # Use geometric horizon for pass rise/set.
gravconst_r => 72, # Specify geodetic data set for sgp4r.
illum => 'sun',
interval => 0,
lazy_pass_position => 0,
model => 'model',
pass_variant => 0,
reblessable => 1,
visible => 1,
);
my %model_attrib = ( # For the benefit of is_model_attribute()
ds50 => 1, # Read-only, but it fits the definition.
epoch => 1, # Hand-set, since we dont want to call the code.
epoch_dynamical => 1, # Read-only, but fits the definition.
);
foreach (keys %attrib) {
$model_attrib{$_} = 1 if $attrib{$_} && !ref $attrib{$_}
}
my %status; # Subclassing data - initialized at end
my %magnitude_table; # Magnitude data - initialized at end
my $magnitude_adjust = 0; # Adjustment to magnitude table value
use constant TLE_INIT => '_init';
=item $tle = Astro::Coord::ECI::TLE->new()
This method instantiates an object to represent a NORAD two- or
three-line orbital element set. This is a subclass of
L<Astro::Coord::ECI|Astro::Coord::ECI>.
Any arguments get passed to the set() method.
It is both anticipated and recommended that you use the parse()
method instead of this method to create an object, since the models
currently have no code to guard against incomplete data.
=cut
sub new {
my $class = shift;
my $self = $class->SUPER::new (%static, @_);
return $self;
}
=item $tle->after_reblessing (\%possible_attributes)
lib/Astro/Coord/ECI/TLE.pm view on Meta::CPAN
=item $kilometers = $tle->apoapsis();
This method returns the apoapsis of the orbit, in kilometers. Since
Astro::Coord::ECI::TLE objects always represent bodies orbiting the
Earth, this is more usually called apogee.
Note that this is the distance from the center of the Earth, not the
altitude.
=cut
sub apoapsis {
my $self = shift;
return $self->{&TLE_INIT}{TLE_apoapsis} ||=
(1 + $self->get('eccentricity')) * $self->semimajor();
}
=item $kilometers = $tle->apogee();
This method is simply a synonym for apoapsis().
=cut
*apogee = \&apoapsis;
# See Astro::Coord::ECI for docs.
sub attribute {
return exists $attrib{$_[1]} ?
__PACKAGE__ :
$_[0]->SUPER::attribute ($_[1])
}
=item $tle->before_reblessing ()
This method supports reblessing into a subclass. It is intended to do
any cleanup the old class needs before reblessing into the new class. It
is called by rebless(), and should not be called by the user.
At this level it does nothing.
=cut
sub before_reblessing {}
=item $type = $tle->body_type ()
This method returns the type of the body as one of the BODY_TYPE_*
constants. This is the C<'object_type'> attribute if that is defined.
Otherwise it is derived from the common name using an algorithm similar
to the one used by the Space Track web site. This algorithm will not
work if the common name is not available, or if it does not conform to
the Space Track naming conventions. Known or suspected differences from
the algorithm described at the bottom of the Satellite Box Score page
include:
* The C<Astro::Coord::ECI::TLE> algorithm is not case-sensitive. The
Space Track algorithm appears to assume all upper-case.
* The C<Astro::Coord::ECI::TLE> algorithm looks for words (that is,
alphanumeric strings delimited by non-alphanumeric characters), whereas
the Space Track documentation seems to say it just looks for substrings.
However, implementing the documented algorithm literally results in OID
20479 'DEBUT (ORIZURU)' being classified as debris, whereas Space Track
returns it in response to a query for name 'deb' that excludes debris.
The possible returns are:
C<< BODY_TYPE_UNKNOWN => dualvar( 0, 'unknown' ) >> if the value of the
C<name> attribute is C<undef>, or if it is empty or contains only
white space.
C<< BODY_TYPE_DEBRIS => dualvar( 1, 'debris' ) >> if the value of the
C<name> attribute contains one of the words 'deb', 'debris', 'coolant',
'shroud', or 'westford needles', all checks being case-insensitive.
C<< BODY_TYPE_ROCKET_BODY => dualvar( 2, 'rocket body' ) >> if the body
is not debris, but the value of the C<name> attribute contains one of
the strings 'r/b', 'akm' (for 'apogee kick motor') or 'pkm' (for
'perigee kick motor') all checks being case-insensitive.
C<< BODY_TYPE_PAYLOAD => dualvar( 3, 'payload' ) >> if the body is not
unknown, debris, or a rocket body.
The above constants are not exported by default, but they are exportable
either by name or using the C<:constants> tag.
If L<Scalar::Util|Scalar::Util> does not export C<dualvar()>, the
constants are defined to be numeric. The cautious programmer will
therefore test them using numeric tests.
=cut
use constant BODY_TYPE_UNKNOWN => dualvar( 0, 'unknown' );
use constant BODY_TYPE_DEBRIS => dualvar( 1, 'debris' );
use constant BODY_TYPE_ROCKET_BODY => dualvar( 2, 'rocket body' );
use constant BODY_TYPE_PAYLOAD => dualvar( 3, 'payload' );
sub body_type {
my ( $self ) = @_;
my $type;
$type = $self->get( 'object_type' )
and return $type;
defined( my $name = $self->get( 'name' ) )
or return BODY_TYPE_UNKNOWN;
$name =~ m/ \A \s* \z /smx
and return BODY_TYPE_UNKNOWN;
( $name =~ m/ \b deb \b /smxi
|| $name =~ m/ \b debris \b /smxi
|| $name =~ m/ \b coolant \b /smxi
|| $name =~ m/ \b shroud \b /smxi
|| $name =~ m/ \b westford \s+ needles \b /smxi )
and return BODY_TYPE_DEBRIS;
( $name =~ m{ \b r/b \b }smxi
|| $name =~ m/ \b [ap] km \b /smxi )
and return BODY_TYPE_ROCKET_BODY;
return BODY_TYPE_PAYLOAD;
}
=item $tle->can_flare ()
This method returns true if the object is capable of generating flares
lib/Astro/Coord/ECI/TLE.pm view on Meta::CPAN
magnitude table with the contents of the named Molczan-format file. The
C<$file_name> argument can also be a scalar reference with the scalar
containing the data, or an open handle. The C<$mag_offset> is an
adjustment to be added to the magnitudes read from the file, and
defaults to 0.
C<magnitude_table( quicksat => $file_name, $mag_offset )> replaces the
magnitude table with the contents of the named Quicksat-format file. The
C<$file_name> argument can also be a scalar reference with the scalar
containing the data, or an open handle. The C<$mag_offset> is an
adjustment to be added to the magnitudes read from the file, and
defaults to 0. In addition to this value, C<0.7> is added to the
magnitude before storage to adjust the data from full-phase to
half-phase.
C<magnitude_table( show => ... )> returns an array which is a slice of
the magnitude table, which is stored as a hash. In other words, it
returns OID/magnitude pairs in no particular order. If any further
arguments are passed, they are the OIDs to return. Otherwise all are
returned.
Examples of Molczan-format data are contained in F<mcnames.zip> and
F<vsnames.zip> available on Mike McCants' web site; these can be fetched
using the L<Astro::SpaceTrack|Astro::SpaceTrack> C<mccants()> method. An
example of Quicksat-format data is contained in F<qsmag.zip>. See Mike
McCants' web site, L<https://www.mmccants.org//> for an
explanation of the differences.
Note that if you have one of the reported pure Perl versions of
L<Scalar::Util|Scalar::Util>, you can not pass open handles to
functionality that would otherwise accept them.
=cut
{
my $openhandle = Scalar::Util->can( 'openhandle' ) || sub { return };
my $parse_file = sub {
my ( $file_name, $mag_offset, $parse_info ) = @_;
defined $mag_offset
or $mag_offset = 0;
$mag_offset += $parse_info->{mag_offset};
my %mag;
my $fh;
if ( $openhandle->( $file_name ) ) {
$fh = $file_name;
} else {
open $fh, '<', $file_name ## no critic (RequireBriefOpen)
or croak "Failed to open $file_name: $!";
}
local $_ = undef; # while (<>) ... does not localize $_.
while ( <$fh> ) {
chomp;
m/ \A \s* (?: \# | \z ) /smx
and next; # Extension to syntax.
$parse_info->{pad} > length
and $_ = sprintf '%-*s', $parse_info->{pad}, $_;
# Perl 5.8 and below require an explicit buffer to unpack.
my ( $id, $mag ) = unpack $parse_info->{template}, $_;
$mag =~ s/ \s+ //smxg;
looks_like_number( $mag )
or next;
$mag{ _normalize_oid( $id ) } = $mag + $parse_info->{mag_offset};
}
close $fh;
%magnitude_table = %mag;
};
my %cmd_def = (
add => sub {
my ( $id, $mag ) = @_;
defined $id
and $id =~ m/ \A [0-9]+ \z /smx
and defined $mag
and looks_like_number( $mag )
or croak 'magnitude_table add needs an OID and a magnitude';
$magnitude_table{ _normalize_oid( $id ) } = $mag;
return;
},
adjust => sub {
my ( $adj ) = @_;
if ( defined $adj ) {
looks_like_number( $adj )
or croak 'magnitude_table adjust needs a floating point number';
$magnitude_adjust = $adj;
return;
} else {
return $magnitude_adjust;
}
},
clear => sub {
%magnitude_table = ();
return;
},
dump => sub {
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Sortkeys = 1;
print Dumper( \%magnitude_table );
return;
},
drop => sub {
my ( $id ) = @_;
defined $id
and $id =~ m/ \A [0-9]+ \z /smx
or croak 'magnitude_table drop needs an OID';
delete $magnitude_table{ _normalize_oid( $id ) };
return;
},
magnitude => sub {
my ( $tbl ) = @_;
HASH_REF eq ref $tbl
or croak 'magnitude_table magnitude needs a hash ref';
my %mag;
foreach my $key ( keys %{ $tbl } ) {
my $val = $tbl->{$key};
$key =~ m/ \A [0-9]+ \z /smx
or croak "OID '$key' must be numeric";
looks_like_number( $val )
or croak "Magnitude '$val' must be numeric";
$mag{ _normalize_oid( $key ) } = $val;
}
%magnitude_table = %mag;
return;
},
molczan => sub {
my ( $file_name, $mag_factor ) = @_;
$parse_file->( $file_name, $mag_factor, {
mag_offset => 0,
pad => 49,
template => 'a5x32a5',
} );
return;
},
quicksat => sub {
my ( $file_name, $mag_factor ) = @_;
$parse_file->( $file_name, $mag_factor, {
mag_offset => 0.7,
pad => 56,
template => 'a5x28a5',
} );
return;
},
show => sub {
my ( @arg ) = @_;
@arg
or return %magnitude_table;
return (
map { $_ => $magnitude_table{$_} }
grep { defined $magnitude_table{$_} }
map { _normalize_oid( $_ ) } @arg
);
},
);
sub magnitude_table {
my ( undef, $cmd, @arg ) = @_; # Invocant not used
my $code = $cmd_def{$cmd}
or croak "'$cmd' is not a valid magnitude_table subcommand";
return $code->( @arg );
}
}
=item $time = $tle->max_effective_date(...);
This method returns the maximum date among its arguments and the
effective date of the $tle object as set in the C<effective> attribute,
if that is defined. If no effective date is set but the C<backdate>
attribute is false, the C<epoch> of the object is used as the effective
date. If there are no arguments and no effective date, C<undef> is
returned.
=cut
sub max_effective_date {
my ($self, @args) = @_;
if (my $effective = $self->get('effective')) {
push @args, $effective;
} elsif (!$self->get('backdate')) {
lib/Astro/Coord/ECI/TLE.pm view on Meta::CPAN
$args[3] *= (SGP_XKMPER / SGP_AE * SGP_XMNPDA / SECSPERDAY); # dx/dt
$args[4] *= (SGP_XKMPER / SGP_AE * SGP_XMNPDA / SECSPERDAY); # dy/dt
$args[5] *= (SGP_XKMPER / SGP_AE * SGP_XMNPDA / SECSPERDAY); # dz/dt
$self->__universal( pop @args );
$self->eci (@args);
$self->equinox_dynamical ($self->{epoch_dynamical});
return $self;
}
# Called by pass() to find the illumination. The arguments are the sun
# object (or nothing), the time, and a reference to the pass information
# hash. The return is either nothing (if $sun is not defined) or
# ( illumination => $illum ).
sub _find_illumination {
my ( $sun, $when, $info ) = @_;
$sun
or return;
my $illum = $info->[0]{illumination};
foreach my $evt ( @{ $info } ) {
$evt->{time} > $when
and last;
$illum = $evt->{illumination};
}
return ( illumination => $illum );
}
# Called by pass() to calculate azimuth, elevation, and range. The
# arguments are the TLE object, the station object, and the time. If the
# TLE's 'lazy_pass_position' attribute is true, nothing is returned.
# Otherwise the azimuth, elevation, and range are calculated and
# returned as three name/value pairs (i.e. a six-element list).
sub _find_position {
my ( $tle, $sta, $when ) = @_;
$tle->get( 'lazy_pass_position' )
and return;
$tle->universal( $when );
my ( $azimuth, $elevation, $range ) = $sta->azel( $tle );
return (
azimuth => $azimuth,
elevation => $elevation,
range => $range,
);
}
# Initial value of the 'inertial' attribute. TLEs are assumed to be
# inertial until set otherwise.
sub __initial_inertial{ return 1 };
# Unsupported, experimental, and subject to change or retraction without
# notice. The intent is to provide a way for the Astro::App::Satpass2
# 'list' command to pick an appropriate template to format each line of
# the listing based on the object being listed.
sub __list_type {
my ( $self ) = @_;
return $self->{inertial} ? 'inertial' : 'fixed';
}
# _looks_like_real
#
# This returns a boolean which is true if the input looks like a real
# number and is false otherwise. It is based on looks_like_number, but
# excludes things like NaN, and Inf.
sub _looks_like_real {
my ( $number ) = @_;
looks_like_number( $number )
or return;
$number =~ m/ \A nan \z /smxi
and return;
$number =~ m/ \A [+-]? inf (?: inity )? \z /smxi
and return;
return 1;
}
# *equinox_dynamical = \&Astro::Coord::ECI::equinox_dynamical;
# $text = $self->_make_tle();
#
# This method manufactures a TLE. It's a 'real' TLE if the 'name'
# attribute is not set, and a 'NASA' TLE (i.e. the 'T' stands for
# 'three') if the 'name' attribute is set. The output is intended
# to be equivalent to the TLE (if any) that initialized the
# object, not identical to it. This method is used to manufacture
# a TLE in the case where $self->get('tle') was called but the
# object was not initialized by the parse() method.
{
my %hack = (
effective => sub {
## my ( $self, $name, $value ) = @_;
my ( undef, undef, $value ) = @_; # Invocant & name unused
my $whole = floor($value);
my ($sec, $min, $hr, undef, undef, $year, undef, $yday) =
gmtime $value;
my $effective =
sprintf '%04d/%03d/%02d:%02d:%06.3f',
$year + 1900, $yday + 1, $hr, $min,
$sec + ($value - $whole);
$effective =~ s/ [.]? 0+ \z //smx;
return ( '--effective', $effective );
},
rcs => sub {
## my ( $self, $name, $value ) = @_;
my ( undef, undef, $value ) = @_; # Invocant & name unused
return ( '--rcs', $value );
},
);
my @required_fields = qw{
firstderivative secondderivative bstardrag inclination
ascendingnode eccentricity argumentofperigee meananomaly
meanmotion revolutionsatepoch
};
sub _make_tle {
my $self = shift;
my $output;
my $oid = $self->get('id');
my $name = $self->get( 'name' );
my @line0;
if ( defined $name ) {
$name =~ s/ \s+ \z //smx;
$name ne ''
lib/Astro/Coord/ECI/TLE.pm view on Meta::CPAN
foreach my $key ( qw{ launch_year launch_num launch_piece } ) {
$intldes{$key} = $value_or_empty->( $self, $key );
}
$intldes{launch_year} eq ''
or $intldes{launch_year} %= 100;
my $tplt = join '',
( $intldes{launch_year} eq '' ? '%2s' : '%02d' ),
( $intldes{launch_num} eq '' ? '%3s' : '%03d' ),
'%s';
$self->{international} = sprintf $tplt, map { $intldes{$_} }
qw{ launch_year launch_num launch_piece };
return 0;
}
}
# _set_object_type
#
# This acts as a mutator for the object type.
{
my %name_to_type;
my @number_to_type;
foreach my $type (
BODY_TYPE_UNKNOWN,
BODY_TYPE_DEBRIS,
BODY_TYPE_ROCKET_BODY,
BODY_TYPE_PAYLOAD,
) {
$number_to_type[$type] = $type;
$name_to_type{ fold_case( $type ) } = $type;
}
sub _set_object_type {
my ( $self, $name, $value ) = @_;
if ( defined $value ) {
if ( $value =~ RE_ALL_DIGITS ) {
$self->{$name} = $number_to_type[$value];
} else {
$self->{$name} = $name_to_type{ fold_case( $value ) };
}
unless ( defined $self->{$name} ) {
carp "Invalid $name '$value'; setting to unknown";
$self->{$name} = BODY_TYPE_UNKNOWN;
}
} else {
$self->{$name} = undef;
}
return 0;
}
}
# _set_optional_float_no_reinit
#
# This acts as a mutator for any attribute whose value is either undef
# or a floating-point number, and which does not cause the model to be
# renitialized when its value changes. We disallow NaN.
sub _set_optional_float_no_reinit {
my ( $self, $name, $value ) = @_;
if ( defined $value && ! _looks_like_real( $value ) ) {
carp "Invalid $name '$value'; must be a float or undef";
$value = undef;
}
$self->{$name} = $value;
return 0;
}
# _set_optional_unsigned_integer_no_reinit
#
# This acts as a mutator for any attribute whose value is either undef
# or an unsigned integer, and which does not cause the model to be
# reinitialized when its value changes.
sub _set_optional_unsigned_integer_no_reinit {
my ( $self, $name, $value ) = @_;
if ( defined $value && $value =~ m/ [^0-9] /smx ) {
carp "Invalid $name '$value'; must be unsigned integer or undef";
$value = undef;
}
$self->{$name} = $value;
return 0;
}
sub _next_elevation_screen {
my ( $sta, $pass_step, @args ) = @_;
ref $sta
or confess 'Programming error - station not a reference';
my ( $suntim, $dawn ) = $sta->next_elevation( @args );
defined $suntim
or confess 'Programming error - time of next elevation undefined';
$dawn or $pass_step = - $pass_step;
my $sun_screen = $suntim + $pass_step / 2;
return ( $suntim, $dawn, $sun_screen,
$dawn ? $sun_screen : $suntim,
);
}
#######################################################################
# Initialization of aliases and status
{
# The following classes initialize themselves on load.
local $@ = undef;
eval { ## no critic (RequireCheckingReturnValueOfEval)
require Astro::Coord::ECI::TLE::Iridium;
};
}
# $$ BEGIN magnitude_table
# The following is all the Celestrak visual list that have magnitudes in
# Heavens Above. These data are generated by the following:
#
# $ tools/heavens-above-mag --celestrak --update
#
# Last-Modified: Sun, 07 Sep 2025 20:59:52 GMT
%magnitude_table = (
'00694' => 2.7, # ATLAS CENTAUR 2 R/B
( run in 0.834 second using v1.01-cache-2.11-cpan-0bb4e1dffa6 )