Astro-SpaceTrack

 view release on metacpan or  search on metacpan

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


This package retrieves orbital data from the Space Track web site
L<https://www.space-track.org> and several others. You must register and
get a user name and password before you can get data from Space Track.

Other methods (C<celestrak()> ...) have
been added to access other repositories of orbital data, and in general
these do not require a Space Track username and password.

Nothing is exported by default, but the shell method/subroutine
and the BODY_STATUS constants (see C<iridium_status()>)
can be exported if you so desire.

Most methods return an HTTP::Response object. See the individual
method document for details. Methods which return orbital data on
success add a 'Pragma: spacetrack-type = orbit' header to the
HTTP::Response object if the request succeeds, and a 'Pragma:
spacetrack-source =' header to specify what source the data came from.

=head2 Methods

The following methods should be considered public:

=over 4

=cut

package Astro::SpaceTrack;

use 5.006002;

use strict;
use warnings;

use Exporter;

our @ISA = qw{ Exporter };

our $VERSION = '0.181';
our @EXPORT_OK = qw{
    shell

    BODY_STATUS_IS_OPERATIONAL
    BODY_STATUS_IS_SPARE
    BODY_STATUS_IS_TUMBLING
    BODY_STATUS_IS_DECAYED

    ARRAY_REF
    CODE_REF
    HASH_REF

    };
our %EXPORT_TAGS = (
    ref		=> [ grep { m/ _REF \z /smx } @EXPORT_OK ],
    status	=> [ grep { m/ \A BODY_STATUS_IS_ /smx } @EXPORT_OK ],
);

use Carp ();
use Getopt::Long 2.39;
use HTTP::Date ();
use HTTP::Request;
use HTTP::Response;
use HTTP::Status qw{
    HTTP_PAYMENT_REQUIRED
    HTTP_BAD_REQUEST
    HTTP_NOT_FOUND
    HTTP_I_AM_A_TEAPOT
    HTTP_INTERNAL_SERVER_ERROR
    HTTP_NOT_ACCEPTABLE
    HTTP_NOT_MODIFIED
    HTTP_OK
    HTTP_PRECONDITION_FAILED
    HTTP_UNAUTHORIZED
    HTTP_INTERNAL_SERVER_ERROR
};
use IO::File;
use IO::Uncompress::Unzip ();
use JSON qw{};
use List::Util ();
use LWP::UserAgent;	# Not in the base.
use POSIX ();
use Scalar::Util 1.07 ();
use Text::ParseWords ();
use Time::Local ();
use URI qw{};
# use URI::Escape qw{};

# Number of OIDs to retrieve at once. This is a global variable so I can
# play with it, but it is neither documented nor supported, and I
# reserve the right to change it or delete it without notice.
our $RETRIEVAL_SIZE = $ENV{SPACETRACK_RETRIEVAL_SIZE};
defined $RETRIEVAL_SIZE or $RETRIEVAL_SIZE = 200;

use constant COPACETIC => 'OK';
use constant BAD_SPACETRACK_RESPONSE =>
	'Unable to parse SpaceTrack response';
use constant INVALID_CATALOG =>
	'Catalog name %s invalid. Legal names are %s.';
use constant LAPSED_FUNDING => 'Funding lapsed.';
use constant LOGIN_FAILED => 'Login failed';
use constant NO_CREDENTIALS => 'Username or password not specified.';
use constant NO_CAT_ID => 'No catalog IDs specified.';
use constant NO_OBJ_NAME => 'No object name specified.';
use constant NO_RECORDS => 'No records found.';

use constant SESSION_PATH => '/';

use constant DEFAULT_SPACE_TRACK_REST_SEARCH_CLASS => 'satcat';
use constant DEFAULT_SPACE_TRACK_VERSION => 2;

# dump_headers constants.
use constant DUMP_NONE => 0;		# No dump
use constant DUMP_TRACE => 0x01;	# Logic trace
use constant DUMP_REQUEST => 0x02;	# Request content
use constant DUMP_DRY_RUN => 0x04;	# Do not execute request
use constant DUMP_COOKIE => 0x08;	# Dump cookies.
use constant DUMP_RESPONSE => 0x10;	# Dump response.
use constant DUMP_TRUNCATED => 0x20;	# Dump with truncated content

