Astro-App-Satpass2

 view release on metacpan or  search on metacpan

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

EOD

    foreach my $body ( sort keys %{ $self->{sky_class} } ) {
	$opt->{changes}
	    and $sky_class{$body}
	    and $sky_class{$body} eq $self->{sky_class}{$body}
	    and next;
	$output .= $self->_sky_class_components( $body ) . "\n";
    }
    foreach my $body ( sort keys ( %sky_class ) ) {
	$self->{sky_class}{$body}
	    or $output .= $self->_sky_class_components( $body ) . "\n";
    }

    my %exclude;
    if ( $opt->{changes} ) {
	%exclude = map { $_ => 1 }
	    SUN_CLASS_DEFAULT, 'Astro::Coord::ECI::Moon';
	foreach my $name ( qw{ sun moon } ) {
	    defined $self->_find_in_sky( $name )
		or $output .= "sky drop $name\n";
	}
    } else {
	$output .= "sky clear\n";
    }
    foreach my $body ( @{ $self->{sky} } ) {
	$exclude{ ref $body }
	    and next;
	$output .= _sky_list_body( $body );
    }

    return $output;
}

sub set : Verb() {
    my ( $self, undef, @args ) = __arguments( @_ );	# $opt unused

    while (@args) {
	my ( $name, $value ) = splice @args, 0, 2;
	$self->_attribute_exists( $name );
	if ( _is_interactive() ) {
	    $nointeractive{$name}
		and $self->wail(
		    "Attribute '$name' may not be set interactively");
	    defined $value and $value eq 'undef'
		and $value = undef;
	}
	if ( $mutator{$name} ) {
	    $self->_deprecation_notice( attribute => $name );
	    $mutator{$name}->($self, $name, $value);
	} else {
	    $self->wail("Read-only attribute '$name'");
	}
    }
    return;
}

sub _set_almanac_horizon {
    my ( $self, $name, $value ) = @_;
    my $parsed = $self->__parse_angle( { accept => 1 }, $value );
    my $internal = looks_like_number( $parsed ) ? deg2rad( $parsed ) :
	$parsed;
    my $eci = Astro::Coord::ECI->new();
    $eci->set( $name => $internal );	# To validate.
    $self->{"_$name"} = $internal;
    return( $self->{$name} = $parsed );
}

{
    my $plus_or_minus_90 = sub { $_[0] >= -90 && $_[0] <= 90 };
    my %validate = (
	horizon		=> $plus_or_minus_90,
	latitude	=> $plus_or_minus_90,
	longitude	=> sub {
	    $_[0] > 360
		and return 0;
	    $_[0] > 180
		and $_[0] -= 360;
	    $_[0] >= -180 && $_[0] <= 180;
	},
    );
    sub _set_angle {
	my ( $self, $name, $value ) = @_;
	my $angle = $self->__parse_angle( $value );
	if ( my $code = $validate{$name} ) {
	    defined $angle or $self->weep(
		"$name angle is undef for value ", defined $value ? $value : 'undef' );
	    $code->( $angle )
		or $self->wail( "Value $value is invalid for $name" );
	}
	$self->{"_$name"} = deg2rad( $angle );
	return ( $self->{$name} = $angle );
    }
}

sub _set_angle_or_undef {
    my ( $self, $name, $value ) = @_;
    defined $value and 'undef' ne $value and goto &_set_angle;
    return ( $self->{$name} = undef );
}

sub _set_code_ref {
    CODE_REF eq ref $_[2]
	or $_[0]->wail( "Attribute $_[1] must be a code reference" );
    return( $_[0]{$_[1]} = $_[2] );
}

# Set an attribute whose value is an Astro::App::Satpass2::Copier object
# %arg is a hash of argument name/value pairs:
#    {name} is the required name of the attribute to set;
#    {value} is the required value of the attribute to set;
#    {class} is the optional class that the object must be;
#    {default} is the optional default value if the required value is
#        undef or '';
#    {undefined} is an optional value which, if true, permits the
#        attribute to be set to undef;
#    {nocopy} is an optional value which, if true, causes the old
#        object's attributes not to be copied to the new object;
#    {message} is an optional message to emit if the object can not be
#	instantiated;
#    {prefix} is an optional reference to an array of name prefixes to

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

    my ($self, $name, $val) = @_;
    $self->{frame}
	and $self->{frame}[-1]{$name} = $val;
    return ($self->{$name} = $val);
}

