Astro-SpaceTrack

 view release on metacpan or  search on metacpan

lib/Astro/SpaceTrack.pm  view on Meta::CPAN

		cookie_name		=> 'chocolatechip',
		domain_space_track	=> 'www.space-track.org',
		session_cookie		=> undef,
	    },
	],
	space_track_version	=> DEFAULT_SPACE_TRACK_VERSION,
	url_iridium_status_kelso =>
	    'https://celestrak.org/SpaceTrack/query/iridium.txt',
	url_iridium_status_sladen =>
	    'http://www.rod.sladen.org.uk/iridium.htm',
	username => undef,	# Login username.
	verbose => undef,	# Verbose error messages for catalogs.
	verify_hostname => 1,	# Don't verify host names by default.
	webcmd => undef,	# Command to get web help.
	with_name => undef,	# True to retrieve three-line element sets.
    };
    bless $self, $class;

    $self->set( identity	=> delete $arg{identity} );

    $ENV{SPACETRACK_OPT} and
	$self->set (grep {defined $_} split '\s+', $ENV{SPACETRACK_OPT});

    # TODO this makes no sense - the first branch of the if() can never
    # be executed because I already deleted $arg{identity}. But I do not
    # want to execute the SPACETRACK_USER code willy-nilly -- maybe warn
    # if identity is 1 and I don't have both a username and a password.
    if ( defined( my $id = delete $arg{identity} ) ) {
	$self->set( identity => $id );
    } elsif ( $ENV{SPACETRACK_USER} ) {
	my ($user, $pass) = split qr{ [:/] }smx, $ENV{SPACETRACK_USER}, 2;
	'' ne $user
	    and '' ne $pass
	    or $user = $pass = undef;
	$self->set (username => $user, password => $pass);
    } else {
	$self->set( identity => undef );
    }

    defined $ENV{SPACETRACK_VERIFY_HOSTNAME}
	and $self->set( verify_hostname =>
	$ENV{SPACETRACK_VERIFY_HOSTNAME} );

    keys %arg
	and $self->set( %arg );

    return $self;
}

=for html <a name="amsat"></a>

=item $resp = $st->amsat ()

B<Note> that this method is non-functional as of September 8 2025
(probably earlier), because Amsat has gone to a "humans-only" policy for
their web site. It will be put through the usual deprecation cycle and
removed.

This method downloads current orbital elements from the Radio Amateur
Satellite Corporation's web page, L<https://www.amsat.org/>. This lists
satellites of interest to radio amateurs, and appears to be updated
weekly.

No Space Track account is needed to access this data. As of version
0.150 the setting of the 'with_name' attribute is honored.

You can specify options as either command-type options (e.g.
C<< amsat( '-file', 'foo.dat' ) >>) or as a leading hash reference (e.g.
C<< amsat( { file => 'foo.dat' } ) >>). If you specify the hash
reference, option names must be specified in full, without the leading
'-', and the argument list will not be parsed for command-type options.
If you specify command-type options, they may be abbreviated, as long as
the abbreviation is unique. Errors in either sort result in an exception
being thrown.

The legal options are:

 -file
   specifies the name of the cache file. If the data
   on line are newer than the modification date of
   the cache file, the cache file will be updated.
   Otherwise the data will be returned from the file.
   Either way the content of the file and the content
   of the returned HTTP::Response object end up the
   same.

On a successful return, the response object will contain headers

 Pragma: spacetrack-type = orbit
 Pragma: spacetrack-source = amsat

These can be accessed by C<< $st->content_type( $resp ) >> and
C<< $st->content_source( $resp ) >> respectively.

If the C<file> option was passed, the following additional header will
be provided:

 Pragma: spacetrack-cache-hit = (either true or false)

This can be accessed by the C<cache_hit()> method. If this pragma is
true, the C<Last-Modified> header of the response will contain the
modification time of the file.

This method is a web page scraper. Any change in the location of the
web page will break this method.

=cut

# Called dynamically
sub _amsat_opts {	## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
    return [
	'file=s'	=> 'Name of cache file',
    ];
}