my @dump_options;
foreach my $key ( sort keys %Astro::SpaceTrack:: ) {

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

#      modified since the modification date of the file. If the data
#      have been modified, the cache file is refreshed; otherwise the
#      response is loaded from the cache file.
#   method => method_name
#      If this is defined, it is the name of the method doing the
#      catalog lookup. This is unused unless 'catalog' is defined, and
#      defaults to the name of the calling method.
#   post_process => code reference
#      If the network operation succeeded and this is defined, it is
#      called and passed the invocant, the HTTP::Response object, and
#      a reference to the catalog information hash (or to an empty hash
#      if 'url' was specified). The HTTP::Response object returned
#      (which may or may not be the one passed in) is the basis for any
#      further processing.
#   spacetrack_source => spacetrack_source
#      If this is defined, the corresponding-named pragma is set. The
#      default comes from the same-named key in the catalog info if that
#      is defined, or the 'method' argument (as defaulted).
#   spacetrack_type => spacetrack_type
#      If this is defined, the corresponding-named pragma is set.
#   url => URL
#      If this is defined, it is the URL of the data to retrieve.
#
# Either 'catalog' or 'url' MUST be specified. If 'url' is defined,
# 'catalog' is ignored.

sub _get_from_net {
    my ( $self, %arg ) = @_;
    delete $self->{_pragmata};

    my $method = defined $arg{method} ? $arg{method} : ( caller 1)[3];
    $method =~ s/ .* :: //smx;

    my $url;
    my $info;
    if ( defined $arg{url} ) {
	$url = $arg{url};
	$info	= {};
    } elsif ( exists $arg{catalog} ) {
	defined $arg{catalog}
	    and $catalogs{$method}
	    and $info = $catalogs{$method}{$arg{catalog}}
	    or return $self->_no_such_catalog( $method, $arg{catalog} );
	$self->_deprecation_notice( $method => $arg{catalog} );
	$url = $info->{url}
	    or Carp::confess "Bug - No url defined for $method( '$arg{catalog}' )";
    } else {
	Carp::confess q<Bug - neither 'url' nor 'catalog' specified>;
    }

    if ( my $resp = $self->_dump_request(
	    args	=> { map { $_ => CODE_REF eq ref $arg{$_} ? 'sub { ... }' : $arg{$_} } keys %arg },
	    method	=> 'GET',
	    url		=> $url,
	    version	=> 2,
	) ) {
	return $resp;
    }

    my $agent = $self->_get_agent();
    my $rqst = HTTP::Request->new( GET	=> $url );
    my $file_time;
    if ( defined $arg{file} ) {
	if ( my @stat = stat $arg{file} ) {
	    $file_time = HTTP::Date::time2str( $stat[9] );
	    $rqst->header( if_modified_since => $file_time );
	}
    }

    my $resp;
    $resp = $self->_dump_request(
	arg	=> sub {
	    my %sanitary = %arg;
	    foreach my $key ( qw{ post_process } ) {
		delete $sanitary{$key}
		    and $sanitary{$key} = CODE_REF;
	    }
	    return \%sanitary;
	},
	message	=> '_get_from_net() request object',
	method	=> 'GET',
	url	=> $url,
	hdrs	=> sub {
	    my %rslt;
	    foreach my $name ( $rqst->header_field_names() ) {
		my @v = $rqst->header( $name );
		$rslt{$name} = @v == 1 ? $v[0] : \@v;
	    }
	    return \%rslt;
	},
    )
	and return $resp;
    $resp = $agent->request( $rqst );
    $self->__dump_response(
	$resp, '_get_from_net() initial response object' );

    if ( $resp->code() == HTTP_NOT_MODIFIED ) {
	defined $arg{file}
	    or Carp::confess q{Programming Error - argument 'file' not defined};
	local $/ = undef;
	open my $fh, '<', $arg{file}
	    or return HTTP::Response->new(
	    HTTP_INTERNAL_SERVER_ERROR,
	    "Unable to read $arg{file}: $!" );
	$resp->content( scalar <$fh> );
	close $fh;
	$resp->code( HTTP_OK );
	defined $file_time
	    and $resp->header( last_modified => $file_time );
	$arg{spacetrack_cache_hit} = 1;
    } else {
	$resp->is_success()
	    and defined $arg{post_process}
	    and $resp = $arg{post_process}->( $self, $resp, $info );
	$resp->is_success()	# $resp may be a different object now.
	    or return $resp;
	$self->_convert_content( $resp );
	if ( defined $arg{file} ) {
	    open my $fh, '>', $arg{file}
		or return HTTP::Response->new(
		HTTP_INTERNAL_SERVER_ERROR,



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