sub _set_sun_class {
    my ( $self, $name, $val ) = @_;
    $self->_attribute_exists( $name );
    return $self->sky( class => $name, $val );
}

sub _set_time_parser {
    my ( $self, $name, $val ) = @_;

    if ( CODE_REF eq ref $val ) {
	$val = _set_time_parser_code( $val );
    } elsif ( defined $val and my $macro = $self->{macro}{$val} ) {
	$val = _set_time_parser_code(
	    $macro->implements( $val, required => 1 ),
	    $val,
	);
    }

    return $self->_set_copyable(
	name	=> $name,
	value	=> $val,
	class	=> 'Astro::App::Satpass2::ParseTime',
	message	=> 'Unknown time parser',
	default	=> 'Astro::App::Satpass2::ParseTime',
	nocopy	=> 1,
	prefix	=> [ 'Astro::App::Satpass2::ParseTime' ],
    );
}

sub _set_time_parser_attribute {
    my ( $self, $name, $val ) = @_;
    defined $val and $val eq 'undef' and $val = undef;
    $self->{time_parser}->$name( $val );
    return $val;
}

sub _set_time_parser_code {
    my ( $code, $name ) = @_;
    require Astro::App::Satpass2::ParseTime::Code;
    my $obj = Astro::App::Satpass2::ParseTime::Code->new();
    return $obj->code( $code, $name );
}

_frame_pop_force_set ( 'twilight' );	# Force use of the set() method
					# in _frame_pop(), because we
					# need to set {_twilight} as
					# well.
sub _set_twilight {
    my ($self, $name, $val) = @_;
    if (my $key = $twilight_abbr{lc $val}) {
	$self->{$name} = $key;
	$self->{_twilight} = $twilight_def{$key};
    } else {
	my $angle = $self->__parse_angle( { accept => 1 }, $val );
	looks_like_number( $angle )
	    or $self->wail( 'Twilight must be number or known keyword' );
	$self->{$name} = $val;
	$self->{_twilight} = deg2rad ($angle);
    }
    return $val;
}

sub _set_tz {
    my ( $self, $name, $val ) = @_;
    $self->_set_formatter_attribute( $name, $val );
    $self->_set_time_parser_attribute( $name, $val );
    return $val;
}

sub _set_unmodified {
    return ($_[0]{$_[1]} = $_[2]);
}

sub _set_warner_attribute {
    my ( $self, $name, $val ) = @_;
    defined $val and $val eq 'undef' and $val = undef;
    $self->{_warner}->$name( $val );
    return $val;
}

sub _set_webcmd {
    my ($self, $name, $val) = @_;
    # TODO warn if $val is true but not '1'.
    if ( my $st = $self->get( 'spacetrack' ) ) {
	# TODO once spacetrack supports '1', just pass $val.
	$st->set( webcmd => $self->_get_browser_command( $val ) );
    }
    return ($self->{$name} = $val);
}

