@@ -1,3 +1,30 @@
+0.065 2014-07-27 T. R. Wyant
+ No changes since 0.064_03.
+
+0.064_03 2014-07-20 T. R. Wyant
+ Another cut at preventing TLE manufacture for oddball objects --
+ hopefully one with fewer false negatives.
+
+0.064_02 2014-07-18 T. R. Wyant
+ Prevent TLE manufacture for oddball objects -- or at least try to.
+
+0.064_01 2014-06-30 T. R. Wyant
+ Add TLE pass variant PASS_VARIANT_TRUNCATE, which truncates passes to
+ the beginning and end times given to pass(), rather then computing
+ complete passes (and therefore requiring the satellite to rise and
+ set).
+ Expose Space Track time formatting and decoding routines for possible
+ use by subclasses. The key routines __to_json() and __from_json()
+ are still undocumented and considered highly experimental, though.
+ Sorry.
+ Add OID 00694 to Astro::Coord::ECI::TLE canned magnitudes.
+
+0.064 2014-05-29 T. R. Wyant
+ No changes from 0.063_01.
+
+0.063_01 2014-05-21 T. R. Wyant
+ Add Astro::Coord::ECI::TLE method illuminated()
+
0.063 2014-05-02 T. R. Wyant
Allow PASS_VARIANT_BRIGHTEST event even if the 'visible' attribute is
false.
@@ -4,7 +4,7 @@
"Tom Wyant (wyant at cpan dot org)"
],
"dynamic_config" : 1,
- "generated_by" : "Module::Build version 0.4205",
+ "generated_by" : "Module::Build version 0.4206",
"license" : [
"perl_5"
],
@@ -45,39 +45,39 @@
"provides" : {
"Astro::Coord::ECI" : {
"file" : "lib/Astro/Coord/ECI.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::Mixin" : {
"file" : "lib/Astro/Coord/ECI/Mixin.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::Moon" : {
"file" : "lib/Astro/Coord/ECI/Moon.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::Star" : {
"file" : "lib/Astro/Coord/ECI/Star.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::Sun" : {
"file" : "lib/Astro/Coord/ECI/Sun.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::TLE" : {
"file" : "lib/Astro/Coord/ECI/TLE.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::TLE::Iridium" : {
"file" : "lib/Astro/Coord/ECI/TLE/Iridium.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::TLE::Set" : {
"file" : "lib/Astro/Coord/ECI/TLE/Set.pm",
- "version" : "0.063"
+ "version" : "0.065"
},
"Astro::Coord::ECI::Utils" : {
"file" : "lib/Astro/Coord/ECI/Utils.pm",
- "version" : "0.063"
+ "version" : "0.065"
}
},
"release_status" : "stable",
@@ -89,5 +89,5 @@
"http://dev.perl.org/licenses/"
]
},
- "version" : "0.063"
+ "version" : "0.065"
}
@@ -5,7 +5,7 @@ author:
build_requires:
Test::More: '0.88'
dynamic_config: 1
-generated_by: 'Module::Build version 0.4205, CPAN::Meta::Converter version 2.140640'
+generated_by: 'Module::Build version 0.4206, CPAN::Meta::Converter version 2.141520'
license: perl
meta-spec:
url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -19,31 +19,31 @@ no_index:
provides:
Astro::Coord::ECI:
file: lib/Astro/Coord/ECI.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::Mixin:
file: lib/Astro/Coord/ECI/Mixin.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::Moon:
file: lib/Astro/Coord/ECI/Moon.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::Star:
file: lib/Astro/Coord/ECI/Star.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::Sun:
file: lib/Astro/Coord/ECI/Sun.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::TLE:
file: lib/Astro/Coord/ECI/TLE.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::TLE::Iridium:
file: lib/Astro/Coord/ECI/TLE/Iridium.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::TLE::Set:
file: lib/Astro/Coord/ECI/TLE/Set.pm
- version: '0.063'
+ version: '0.065'
Astro::Coord::ECI::Utils:
file: lib/Astro/Coord/ECI/Utils.pm
- version: '0.063'
+ version: '0.065'
requires:
Carp: '0'
Data::Dumper: '0'
@@ -59,4 +59,4 @@ requires:
resources:
bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=Astro-satpass
license: http://dev.perl.org/licenses/
-version: '0.063'
+version: '0.065'
@@ -37,7 +37,7 @@ $clipboard_unavailable = $IO::Clipboard::clipboard_unavailable;
# Initialization
#
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use constant LINFMT => <<eod;
%s %s %8.4f %9.4f %7.1f %-4s %s
@@ -1518,7 +1518,8 @@ sub magnitude_table {
BEGIN {
foreach my $code (PASS_EVENT_NONE, PASS_EVENT_SHADOWED,
PASS_EVENT_LIT, PASS_EVENT_DAY, PASS_EVENT_RISE,
- PASS_EVENT_MAX, PASS_EVENT_SET, PASS_EVENT_BRIGHTEST ) {
+ PASS_EVENT_MAX, PASS_EVENT_SET, PASS_EVENT_START,
+ PASS_EVENT_END, PASS_EVENT_BRIGHTEST ) {
my $val = ucfirst $code;
$event_name[$code] = sub {$val};
}
@@ -1528,7 +1529,7 @@ sub magnitude_table {
$_[0]{appulse}{body}->get ('id')};
$cmdlgl{pass} = [ qw{ choose=s@ dump flatten
- magnitude|brightest quiet } ];
+ magnitude|brightest quiet truncate! } ];
}
sub pass {
@@ -1571,7 +1572,9 @@ eod
my $appulse = deg2rad ($parm{appulse});
my $pass_threshold = deg2rad( $parm{pass_threshold} );
my $pass_variant = $cmdopt{magnitude} ? PASS_VARIANT_BRIGHTEST :
- PASS_VARIANT_NONE;
+ PASS_VARIANT_NONE;
+ $cmdopt{truncate}
+ and $pass_variant |= PASS_VARIANT_TRUNCATE;
my $line_format = $cmdopt{magnitude} ? LINFMT_MAG : LINFMT;
# Print the header
@@ -1790,10 +1793,11 @@ while ($time <= $endtm) {
my $name = $body->get ('name') || $body->get ('id') || 'body';
$name = substr $name, 0, 16;
if ($body->represents ('Astro::Coord::ECI::TLE')) {
- my $illum = ($sta->azel ($body))[1] < 0 ? 10 :
- ($sta->azel ($sun->universal ($time)))[1] > $twilight ? 2 :
-## ($body->azel ($sun))[1] > $body->dip() ? 1 : 0;
- $body->__sun_elev_from_sat( $time ) >= 0 ? 1 : 0;
+ my $illum = $body->illuminated() ? 1 : 0;
+ $illum
+ and ( $sta->azel( $body ) )[1] >= 0
+ and ( $sta->azel($sun->universal( $time ) ) )[1] > $twilight
+ and $illum = 2;
my $line;
if ( $cmdopt{magnitude} ) {
my $mag = $body->magnitude( $sta );
@@ -17,7 +17,7 @@ use Pod::Usage;
use POSIX qw{strftime};
use Time::Local;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
Getopt::Long::Configure( 'pass_through' ); # Numbers may be negative.
@@ -12,7 +12,7 @@ use Astro::Coord::ECI;
use Astro::Coord::ECI::Sun;
use Astro::Coord::ECI::Utils qw{ deg2rad rad2deg };
-our $VERSION = '0.063';
+our $VERSION = '0.065';
my $time = time;
@@ -23,7 +23,7 @@ use Getopt::Long 2.33;
use Pod::Usage;
use POSIX qw{strftime};
-our $VERSION = '0.063';
+our $VERSION = '0.065';
my %opt;
@@ -9,7 +9,7 @@ use Astro::Coord::ECI::TLE;
use Getopt::Long 2.33;
use Pod::Usage;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
my %opt;
@@ -18,7 +18,7 @@ use Astro::Coord::ECI::Utils qw(deg2rad rad2deg SECSPERDAY);
use Astro::SpaceTrack;
use POSIX qw{strftime};
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use constant TIMFMT => '%d %b %H:%M:%S';
@@ -15,7 +15,7 @@ use Pod::Usage;
use POSIX qw{ strftime };
use Time::Local;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
# Map -want-events values to TLE event codes.
my %wanted_event_map = (
@@ -12,7 +12,7 @@ use HTTP::Date;
use LWP::UserAgent;
use Pod::Usage;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
my %opt;
@@ -79,6 +79,11 @@ if ( $opt{merge} ) {
$output =~ s/ [}] \s* \z /);\n/smx;
$output =~ s/ ' ( -? \d+ [.] \d+ ) ' /$1/smxg;
$output =~ s/ (?<= \d ) (?: (?= \n ) | \z ) /,/smxg;
+ print <<"EOD";
+# Last-Modified: @{[ time2str( $date ) ]}
+
+EOD
+
print $output;
} else {
foreach my $oid ( sort { $a <=> $b } keys %found ) {
@@ -86,8 +91,8 @@ if ( $opt{merge} ) {
and next;
say "$oid $sources[$found{$oid}]";
}
+ say 'Last-Modified: ', time2str( $date );
}
-say 'Last-Modified: ', time2str( $date );
sub last_modified {
my ( $resp ) = @_;
@@ -14,7 +14,7 @@ use Getopt::Long 2.33;
use Pod::Usage;
use XML::Writer;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
my %opt = (
latitude => 52.069858, # Degrees north of Equator
@@ -5,7 +5,7 @@ use 5.006002;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use base qw{ Exporter };
@@ -11,7 +11,7 @@ use Astro::Coord::ECI::Utils qw{ __default_station PIOVER2 SECSPERDAY };
use Exporter ();
use POSIX qw{ floor };
-our $VERSION = '0.063';
+our $VERSION = '0.065';
our @EXPORT_OK = qw{
almanac almanac_hash
@@ -43,7 +43,7 @@ package Astro::Coord::ECI::Moon;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use base qw{Astro::Coord::ECI};
@@ -483,7 +483,7 @@ eod
# The moon is normally positioned in inertial coordinates.
-sub _initial_inertial { return 1 }
+sub __initial_inertial { return 1 }
1;
@@ -50,7 +50,7 @@ package Astro::Coord::ECI::Star;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use base qw{Astro::Coord::ECI};
@@ -50,7 +50,7 @@ package Astro::Coord::ECI::Sun;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use base qw{Astro::Coord::ECI};
@@ -457,7 +457,7 @@ eod
# The Sun is normally positioned in inertial coordinates.
-sub _initial_inertial { return 1 }
+sub __initial_inertial { return 1 }
1;
@@ -121,7 +121,7 @@ use warnings;
use base qw{Astro::Coord::ECI::TLE};
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use Astro::Coord::ECI::Sun;
use Astro::Coord::ECI::Utils qw{:all};
@@ -140,7 +140,7 @@ our @CARP_NOT = qw{
Astro::Coord::ECI
};
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use constant ERR_NOCURRENT => <<eod;
Error - Can not call %s because there is no current member. Be
@@ -229,14 +229,15 @@ package Astro::Coord::ECI::TLE;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use base qw{ Astro::Coord::ECI Exporter };
use Astro::Coord::ECI::Utils qw{ :params :time deg2rad distsq
- dynamical_delta embodies find_first_true fold_case load_module
- looks_like_number max min mod2pi PI PIOVER2 rad2deg
- SECSPERDAY TWOPI thetag __default_station };
+ dynamical_delta embodies find_first_true fold_case
+ format_space_track_json_time load_module looks_like_number max min
+ mod2pi PI PIOVER2 rad2deg SECSPERDAY TWOPI thetag __default_station
+ };
use Carp qw{carp croak confess};
use Data::Dumper;
@@ -268,6 +269,7 @@ BEGIN {
PASS_VARIANT_NO_ILLUMINATION
PASS_VARIANT_START_END
PASS_VARIANT_BRIGHTEST
+ PASS_VARIANT_TRUNCATE
PASS_VARIANT_NONE
BODY_TYPE_UNKNOWN
BODY_TYPE_DEBRIS
@@ -769,6 +771,18 @@ L</Attributes> section for a description of the attributes.
}
}
+=item $illuminated = $tle->illuminated();
+
+This method returns a true value if the body is illuminated, and a false
+value if it is not.
+
+=cut
+
+sub illuminated {
+ my ( $self, $time ) = @_;
+ return $self->__sun_elev_from_sat( $time ) >= 0;
+}
+
=item @events = $tle->intrinsic_events( $start, $end );
This method returns any events that are intrinsic to the C<$tle> object.
@@ -893,8 +907,8 @@ sub magnitude {
my $time = $self->universal();
# If the illuminating body is below the horizon, we return undef.
- $self->__sun_elev_from_sat( $time ) < 0
- and return undef; ## no critic (ProhibitExplicitReturnUndef)
+ $self->illuminated()
+ or return undef; ## no critic (ProhibitExplicitReturnUndef)
# Compute the range amd the elevation.
my ( undef, $elev, $range ) = $sta->universal( $time )->azel( $self );
@@ -1385,7 +1399,8 @@ This method returns passes of the body over the given station between
the given start end end times. The \@sky argument is background bodies
to compute appulses with (see note 3).
-A pass is detected by this method when the body sets. This means that
+A pass is detected by this method when the body sets. Unless
+C<PASS_VARIANT_TRUNCATE> (see below) is in effect, this means that
passes are not usefully detected for geosynchronous or
near-geosynchronous bodies, and that passes where the body sets after
the C<$end> time will not be detected.
@@ -1545,13 +1560,15 @@ use constant PASS_VARIANT_FAKE_MAX => 0x02;
use constant PASS_VARIANT_START_END => 0x04;
use constant PASS_VARIANT_NO_ILLUMINATION => 0x08;
use constant PASS_VARIANT_BRIGHTEST => 0x10;
+use constant PASS_VARIANT_TRUNCATE => 0x20;
use constant PASS_VARIANT_NONE => 0x00; # Must be 0.
my @pass_variant_mask = (
PASS_VARIANT_NO_ILLUMINATION | PASS_VARIANT_START_END |
- PASS_VARIANT_BRIGHTEST,
+ PASS_VARIANT_BRIGHTEST | PASS_VARIANT_TRUNCATE,
PASS_VARIANT_VISIBLE_EVENTS | PASS_VARIANT_FAKE_MAX |
- PASS_VARIANT_START_END | PASS_VARIANT_BRIGHTEST,
+ PASS_VARIANT_START_END | PASS_VARIANT_BRIGHTEST |
+ PASS_VARIANT_TRUNCATE,
);
use constant SCREENING_HORIZON_OFFSET => deg2rad( -3 );
@@ -1563,14 +1580,35 @@ use constant SCREENING_HORIZON_OFFSET => deg2rad( -3 );
*_nodelegate_pass = \&pass;
+# The following method is not supported, and may be changed or retracted
+# at any time without notice. Its purpose in life is to provide a handle
+# by which the experimental and unreleased Astro::Coord::ECI::Points
+# objects can manipulate the the start and end times of the pass
+# calculation.
+sub __default_pass_times {
+ my ( $self, $start, $end ) = @_;
+ defined $start
+ or $start = time;
+ defined $end
+ or $end = $start + 7 * SECSPERDAY;
+ return ( $start, $end );
+}
+
sub pass {
my @args = __default_station( @_ );
my @sky;
ref $args[-1] eq 'ARRAY' and @sky = @{pop @args};
my $tle = shift @args;
my $sta = shift @args;
- my $pass_start = shift @args || time ();
- my $pass_end = shift @args || $pass_start + 7 * SECSPERDAY;
+
+ # We give subclasses a way of specifying their own default times. If
+ # an undefined end time is returned, the subclass is stating that
+ # there are no passes in the given range, and we simply return.
+ my ( $pass_start, $pass_end ) = $tle->__default_pass_times(
+ splice @args, 0, 2 );
+ defined $pass_start
+ or return;
+
$pass_end >= $pass_start or croak <<eod;
Error - End time must be after start time.
eod
@@ -1596,10 +1634,10 @@ eod
$pass_variant_mask[ $want_visible ? 1 : 0 ];
defined $tle->get( 'intrinsic_magnitude' )
or $pass_variant &= ~ PASS_VARIANT_BRIGHTEST;
+ my $truncate = $pass_variant & PASS_VARIANT_TRUNCATE;
defined $pass_threshold
and $pass_threshold > $horizon
or $pass_threshold = $horizon;
- my $pass_backup_earliest = $tle->__pass_backup_earliest();
# We need the number of radians the satellite travels in a minute so
# we can be slightly conservative determining whether the satellite
@@ -1644,6 +1682,8 @@ eod
my $bigstep = 5 * $step;
my $littlestep = $step;
my $end = $pass_end;
+ $truncate
+ and $end += $littlestep;
my @info; # Information on an individual pass.
my @passes; # Accumulated informtion on all passes.
my $visible;
@@ -1693,27 +1733,29 @@ eod
# but not the screening horizon before the end of the prediction
# period. Sigh.
- if ( $elev < $screening_horizon ||
- @info && $elev < $effective_horizon &&
- $info[-1]{elevation} >= $effective_horizon
+ if ( $elev < $screening_horizon
+ || @info && $elev < $effective_horizon &&
+ $info[-1]{elevation} >= $effective_horizon
+ || $truncate && $time >= $pass_end
) {
@info = () unless $visible;
next unless @info;
-
# We may have skipped part of the pass because it began in
# daylight or before the official beginning of the prediction
# period. Pick up that part now.
{ # Single-iteration loop.
- ( my $time = $info[0]{time} - $step ) < $pass_backup_earliest
+ my $time = $info[0]{time} - $step;
+ $truncate
+ and $time < $pass_start
and last;
my ( $try_azm, $try_elev, $try_rng ) = $sta->azel (
$tle->universal( $time ) );
last if $try_elev < $effective_horizon;
my $litup = $time < $suntim ? 2 - $dawn : 1 + $dawn;
1 == $litup
- and $tle->__sun_elev_from_sat( $time ) < 0
+ and not $tle->illuminated( $time )
and $litup = 0;
unshift @info, {
azimuth => $try_azm,
@@ -1772,6 +1814,11 @@ eod
( $info[0]{time} - $pass_step,
$info[-1]{time} + $pass_step
);
+ $truncate
+ and ( $trial_start, $trial_finish ) = (
+ max( $trial_start, $pass_start ),
+ min( $trial_finish, $pass_end )
+ );
my $culmination = find_first_true( $trial_start,
$trial_finish,
sub { ( $sta->azel( $tle->universal( $_[0] ) ) )[1] >
@@ -1782,14 +1829,17 @@ eod
# Compute exact rise and set.
- my $sat_rise = find_first_true( $info[0]{time} - $step,
+ $truncate
+ or ( $trial_start, $trial_finish ) = (
+ $info[0]{time} - $step, $info[-1]{time} + $step );
+ my $sat_rise = find_first_true( $trial_start,
$culmination,
sub { ( $sta->azel( $tle->universal( $_[0] ) ) )[1] >=
$effective_horizon
},
);
my $sat_set = find_first_true ( $culmination,
- $info[-1]{time} + $step,
+ $trial_finish,
sub { ( $sta->azel( $tle->universal( $_[0] ) ) )[1] <
$effective_horizon
},
@@ -1842,7 +1892,7 @@ eod
if !$suntim || $time >= $suntim;
my $litup = $time < $suntim ? 2 - $dawn : 1 + $dawn;
1 == $litup
- and $tle->__sun_elev_from_sat( $time ) < 0
+ and not $tle->illuminated( $time )
and $litup = 0;
push @illumination, illumination => $lighting[$litup];
}
@@ -1877,7 +1927,7 @@ eod
my $litup = $_[0] < $suntim ?
2 - $dawn : 1 + $dawn;
1 == $litup
- and $tle->__sun_elev_from_sat( $_[0] ) < 0
+ and not $tle->illuminated( $_[0] )
and $litup = 0;
$lighting[$litup] == $evt->{illumination}
});
@@ -1974,6 +2024,26 @@ eod
$info[-1]{event} = PASS_EVENT_END;
}
+ # If PASS_VARIANT_TRUNCATE is in effect, the first and last
+ # events should be 'start' and 'end' IF AND ONLY IF the
+ # satellite is above the horizon at that point AND the time
+ # is at the start or end of the interval. Because the first
+ # event is AFTER its exact time, we need to back up a bit
+ # and recalculate.
+
+ if ( $truncate ) {
+ my $prior = $info[0]{time} - 1;
+ if ( $prior <= $pass_start ) {
+ my $elevation = ( $sta->azel(
+ $tle->universal( $prior ) ) )[1];
+ $elevation > $effective_horizon
+ and $info[0]{event} = PASS_EVENT_START;
+ }
+ $info[-1]{elevation} > $effective_horizon
+ and $info[-1]{time} >= $pass_end
+ and $info[-1]{event} = PASS_EVENT_END;
+ }
+
# Pick up the first and last event times, to use to bracket
# future calculations.
@@ -2190,14 +2260,6 @@ eod
}
}
-# Unpublished, and subject to retraction. The sole purpose of this is to
-# give the experimental Astro::Coord::ECI::Point class a way to prevent
-# the Astro::Coord::ECI::TLE pass() method from backing up ad infinitum
-# trying to find the time when the body rises.
-sub __pass_backup_earliest {
- return 0;
-}
-
# Compute the position of the satellite at its brightest. We expect to
# be called only if the computation makes sense -- that is, if the
# intrinsic_magnitude attribute is set and we are calculating
@@ -7199,6 +7261,11 @@ sub _r_dump {
# given time.
sub __sun_elev_from_sat {
my ( $self, $time ) = @_;
+ if ( defined $time ) {
+ $self->universal( $time );
+ } else {
+ $time = $self->universal();
+ }
return ( $self->azel_offset(
$self->get( 'illum' )->universal( $time ),
$self->get( 'edge_of_earths_shadow' ),
@@ -7296,14 +7363,14 @@ encoded with a four-digit year.
return 'Generated by ' . __PACKAGE__ . ' v' . $VERSION;
},
CREATION_DATE => sub {
- return _format_json_time( time );
+ return format_space_track_json_time( time );
},
ECCENTRICITY => 'eccentricity',
ELEMENT_SET_NO => 'elementnumber',
EPHEMERIS_TYPE => 'ephemeristype',
EPOCH => sub {
my ( $self ) = @_;
- return _format_json_time( floor( $self->get( 'epoch' ) ) );
+ return format_space_track_json_time( floor( $self->get( 'epoch' ) ) );
},
EPOCH_MICROSECONDS => sub {
my ( $self ) = @_;
@@ -7380,23 +7447,11 @@ encoded with a four-digit year.
# TLE_LINE2 is handled programmatically
effective_date => sub {
my ( $self ) = @_;
- return _format_json_time( $self->get( 'effective' ) );
+ return format_space_track_json_time( $self->get( 'effective' ) );
},
intrinsic_magnitude => 'intrinsic_magnitude',
);
- sub _format_json_time {
- my ( $time ) = @_;
- defined $time
- and $time =~ m/ \S /smx
- or return;
- my @parts = gmtime floor( $time + .5 );
- $parts[4] += 1;
- $parts[5] += 1900;
- return sprintf '%04d-%02d-%02d %02d:%02d:%02d', reverse
- @parts[ 0 .. 5 ];
- };
-
# This guy is to be used by subclasses so they don't have to
# implement their own converter. The arguments (after the invocant)
# are a reference to the mapping hash and an optional reference to
@@ -7477,7 +7532,7 @@ encoded with a four-digit year.
# return 'Generated by ' . __PACKAGE__ . ' v' . $VERSION;
# },
# CREATION_DATE => sub {
-# return _format_json_time( time );
+# return format_space_track_json_time( time );
# },
EPOCH => 'epoch',
FILE => 'file',
@@ -7540,65 +7595,74 @@ encoded with a four-digit year.
foreach my $arg ( @args ) {
my $decode = $json->decode( $arg );
-BODY_LOOP:
foreach my $hash ( 'ARRAY' eq ref $decode ? @{ $decode } :
$decode ) {
- if ( exists $hash->{SATNAME} ) { # TODO Deprecated
- warnings::enabled( 'deprecated' )
- and carp 'The SATNAME JSON key is deprecated ',
- 'in favor of the OBJECT_NAME key';
- exists $hash->{OBJECT_NAME}
- or $hash->{OBJECT_NAME} = $hash->{SATNAME};
- delete $hash->{SATNAME};
- }
+ my $class = $hash->{astro_coord_eci_class} || __PACKAGE__;
+ load_module( $class );
+ push @rslt, $class->__from_json( $hash );
- foreach my $key ( @required ) {
- defined $hash->{$key} and next;
- next BODY_LOOP;
- }
+ }
+ }
- defined $hash->{INTLDES}
- and $hash->{INTLDES} =~
- s/ \A \d{2} ( \d{2} ) - /$1/smx;
+ return @rslt;
+ }
- foreach my $key ( qw{ EPOCH effective_date } ) {
- defined $hash->{$key}
- and $hash->{$key} = _decode_json_time( $hash->{$key} );
- }
- defined $hash->{EPOCH_MICROSECONDS}
- and $hash->{EPOCH} += $hash->{EPOCH_MICROSECONDS} /
- 1_000_000;
-
- foreach my $key ( qw{
- ARG_OF_PERICENTER INCLINATION MEAN_ANOMALY
- RA_OF_ASC_NODE
- } ) {
- $hash->{$key} *= SGP_DE2RA;
- }
+ sub __from_json {
+ my ( $class, $hash ) = @_;
- {
- my $temp = SGP_TWOPI;
- foreach my $key ( qw{
- MEAN_MOTION MEAN_MOTION_DOT MEAN_MOTION_DDOT
- } ) {
- $temp /= SGP_XMNPDA;
- $hash->{$key} *= $temp;
- }
- }
+ if ( exists $hash->{SATNAME} ) { # TODO Deprecated
+ warnings::enabled( 'deprecated' )
+ and carp 'The SATNAME JSON key is deprecated ',
+ 'in favor of the OBJECT_NAME key';
+ exists $hash->{OBJECT_NAME}
+ or $hash->{OBJECT_NAME} = $hash->{SATNAME};
+ delete $hash->{SATNAME};
+ }
- my %tle;
- foreach my $key ( keys %{ $hash } ) {
- my $value = $hash->{$key};
- my $attr = $json_map{$key}
- or next;
- $tle{$attr} = $value;
- }
- push @rslt, __PACKAGE__->new( %tle );
+ foreach my $key ( @required ) {
+ defined $hash->{$key}
+ or return;
+ }
+
+ defined $hash->{INTLDES}
+ and $hash->{INTLDES} =~
+ s/ \A \d{2} ( \d{2} ) - /$1/smx;
+
+ foreach my $key ( qw{ EPOCH effective_date } ) {
+ defined $hash->{$key}
+ and $hash->{$key} = _decode_json_time( $hash->{$key} );
+ }
+ defined $hash->{EPOCH_MICROSECONDS}
+ and $hash->{EPOCH} += $hash->{EPOCH_MICROSECONDS} /
+ 1_000_000;
+
+ foreach my $key ( qw{
+ ARG_OF_PERICENTER INCLINATION MEAN_ANOMALY
+ RA_OF_ASC_NODE
+ } ) {
+ $hash->{$key} *= SGP_DE2RA;
+ }
+
+ {
+ my $temp = SGP_TWOPI;
+ foreach my $key ( qw{
+ MEAN_MOTION MEAN_MOTION_DOT MEAN_MOTION_DDOT
+ } ) {
+ $temp /= SGP_XMNPDA;
+ $hash->{$key} *= $temp;
}
}
- return @rslt;
+ my %tle;
+ foreach my $key ( keys %{ $hash } ) {
+ my $value = $hash->{$key};
+ my $attr = $json_map{$key}
+ or next;
+ $tle{$attr} = $value;
+ }
+
+ return $class->new( %tle );
}
}
@@ -7720,7 +7784,7 @@ sub _find_position {
# Initial value of the 'inertial' attribute. TLEs are assumed to be
# inertial until set otherwise.
-sub _initial_inertial{ return 1 };
+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
@@ -7780,14 +7844,21 @@ sub _looks_like_real {
},
);
+ 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 ( my $name = $self->get( 'name' ) ) ) {
+ if ( defined $name ) {
$name =~ s/ \s+ \z //smx;
$name ne ''
and push @line0, substr $name, 0, 24;
@@ -7805,13 +7876,23 @@ sub _looks_like_real {
my %ele;
{
- foreach (qw{firstderivative secondderivative bstardrag
- inclination ascendingnode eccentricity
- argumentofperigee meananomaly meanmotion
- revolutionsatepoch}) {
- defined ($ele{$_} = $self->get($_))
- or croak "OID $oid ", ucfirst $_,
- "undefined; can not generate TLE";
+ my @missing_fields;
+ foreach ( @required_fields ) {
+ defined( $ele{$_} = $self->get( $_ ) )
+ and next;
+ push @missing_fields, $_;
+ }
+
+ if ( @missing_fields ) {
+ # If all required fields are missing we presume it is
+ # deliberate, and return nothing.
+ @required_fields == @missing_fields
+ and return undef; ## no critic (ProhibitExplicitReturnUndef)
+ # Otherwise we croak with an error
+ croak 'Can not generate TLE for ',
+ defined $oid ? $oid : $name,
+ '; undefined attribute(s) ',
+ join ', ', @missing_fields;
}
my $temp = SGP_TWOPI;
foreach (qw{meanmotion firstderivative secondderivative}) {
@@ -8864,7 +8945,7 @@ sub _next_elevation_screen {
#
# $ eg/visual -merge
#
-# Last-Modified: Tue, 07 Jan 2014 03:07:16 GMT
+# Last-Modified: Sat, 14 Jun 2014 22:30:35 GMT
%magnitude_table = (
'00694' => 3.5,
@@ -9419,6 +9500,16 @@ that setting this (with C<< visible => 0 >>) saves about 20% in wall
clock time versus not setting it. Your mileage may, of course, vary.
This has no effect unless the C<visible> attribute is false.
+* C<PASS_VARIANT_TRUNCATE> - Specifies that any pass in progress at the
+beginning or end of the calculation interval is to be truncated to the
+interval. If the calculation interval starts with the body already above
+the horizon, the initial event of that pass will be C<PASS_EVENT_START>
+rather than C<PASS_EVENT_RISE>. Similarly, if the calculation interval
+ends with the body still above the horizon, the final event of that pass
+will be C<PASS_EVENT_END>. With this variant in effect, 'passes' will be
+computed for synchronous satellites. This variant is to be considered
+B<experimental.>
+
* C<PASS_VARIANT_BRIGHTEST> - Specifies that the the moment the
satellite is brightest be computed as part of the pass statistics. The
computation is to the nearest second, and is not done if
@@ -104,7 +104,7 @@ package Astro::Coord::ECI::Utils;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
our @ISA = qw{Exporter};
use Carp;
@@ -140,15 +140,16 @@ our @EXPORT_OK = ( qw{
AU $DATETIMEFORMAT $JD_GREGORIAN JD_OF_EPOCH LIGHTYEAR PARSEC
PERL2000 PI PIOVER2 SECSPERDAY SECS_PER_SIDERIAL_DAY
SPEED_OF_LIGHT TWOPI acos add_magnitudes asin
- atmospheric_extinction date2epoch date2jd deg2rad distsq
- dynamical_delta embodies epoch2datetime equation_of_time
- find_first_true fold_case intensity_to_magnitude jcent2000
- jd2date jd2datetime jday2000 julianday keplers_equation
- load_module looks_like_number max min mod2pi
+ atmospheric_extinction date2epoch date2jd
+ decode_space_track_json_time deg2rad distsq dynamical_delta
+ embodies epoch2datetime equation_of_time find_first_true
+ fold_case format_space_track_json_time intensity_to_magnitude
+ jcent2000 jd2date jd2datetime jday2000 julianday
+ keplers_equation load_module looks_like_number max min mod2pi
nutation_in_longitude nutation_in_obliquity obliquity omega
rad2deg tan theta0 thetag vector_cross_product
- vector_dot_product vector_magnitude vector_unitize
- __classisa __default_station __instance },
+ vector_dot_product vector_magnitude vector_unitize __classisa
+ __default_station __instance },
@time_routines );
our %EXPORT_TAGS = (
@@ -337,6 +338,39 @@ sub date2epoch {
(($hr || 0) * 60 + ($min || 0)) * 60 + ($sec || 0);
}
+=item $time = decode_space_track_json_time( $string )
+
+This subroutine decodes a time in the format Space Track uses in their
+JSON code. This is ISO-8601-ish, but with a possible fractional part and
+no zone.
+
+=cut
+
+sub decode_space_track_json_time {
+ my ( $string ) = @_;
+ $string =~ m{ \A \s*
+ ( \d+ ) \D+ ( \d+ ) \D+ ( \d+ ) \D+
+ ( \d+ ) \D+ ( \d+ ) \D+ ( \d+ ) (?: ( [.] \d* ) )? \s* \z }smx
+ or return;
+ my @time = ( $1, $2, $3, $4, $5, $6 );
+ my $frac = $7;
+ if ( $time[0] < 100 ) {
+ $time[0] < 57
+ and $time[0] += 100;
+ } elsif ( $time[0] < 1900 ) {
+ return;
+ } else {
+ $time[0] -= 1900;
+ }
+ $time[1] -= 1;
+ my $rslt = timegm( reverse @time );
+ defined $frac
+ and $frac ne '.'
+ and $rslt += $frac;
+ return $rslt;
+}
+
+
# my ( $self, $station, @args ) = __default_station( @_ )
#
# This exportable subroutine checks whether the second argument embodies
@@ -589,10 +623,29 @@ C<CORE::fc> if that is available, otherwise it maps to C<CORE::lc>.
*fold_case = CORE->can( 'fc' ) || sub ($) { return lc $_[0] };
+=item $fmtd = format_space_track_json_time( time() )
+
+This function takes as input a Perl time, and returns that time
+in a format consistent with the Space Track JSON data. This is
+ISO-8601-ish, in Universal time, but without the zone indicated.
+
+=cut
+
+sub format_space_track_json_time {
+ my ( $time ) = @_;
+ defined $time
+ and $time =~ m/ \S /smx
+ or return;
+ my @parts = gmtime floor( $time + .5 );
+ $parts[4] += 1;
+ $parts[5] += 1900;
+ return sprintf '%04d-%02d-%02d %02d:%02d:%02d', reverse
+ @parts[ 0 .. 5 ];
+}
=item $difference = intensity_to_magnitude ($ratio)
-This method converts a ratio of light intensities to a difference in
+This function converts a ratio of light intensities to a difference in
stellar magnitudes. The algorithm comes from Jean Meeus' "Astronomical
Algorithms", Second Edition, Chapter 56, Page 395.
@@ -143,7 +143,7 @@ package Astro::Coord::ECI;
use strict;
use warnings;
-our $VERSION = '0.063';
+our $VERSION = '0.065';
use Astro::Coord::ECI::Utils qw{:all};
use Carp;
@@ -186,7 +186,7 @@ to the set() method once the object has been instantiated.
sub new {
my ( $class, @args ) = @_;
my $self = bless { %static }, ref $class || $class;
- $self->{inertial} = $self->_initial_inertial();
+ $self->{inertial} = $self->__initial_inertial();
@args and $self->set( @args );
exists $self->{almanac_horizon}
or $self->set( almanac_horizon => 0 );
@@ -3374,7 +3374,7 @@ sub _expand_args_default_station {
return @args;
}
-# $value = $self->_initial_inertial
+# $value = $self->__initial_inertial
#
# Return the initial setting of the inertial attribute. At this
# level we are assumed not inertial until we acquire a position.
@@ -3383,7 +3383,7 @@ sub _expand_args_default_station {
# Setting the coordinates explicitly will still set the {inertial}
# attribute appropriately.
-sub _initial_inertial{ return };
+sub __initial_inertial{ return };
# $value = _local_mean_delta ($coord)
@@ -290,6 +290,9 @@ u_cmp_eql find_first_true => [
0, 1, sub{ sin( $_[0] ) >= sin( .5 ) }, .0001 ], .5, '%.4f',
'find_first_true looking for sin( $x ) >= sin( .5 )';
+u_cmp_eql format_space_track_json_time => timegm( 0, 0, 0, 1, 3, 114 ),
+ '2014-04-01 00:00:00', '%s', 'Format Space Tracj JSON time';
+
done_testing;
sub instantiate ($) {
@@ -324,8 +327,13 @@ sub u_cmp_eql (@) {
} else {
defined $tplt
and ( $want, $got ) = map { sprintf $tplt, $_ } ( $want, $got );
- @_ = ( $got, '==', $want, $title );
- goto &cmp_ok;
+ if ( defined $tplt && '%s' eq $tplt ) {
+ @_ = ( $got, $want, $title );
+ goto &is;
+ } else {
+ @_ = ( $got, '==', $want, $title );
+ goto &cmp_ok;
+ }
}
} else {
@_ = "Astro::Coord::ECI::Utils does not have subroutine $sub()";
@@ -416,6 +416,53 @@ EOD
-41.0 61.9 49.8 Moon
EOD
+ $tle->set( pass_variant => PASS_VARIANT_TRUNCATE );
+ @pass = ();
+ if (
+ eval {
+ @pass = $tle->pass(
+ timegm( 0, 0, 0, 13, 9, 80 ),
+ timegm( 0, 0, 0, 14, 9, 80 ),
+ );
+ 1;
+ }
+ ) {
+ ok @pass == 1, 'Found 1 passes over Greenwich'
+ or diag "Found @{[ scalar @pass ]} passes over Greenwich";
+ } else {
+ fail "Error in pass() method: $@";
+ }
+
+ is format_pass( $pass[0] ), <<'EOD', 'Pass 1';
+1980/10/13 05:39:02 0.0 199.0 1687.8 lit rise
+1980/10/13 05:42:43 55.9 115.6 255.5 lit max
+1980/10/13 05:46:37 0.0 29.7 1778.5 lit set
+EOD
+
+ @pass = ();
+ if (
+ eval {
+ @pass = $tle->pass(
+ timegm( 0, 40, 5, 13, 9, 80 ),
+ timegm( 0, 45, 5, 13, 9, 80 ),
+ );
+ 1;
+ }
+ ) {
+ ok @pass == 1, 'Found 1 passes over Greenwich'
+ or diag "Found @{[ scalar @pass ]} passes over Greenwich";
+ } else {
+ fail "Error in pass() method: $@";
+ }
+
+ is format_pass( $pass[0] ), <<'EOD', 'Pass 1';
+1980/10/13 05:40:01 4.2 197.4 1251.1 lit start
+1980/10/13 05:42:43 55.9 115.6 255.5 lit max
+1980/10/13 05:45:00 7.4 32.6 1063.4 lit end
+EOD
+
+ $tle->set( pass_variant => PASS_VARIANT_NONE );
+
my ( $tle2 ) = Astro::Coord::ECI::TLE->parse(
{ station => $sta }, <<'EOD' );
11801
@@ -440,11 +487,11 @@ EOD
}
is format_pass( $pass[0] ), <<'EOD', 'Pass 1';
-1980/10/13 05:39:02 0.0 199.0 1687.8 lit start
+1980/10/13 05:39:02 0.0 199.0 1687.8 lit rise
1980/10/13 05:39:02 0.0 199.0 1687.4 lit apls
-17.8 203.7 17.8 11801
1980/10/13 05:42:43 55.9 115.6 255.5 lit max
-1980/10/13 05:46:37 0.0 29.7 1778.5 lit end
+1980/10/13 05:46:37 0.0 29.7 1778.5 lit set
EOD
}
@@ -20,7 +20,7 @@ EOD
is last_modified(
'http://celestrak.com/SpaceTrack/query/visual.txt' ),
-'Tue, 07 Jan 2014 03:07:16 GMT',
+'Sat, 14 Jun 2014 22:30:35 GMT',
'Celestrak visual.txt Last-Modified';
is last_modified( mccants => 'vsnames' ),
@@ -28,7 +28,7 @@ is last_modified( mccants => 'vsnames' ),
'McCants vsnames.mag Last-Modified';
is last_modified( mccants => 'mcnames' ),
- 'Mon, 06 Jan 2014 10:21:29 GMT',
+ 'Tue, 20 May 2014 23:28:07 GMT',
'McCants mcnames.mag Last-Modified';
done_testing;