sub amsat {
    my ( $self, @args ) = @_;

    $self->_deprecation_notice( 'amsat' );

    ( my $opt, @args ) = _parse_args( @args );

    return $self->_get_from_net(
	%{ $opt },
	# url	=> 'http://www.amsat.org/amsat/ftp/keps/current/nasabare.txt',
	url	=> 'https://www.amsat.org/tle/current/nasabare.txt',
	post_process	=> sub {
	    my ( $self, $resp ) = @_;
	    unless ( $self->{with_name} ) {
		my @content = split qr{ \015? \012 }smx,
		    $resp->content();
		@content % 3
		    and return HTTP::Response->new(
		    HTTP_PRECONDITION_FAILED,
		    'Response does not contain a multiple of 3 lines' );
		my $ct = '';
		while ( @content ) {
		    shift @content;
		    $ct .= join '', map { "$_\n" } splice @content, 0, 2;
		}
		$resp->content( $ct );

lib/Astro/SpaceTrack.pm  view on Meta::CPAN

EOD
    return HTTP::Response->new (HTTP_OK, undef, undef, "Login successful.\n");
}

=for html <a name="logout"></a>

=item $st->logout()

This method deletes all session cookies. It returns an HTTP::Response
object that indicates success.

=cut

sub logout {
    my ( $self ) = @_;
    foreach my $spacetrack_interface_info (
	@{ $self->{_space_track_interface} } ) {
	$spacetrack_interface_info
	    or next;
	exists $spacetrack_interface_info->{session_cookie}
	    and $spacetrack_interface_info->{session_cookie} = undef;
	exists $spacetrack_interface_info->{cookie_expires}
	    and $spacetrack_interface_info->{cookie_expires} = 0;
    }
    return HTTP::Response->new(
	HTTP_OK, undef, undef, "Logout successful.\n" );
}

=for html <a name="mccants"></a>

=item $resp = $st->mccants( catalog )

This method retrieves one of several pieces of data that Mike McCants
makes available on his web site. The return is the
L<HTTP::Response|HTTP::Response> object from the retrieval. Valid
catalog names are:

 classified: Classified TLE file (classfd.zip)
 integrated: Integrated TLE file (inttles.zip)
 mcnames: Molczan-format magnitude file (mcnames.zip) REMOVED
 quicksat: Quicksat-format magnitude file (qsmag.zip) REMOVED
 vsnames: Molczan-format mags of visual bodies (vsnames.zip) REMOVED

The files marked B<REMOVED> have been removed from Mike McCants' web
site. As of version 0.181, use of the associated arguments is fatal.

You can specify options as either command-type options (e.g. C<<
mccants( '-file', 'foo.dat', ... ) >>) or as a leading hash reference
(e.g. C<< mccants( { file => 'foo.dat' }, ...) >>). If you specify the
hash reference, option names must be specified in full, without the
leading '-', and the argument list will not be parsed for command-type
options.  If you specify command-type options, they may be abbreviated,
as long as the abbreviation is unique. Errors in either sort result in
an exception being thrown.

The legal options are:

 -file
   specifies the name of the cache file. If the data
   on line are newer than the modification date of
   the cache file, the cache file will be updated.
   Otherwise the data will be returned from the file.
   Either way the content of the file and the content
   of the returned HTTP::Response object end up the
   same.

On success, the content of the returned object is the actual data,
unzipped and with line endings normalized for the current system.

If this method succeeds, the response will contain headers

 Pragma: spacetrack-type = (see below)
 Pragma: spacetrack-source = mccants

The content of the spacetrack-type pragma depends on the catalog
fetched, as follows:

 classified: 'orbit'
 integrated: 'orbit'

If the C<file> option was passed, the following additional header will
be provided:

 Pragma: spacetrack-cache-hit = (either true or false)

This can be accessed by the C<cache_hit()> method. If this pragma is
true, the C<Last-Modified> header of the response will contain the
modification time of the file.

No Space Track username and password are required to use this method.

=cut

# Called dynamically
sub _mccants_opts {	## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
    return [
	'file=s'	=> 'Name of cache file',
    ];
}

sub mccants {
    my ( $self, @args ) = @_;

    ( my $opt, @args ) = _parse_args( @args );

    return $self->_get_from_net(
	%{ $opt },
	catalog	=> $args[0],
	post_process	=> sub {
	    my ( undef, $resp, $info ) = @_;	# Invocant unused
	    my ( $content, @zip_opt );
	    defined $info->{member}
		and push @zip_opt, Name => $info->{member};
	    IO::Uncompress::Unzip::unzip( \( $resp->content() ),
		\$content, @zip_opt )
		or return HTTP::Response->new(
		HTTP_NOT_FOUND,
		$IO::Uncompress::Unzip::UnzipError );
	    $resp->content( $content );
	    return $resp;
	},

lib/Astro/SpaceTrack.pm  view on Meta::CPAN

reference, option names must be specified in full, without the leading
'-', and the argument list will not be parsed for command-type options.
If you specify command-type options, they may be abbreviated, as long as
the abbreviation is unique. Errors in either sort result in an exception
being thrown.

The legal options are:

 -descending
   specifies the data be returned in descending order.
 -end_epoch date
   specifies the end epoch for the desired data.
 -format format_name
   specifies the format in which the data are retrieved.
 -json
   specifies the TLE be returned in JSON format.
 -last5
   Ignored and deprecated.
 -start_epoch date
   specifies the start epoch for the desired data.
 -since_file number
   specifies that only data since the given Space Track
   file number be retrieved.
 -sort type
   specifies how to sort the data. Legal types are
   'catnum' and 'epoch', with 'catnum' the default.

The C<-format> option takes any argument supported by the Space Track
interface: C<tle>, C<3le>, C<json>, C<csv>, C<html>, or C<xml>.
Specifying C<-json> is equivalent to specifying C<-format json>, and if
you specify C<-json>, specifying C<-format> with any other value than
C<'json'> results in an exception being thrown. In addition, you can
specify format C<'legacy'> which is equivalent to C<'tle'> if the
C<with_name> attribute is false, or C<'3le'> (but without the leading
C<'0 '> before the common name) if C<with_name> is true. The default is
C<'legacy'> unless C<-json> is specified.

If you specify either start_epoch or end_epoch, you get data with epochs
at least equal to the start epoch, but less than the end epoch (i.e. the
interval is closed at the beginning but open at the end). If you specify
only one of these, you get a one-day interval. Dates are specified
either numerically (as a Perl date) or as numeric year-month-day (and
optional hour, hour:minute, or hour:minute:second), punctuated by any
non-numeric string. It is an error to specify an end_epoch before the
start_epoch.

If you are passing the options as a hash reference, you must specify
a value for Boolean options like 'descending' and 'json'. This value is
interpreted in the Perl sense - that is, undef, 0, and '' are false,
and anything else is true.

In order not to load the Space Track web site too heavily, data are
retrieved in batches of 200. Ranges will be subdivided and handled in
more than one retrieval if necessary. To limit the damage done by a
pernicious range, ranges greater than the max_range setting (which
defaults to 500) will be ignored with a warning to STDERR.

If you specify C<-json> and more than one retrieval is needed, data from
retrievals after the first B<may> have field C<_file_of_record> added.
This is because of the theoretical possibility that the database may be
updated between the first and last queries, and therefore taking the
maximum C<FILE> from queries after the first may cause updates to be
skipped. The C<_file_of_record> key will appear only in data having a
C<FILE> value greater than the largest C<FILE> in the first retrieval.

This method implicitly calls the C<login()> method if the session cookie
is missing or expired. If C<login()> fails, you will get the
HTTP::Response from C<login()>.

If this method succeeds, a 'Pragma: spacetrack-type = orbit' header is
added to the HTTP::Response object returned.

=cut

# Called dynamically
sub _retrieve_opts {	## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
    return [
	_get_retrieve_options(),
    ];
}

sub retrieve {
    my ( $self, @args ) = @_;
    delete $self->{_pragmata};

    @args = $self->_parse_retrieve_args( @args );
    my $opt = _parse_retrieve_dates( shift @args );

    my $rest = $self->_convert_retrieve_options_to_rest( $opt );

    @args = $self->_expand_oid_list( @args )
	or return HTTP::Response->new( HTTP_PRECONDITION_FAILED, NO_CAT_ID );

    my $no_execute = $self->getv( 'dump_headers' ) & DUMP_DRY_RUN;

##  $rest->{orderby} = 'EPOCH desc';

    my $accumulator = _accumulator_for (
	$no_execute ?
	    ( json => { pretty => 1 } ) :
	    ( $rest->{format}, {
		    file => 1,
		    pretty => $self->getv( 'pretty' )
		},
	    )
    );

    while ( @args ) {

	my @batch = splice @args, 0, $RETRIEVAL_SIZE;
	$rest->{NORAD_CAT_ID} = _stringify_oid_list( {
		separator	=> ',',
		range_operator	=> '--',
	    }, @batch );

	my $resp = $self->spacetrack_query_v2(
	    basicspacedata	=> 'query',
	    _sort_rest_arguments( $rest )
	);

	$resp->is_success()

lib/Astro/SpaceTrack.pm  view on Meta::CPAN

#	my $resp = $self->_get_agent()->get( $url );
	my $resp = $self->_get_agent()->get( $uri );

	if ( $resp->is_success() ) {

	    if ( $self->{pretty} &&
		_find_rest_arg_value( \@args, format => 'json' ) eq 'json'
	    ) {
		my $json = $self->_get_json_object();
		$resp->content( $json->encode( $json->decode(
			    $resp->content() ) ) );
	    }

	    if ( __PACKAGE__ ne caller ) {

		my $kind = _find_rest_arg_value( \@args,
		    basicspacedata => '' );
		my $class = _find_rest_arg_value( \@args,
		    class => '' );

		if ( 'modeldef' eq $kind ) {

		    $self->_add_pragmata( $resp,
			'spacetrack-type' => 'modeldef',
			'spacetrack-source' => 'spacetrack',
			'spacetrack-interface' => 2,
		    );

		} elsif ( 'query' eq $kind && $tle_class{$class} ) {

		    $self->_add_pragmata( $resp,
			'spacetrack-type' => 'orbit',
			'spacetrack-source' => 'spacetrack',
			'spacetrack-interface' => 2,
		    );

		}
	    }
	}

	$self->__dump_response( $resp );
	return $resp;
    }
}

sub _find_rest_arg_value {
    my ( $args, $name, $default ) = @_;
    for ( my $inx = $#$args - 1; $inx >= 0; $inx -= 2 ) {
	$args->[$inx] eq $name
	    and return $args->[$inx + 1];
    }
    return $default;
}

=for html <a name="update"></a>

=item $resp = $st->update( $file_name );

This method updates the named TLE file, which must be in JSON format. On
a successful update, the content of the returned HTTP::Response object
is the updated TLE data, in whatever format is desired. If any updates
were in fact found, the file is rewritten. The rewritten JSON will be
pretty if the C<pretty> attribute is true.

The file to be updated can be generated by using the C<-json> option on
any of the methods that accesses Space Track data. For example,

 # Assuming $ENV{SPACETRACK_USER} contains
 # username/password
 my $st = Astro::SpaceTrack->new(
     pretty              => 1,
 );
 my $rslt = $st->spacetrack( { json => 1 }, 'iridium' );
 $rslt->is_success()
     or die $rslt->status_line();
 open my $fh, '>', 'iridium.json'
     or die "Failed to open file: $!";
 print { $fh } $rslt->content();
 close $fh;

The following is the equivalent example using the F<SpaceTrack> script:

 SpaceTrack> set pretty 1
 SpaceTrack> spacetrack -json iridium >iridium.json

This method reads the file to be updated, determines the highest C<FILE>
value, and then requests the given OIDs, restricting the return to
C<FILE> values greater than the highest found. If anything is returned,
the file is rewritten.

The following options may be specified:

 -json
   specifies the TLE be returned in JSON format

Options may be specified either in command-line style (that is, as
C<< spacetrack( '-json', ... ) >>) or as a hash reference (that is, as
C<< spacetrack( { json => 1 }, ... ) >>).

B<Note> that there is no way to specify the C<-rcs> or C<-effective>
options. If the file being updated contains these values, they will be
lost as the individual OIDs are updated.

=cut

{

    my %encode = (
	'3le'	=> sub {
	    my ( undef, $data ) = @_;	# JSON object unused
	    return join '', map {
		"$_->{OBJECT_NAME}\n$_->{TLE_LINE1}\n$_->{TLE_LINE2}\n"
	    } @{ $data };
	},
	json	=> sub {
	    my ( $json, $data ) = @_;
	    return $json->encode( $data );
	},
	tle	=> sub {
	    my ( undef, $data ) = @_;	# JSON object unused
	    return join '', map {
		"$_->{TLE_LINE1}\n$_->{TLE_LINE2}\n"
	    } @{ $data };
	},
    );

    # Called dynamically
    sub _update_opts {	## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
	return [
	    _get_retrieve_options(),
	];
    }

    sub update {
	my ( $self, @args ) = @_;

	my ( $opt, $fn ) = $self->_parse_retrieve_args( @args );

	$opt = { %{ $opt } };	# Since we modify it.

	delete $opt->{start_epoch}
	    and Carp::croak '-start_epoch not allowed';
	delete $opt->{end_epoch}
	    and Carp::croak '-end_epoch not allowed';

	my $json = $self->_get_json_object();
	my $data;
	{
	    local $/ = undef;
	    open my $fh, '<', $fn
		or Carp::croak "Unable to open $fn: $!";
	    $data = $json->decode( <$fh> );
	    close $fh;
	}

	my $file = -1;
	my @oids;
	foreach my $datum ( @{ $data } ) {
	    push @oids, $datum->{NORAD_CAT_ID};
	    my $ff = defined $datum->{_file_of_record} ?
		delete $datum->{_file_of_record} :
		$datum->{FILE};



( run in 0.645 second using v1.01-cache-2.11-cpan-39bf76dae61 )