sub show : Verb( changes! deprecated! readonly! ) Tweak( -completion _readline_complete_subcommand ) {
    my ( $self, $opt, @args ) = __arguments( @_ );

    foreach my $name ( qw{ deprecated readonly } ) {
	exists $opt->{$name} or $opt->{$name} = 1;
    }
    my $output;

    unless ( @args ) {
	foreach my $name ( sort keys %accessor ) {
	    $self->_attribute_exists( $name, query => 1 )
		or next;
	    $nointeractive{$name}
		and next;
	    exists $mutator{$name}
		or $opt->{readonly}
		or next;
	    my $depr;
	    ( $depr = $self->_deprecation_in_progress( attribute =>
		    $name ) )
		and ( not $opt->{deprecated} or $depr >= 3 )
		and next;
	    push @args, $name;
	}
    }

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

#	$angle = _parse_angle_parts ( @parts );
#
#	Joins parts of angles into an angle.
#	The @parts array is array references describing the parts in
#	decreasing significance, with [0] being the value, and [1] being
#	the number in the next larger part. For the first piece, [1]
#	should be the number in an entire circle.

sub _parse_angle_parts {
    my @parts = @_;
    my $angle = 0;
    my $circle = 1;
    my $places;
    foreach ( @parts ) {
	my ( $part, $size ) = @{ $_ };
	defined $part or last;
	$circle *= $size;
	$angle = $angle * $size + $part;
	$places = $part =~ m/ [.] ( [0-9]+ ) /smx ? length $1 : 0;
    }
    $angle *= 360 / $circle;
    if ( my $mag = sprintf '%d', $circle / 360 ) {
	$places += length $mag;
    }
    return sprintf( '%.*f', $places, $angle ) + 0;
}

# Documented in POD

sub __parse_angle {
    my ( $self, @args ) = @_;
    my $opt = HASH_REF eq ref $args[0] ? shift @args : {};
    my ( $angle ) = @args;
    defined $angle or return;

    if ( $angle =~ m/ : /smx ) {

	my ($h, $m, $s) = split ':', $angle;
	return _parse_angle_parts(
	    [ $h => 24 ],
	    [ $m => 60 ],
	    [ $s => 60 ],
	);

    } elsif ( $angle =~
	m{ \A ( [-+] )? ( [0-9]* ) d
	    ( [0-9]* (?: [.] [0-9]* )? ) (?: m
	    ( [0-9]* (?: [.] [0-9]* )? ) s? )? \z
	}smxi ) {
	my ( $sgn, $deg, $min, $sec ) = ( $1, $2, $3, $4 );
	$angle = _parse_angle_parts(
	    [ $deg => 360 ],
	    [ $min => 60 ],
	    [ $sec => 60 ],
	);
	$sgn and '-' eq $sgn and return -$angle;
	return $angle;
    }

    $opt->{accept}
	or looks_like_number( $angle )
	or $self->wail( "Invalid angle '$angle'" );

    return $angle;
}

# Documented in POD
{
    my %units = (
	au => AU,
	ft => 0.0003048,
	km => 1,
	ly => LIGHTYEAR,
	m => .001,
	mi => 1.609344,
	pc => PARSEC,
    );

    sub __parse_distance {
	my ($self, $string, $dfdist) = @_;
	defined $dfdist or $dfdist = 'km';
	my $dfunits = $dfdist =~ s/ ( [[:alpha:]]+ ) \z //smx ? $1 : 'km';
	my $units = lc (
	    $string =~ s/ \s* ( [[:alpha:]]+ ) \z //smx ? $1 : $dfunits );
	$units{$units}
	    or $self->wail( "Units of '$units' are unknown" );
	$string ne '' or $string = $dfdist;
	looks_like_number ($string)
	    or $self->wail( "'$string' is not a number" );
	return $string * $units{$units};
    }
}

# Documented in POD

sub __parse_time {
    my ($self, $time, $default) = @_;
    my $pt = $self->{time_parser}
	or $self->wail( 'No time parser available' );
    $self->{time_parser}->can( 'station' )
	and $self->_set_time_parser_attribute(
	station => $self->station() );
    if ( defined( my $time = $pt->parse( $time, $default ) ) ) {
	return $time;
    }
    $self->wail( "Invalid time '$time'" );
    return;
}

#	Reset the last time set. This is called from __arguments() in
#	::Utils if the invocant is an Astro::App::Satpass2.

sub __parse_time_reset {
    my ( $self ) = @_;
    defined ( my $pt = $self->{time_parser} )
	or return;
    $pt->reset();
    return;
}

#	$string = _rad2hms ($angle)

#	Converts the given angle in radians to hours, minutes, and
#	seconds (of right ascension, presumably)

sub _rad2hms {
    my $sec = shift;
    $sec *= 12 / PI;
    my $hr = floor( $sec );
    $sec = ( $sec - $hr ) * 60;
    my $min = floor( $sec );
    $sec = ( $sec - $min ) * 60;
    my $rslt = sprintf '%2d:%02d:%02d', $hr, $min, floor( $sec + .5 );
    return $rslt;
}

#	$line = $self->_read_continuation( $in, $error_message );
#
#	Acquire a line from $in, which must be a code reference taking
#	the prompt as an argument. If $in is not a code reference, or if
#	it returns undef, we wail() with the error message.  Otherwise
#	we return the line read. I expect this to be used only by
#	__tokenize().

sub _read_continuation {
    my ( $self, $in, $error ) = @_;
    $in and defined( my $more = $in->(
	    my $prompt = $self->get( 'continuation_prompt' ) ) )

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

lines and comments are ignored. The first token in the line is the
method name, and subsequent tokens are arguments to that method. See
L<run()|/run> for the details of that method, and L</TOKENIZING> for
details of the tokenizer. Finally, see L<initfile()|/initfile> for where
to put your initialization file, which is just a script that gets
executed every time you invoke the L<run()|/run> method.

If you want to be interactive, simply

 use Astro::App::Satpass2;
 Astro::App::Satpass2->run(@ARGV);

which is essentially the content of the F<satpass2> script.  In this
last case, the user will be prompted for commands once the commands in
@ARGV are used up, unless those commands include 'exit'.

=head1 NOTICE

Geocoding using TomTom has been dropped as of version 0.024.
The old, undocumented interface has been dropped, and the new one
requires an API key.

The eventual plan is to retire the F<satpass> script in favor of this
package, and to rename the satpass-less F<Astro-satpass> distribution to
F<Astro-Coord-ECI>.

=head1 OVERVIEW

This class implements an application to predict satellite visibility and
related phenomena. It is a mostly-compatible rewrite and eventual
replacement of the F<satpass> script in distribution C<Astro-satpass>,
aimed at making it easier to test, and removing some of the odder cruft
that has accumulated in the F<satpass> script.

The easiest way to make use of this class is via the bundled F<satpass2>
script, which simply calls the L<run()|/run> method.
L<Astro::App::Satpass2::TUTORIAL|Astro::App::Satpass2::TUTORIAL> covers
getting started with this script. If you do nothing else, see the
tutorial on setting up an initialization file, since the L<satpass2>
script will be much more easy to use if you configure some things up
front.

You can also instantiate an C<Astro::App::Satpass2> object yourself and
access all its functionality programmatically. If you are doing this you
may still want to consult the
L<TUTORIAL|Astro::App::Satpass2::TUTORIAL>, because the F<satpass2>
commands correspond directly to C<Astro::App::Satpass2> methods.

=head1 Optional Modules

An attempt has been made to keep the requirements of this module
reasonably modest. But there are a number of optional modules which, if
installed, give you increased functionality. If you do not install these
initially and find you want the added functionality, you can always
install them later. The optional modules are:

=over

=item L<Astro::SIMBAD::Client|Astro::SIMBAD::Client>

This module looks up the positions of astronomical bodies in the SIMBAD
database at L<https://simbad.cds.unistra.fr/simbad/>. This is only used
by the C<lookup> subcommand of the L<sky()|/sky> method.

=item L<Astro::SpaceTrack|Astro::SpaceTrack>

This module retrieves satellite orbital elements from various sources.
Since you have to have these to predict satellite positions, this is the
least optional of the optional modules. Without it, you would have to
download orbital elements some other way and then use the
L<load()|/load> method to import them into C<Astro::App::Satpass2>.

=item L<Date::Manip|Date::Manip>

This module is a very flexible (and very large) time parser. If it is
installed, C<Astro::App::Satpass2> will use it to parse times. If it is
not available a home-grown ISO-8601-ish parser will be used. There are
really three options here:

* If you have Perl 5.10 or above, you have the full functionality of
L<Date::Manip|Date::Manip>.

* If you a Perl before 5.10, you can (as of this writing) install the
latest L<Date::Manip|Date::Manip>, but you will be using the version 5
back end, which may not support summer time (a.k.a. daylight saving
time) and may have other deficiencies versus the current release.

* The home-grown parser is
L<Astro::App::Satpass2::ParseTime::ISO86O1|Astro::App::Satpass2::ParseTime::ISO8601>.
This does not support summer time, nor time zones other than the user's
default time and GMT. Dates and times must be specified as numeric
year-month-day hour:minute:second, though there is some flexibility on
punctuation, and as a convenience you can use C<yesterday>, C<today>, or
C<tomorrow> in lieu of the C<year-month-day>.

=item L<DateTime|DateTime> and L<DateTime::TimeZone|DateTime::TimeZone>

If both of these are available, C<Astro::App::Satpass2> will use them to
format dates. If they are not, it will use C<POSIX::strftime>. If you
are using C<POSIX::strftime>, time zones other than the default time
zone and GMT are not supported, though if you set the L<tz|/tz>
attribute C<Astro::App::Satpass2> will place its value in C<$ENV{TZ}>
before calling C<strftime()> in case the underlying code pays attention
to this.

If you have L<DateTime|DateTime> and
L<DateTime::TimeZone|DateTime::TimeZone> installed,
C<Astro::App::Satpass2> will let you use C<Cldr> time formats if you
like, instead of C<strftime> formats.

=item L<Geo::Coder::OSM|Geo::Coder::OSM>

This module is used by the Open Street Map geocoder for the
L<geocode()|/geocode> method. If you are not interested in using the
L<geocode()|/geocode> method you do not need this module.

=item L<Geo::WebService::Elevation::USGS|Geo::WebService::Elevation::USGS>

This module is only used by the L<height()|/height> method, or
indirectly by the L<geocode()|/geocode> method. If you are not
interested in these you do not need this module.

lib/Astro/App/Satpass2.pm  view on Meta::CPAN


=over

=item desired_equinox_dynamical

The argument, if any, is parsed using the time parser.

=item format

The following arguments are passed to
L<Astro::App::Satpass2::Format::Template|Astro::App::Satpass2::Format::Template>
L<format()|Astro::App::Satpass2::Format::Template/format>:

 sp       => the invocant of this method;
 template => the first argument to this method;
 arg      => [ all arguments after the first ].

An example may help:

 my $output = $self->formatter( format => qw{ foo bar baz } )

is equivalent to

 my $fmtr = $self->get( 'formatter' );
 my $output = $fmtr->format(
     template => 'foo',
     arg      => [ qw{ bar baz } ],
     sp       => $self,
 );

=back

This method takes the following options:

=over

=item -changes

This option is only useful with the formatter's
L<config()|Astro::App::Satpass2::Format/config> method. It causes
this method to return only changes from the default. It can be negated
by prefixing C<no>.

The default is C<-nochanges>.

=item -raw

This option causes the method to return whatever the underlying method
call returned. If negated (as C<-noraw>), the return is formatted for
text display.

The default is C<-noraw> if called interactively, and C<-raw> otherwise.

=back

=head2 geocode

 $output = $satpass2->geocode('1600 Pennsylvania Ave, Washington DC');
 satpass2> geocode '1600 Pennsylvania Ave, Washington DC'

This interactive method looks up its argument using the currently-set
L<geocoder|/geocoder>. It will fail if no geocoder is set.

If exactly one match is found, the location, latitude, and longitude
attributes are set accordingly.

If exactly one match is found and the L<autoheight|/autoheight>
attribute is true, the L<height()|/height> method will be called on the
resultant position. This operation may fail if the location is outside
the USA.

The argument can be defaulted, in which case the current location
attribute is looked up.

The results of the lookup are returned.

=head2 geodetic

 $satpass2->geodetic( $name, $latitude, $longitude, $elevation );
 satpass2> geodetic name latitude longitude elevation

This interactive method adds a geodetic position to the observing list.
The arguments are the name of the object, the latitude and longitude of
the object (in degrees by default, see L</SPECIFYING ANGLES> for
details), and the height of the object (in kilometers by default, see
L</SPECIFYING DISTANCES> for details) above the current ellipsoid (WGS84
by default). Nothing is returned.

The motivation was to try to judge the observability of those Wallops
Island cloud studies. The L</pass> method will not report on these, but
the L</position> method will.

=head2 get

 $value = $satpass2->get( $name );

This non-interactive method returns the value of the given attribute.
See L<show()|/show> for the corresponding interactive method.

=head2 height

 $output = $satpass2->height( $latitude, $longitude );
 satpass2> height latitude longitude

This interactive method queries the USGS online database for the height
of the ground above sea level at the given latitude and longitude. If
these were not specified, they default to the current settings of the
L</latitude> and L</longitude> attributes.

If the query succeeds, this method returns the 'set' command necessary
to set the height to the retrieved value.

This method will fail if the
L<Geo::WebService::Elevation::USGS|Geo::WebService::Elevation::USGS>
module can not be loaded.

=head2 help

 $output =  $satpass2->help(...)
 satpass2> help

lib/Astro/App/Satpass2.pm  view on Meta::CPAN

This subcommand maintains the classes of background objects. It takes
the following subcommand-specific options:

=over

=item -add

If this Boolean option is asserted, the object is added to the sky once
it is successfully defined.

You may not specify both C<-add> and C<-delete> on the same command.

=item -delete

If this Boolean option is asserted, the arguments are the
case-insensitive names of class definitions to remove. The definition
for the Sun can not be removed, and any class actually instantiated in
the sky can not be removed. Nothing is returned.

You may not specify both C<-add> and C<-delete> on the same command.

=back

Options can be specified either command-line style (with leading dashes
or double dashes, as documented above) or as an optional hash reference
appearing immediately after the subcommand name. In the latter case
option names must be specified in full.

Unless the C<-delete> option is specified (see above), the arguments are
the case-preserved name of the object being defined, the name of the
class that implements it, and optional attribute values (specified as
name/value pairs). You may not specify the C<name> attribute, because
this is derived from the first argument. This information is added to
the known object definitions, replacing the previous definition if any.
Nothing is returned.

If only a name is specified, the definition of that name is returned,
formatted as a C<'sky class'> command. If no arguments at all are
specified, all defined classes are returned.

=head3 clear

This subcommand clears all background objects. It takes no arguments.
Nothing is returned.

=head3 drop

This subcommand removes background objects. The arguments are the names
of the background objects to be removed, or portions thereof. They are
made into a case-insensitive regular expression to perform the removal.
Nothing is returned.

=head3 list

This subcommand returns a string containing a list of the background
objects, in the format of the 'sky add' commands needed to re-create
them. If no subcommand at all is given, 'list' is assumed.

=head3 lookup

This subcommand takes as its argument a name, looks that name up in the
University of Strasbourg's SIMBAD database, and adds the object to the
background. An error occurs if the object can not be found. This
subcommand will fail if the
L<Astro::SIMBAD::Client|Astro::SIMBAD::Client> module can not be loaded.
Nothing is returned.

=head2 source

 $output = $satpass2->source( $file_name );
 satpass2> source file_name

This interactive method takes commands from the given file and runs
them. The concatenated output is returned.

Normally an exception is thrown if the file can not be opened. If the
C<-optional> option is specified, open failures cause the method to
return C<undef>.

This method uses a generic input mechanism, and can load files from a
number of sources. See L</SPECIFYING INPUT DATA> for the details.

=head2 spacetrack

 $satpass2->spacetrack( set => username => 'yehudi' );
 satpass2> spacetrack set username yehudi
 
 say $satpass2->spacetrack( get => 'username' );
 satpass2> spacetrack get username

This interactive method takes as its arguments the name of a method, and
any arguments to be passed to that method. This method is called on the
object which is stored in the
L<spacetrack attribute|/spacetrack attribute>, and any results returned.
Normally it will be used to configure the spacetrack object. See the
L<Astro::SpaceTrack|Astro::SpaceTrack> documentation for further
details.

If the L<Astro::SpaceTrack|Astro::SpaceTrack> method returns
orbital elements, those elements are added to C<Astro::App::Satpass2>'s
internal list.

Similarly, if the L<Astro::SpaceTrack|Astro::SpaceTrack> method returns
Iridium status information, this will replace the built-in status.

In addition to the actual L<Astro::SpaceTrack|Astro::SpaceTrack>
methods, this method emulates methods which it would be useful (to
C<Astro::App::Satpass2> for L<Astro::SpaceTrack|Astro::SpaceTrack> to
have. These are:

=over

=item show

This can be used to display multiple
L<Astro::SpaceTrack|Astro::SpaceTrack> attributes. If no attribute names
are provided, all attributes are displayed. If C<-changes> is specified,
only changed attributes are displayed.

=item config



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