Astro-satpass

 view release on metacpan or  search on metacpan

lib/Astro/Coord/ECI/Moon.pm  view on Meta::CPAN

    next if m/ \A \s* [#] /smx;
    s/^-// and do {
	last if $_ eq 'end';
	$where = $terms{$_} ||= [];
	next;
	};
    s/_//g;
    push @$where, [split '\s+', $_];
    }
}	# End local symbol block.

my %static = (
    id => 'Moon',
    name => 'Moon',
    diameter => 3476,
);

my $weaken = eval {
    require Scalar::Util;
    Scalar::Util->can('weaken');
};

our $Singleton = $weaken;

my %object;	# By class

=item $moon = Astro::Coord::ECI::Moon->new ();

This method instantiates an object to represent the coordinates of the
Moon. This is a subclass of Astro::Coord::ECI, with the id and name
attributes set to 'Moon', and the diameter attribute set to 3476 km
per Jean Meeus' "Astronomical Algorithms", 2nd Edition, Appendix I,
page 407.

Any arguments are passed to the set() method once the object has been
instantiated. Yes, you can override the "hard-wired" id and name in
this way.

If C<$Astro::Coord::ECI::Moon::Singleton> is true, you get a singleton
object; that is, only one object is instantiated and subsequent calls to
C<new()> just return that object. If higher-accuracy subclasses are ever
implemented, there will be one singleton for each class.

The singleton logic only works if L<Scalar::Util|Scalar::Util> exports
C<weaken()>. If it does not, the setting of
C<$Astro::Coord::ECI::Moon::Singleton> is silently ignored. The default
is true if L<Scalar::Util|Scalar::Util> can be loaded and exports
C<weaken()>, and false otherwise.

=cut

sub new {
    my ($class, @args) = @_;
    ref $class and $class = ref $class;
    if ( $Singleton && $weaken && __classisa( $class, __PACKAGE__ ) ) {
	my $self;
	if ( $self = $object{$class} ) {
	    $self->set( @args ) if @args;
	    return $self;
	} else {
	    $self = $object{$class} = $class->SUPER::new (%static, @args);
	    $weaken->( $object{$class} );
	    return $self;
	}
    } else {
	return $class->SUPER::new (%static, @args);
    }
}

=item @almanac = $moon->almanac ($station, $start, $end);

This method produces almanac data for the Moon for the given observing
station, between the given start and end times. The station is assumed
to be Earth-Fixed - that is, you can't do this for something in orbit.

The C<$station> argument may be omitted if the C<station> attribute has
been set. That is, this method can also be called as

 @almanac = $moon->almanac( $start, $end )

The start time defaults to the current time setting of the $moon
object, and the end time defaults to a day after the start time.

The almanac data consists of a list of list references. Each list
reference points to a list containing the following elements:

 [0] => time
 [1] => event (string)
 [2] => detail (integer)
 [3] => description (string)

The @almanac list is returned sorted by time.

The following events, details, and descriptions are at least
potentially returned:

 horizon: 0 = Moon set, 1 = Moon rise;
 transit: 1 = Moon transits meridian;
 quarter: 0 = new moon, 1 = first quarter,
          2 = full moon, 3 = last quarter.

=cut

sub __almanac_event_type_iterator {
    my ( $self, $station ) = @_;

    my $inx = 0;

    my $horizon = $station->__get_almanac_horizon();

    my @events = (
	[ $station, next_elevation => [ $self, $horizon, 1 ],
	    horizon	=> '__horizon_name' ],
	[ $station, next_meridian => [ $self ],
	    transit	=> '__transit_name' ],
	[ $self, next_quarter => [], quarter => '__quarter_name' ],
    );

    return sub {
	$inx < @events
	    and return @{ $events[$inx++] };
	return;
    };
}

use Astro::Coord::ECI::Mixin qw{ almanac };

=item @almanac = $moon->almanac_hash($station, $start, $end);

This convenience method wraps $moon->almanac(), but returns a list of
hash references, sort of like Astro::Coord::ECI::TLE->pass()
does. The hashes contain the following keys:

  {almanac} => {
    {event} => the event type;
    {detail} => the event detail (typically 0 or 1);
    {description} => the event description;
  }
  {body} => the original object ($moon);
  {station} => the observing station;
  {time} => the time the quarter occurred.

The {time}, {event}, {detail}, and {description} keys correspond to
elements 0 through 3 of the list returned by almanac().

=cut

use Astro::Coord::ECI::Mixin qw{ almanac_hash };

=item $coord2 = $coord->clone ();

If singleton objects are enabled, this override of the superclass'
method simply returns the invocant. Otherwise it does a deep clone of an
object, producing a different but identical object.

Prior to version 0.099_01 it always returned a clone. Yes,
this is a change in long-standing functionality, but a long-standing bug
is still a bug.

=cut

sub clone {
    my ( $self ) = @_;
    $Singleton
	and $weaken
	and return $self;
    return $self->SUPER::clone();
}

=item $elevation = $moon->correct_for_refraction( $elevation )

This override of the superclass' method simply returns the elevation
passed to it. Since the Moon has no atmosphere to speak of, there should
be no diffraction to speak of either.

See the L<Astro::Coord::ECI|Astro::Coord::ECI> C<azel()> and
C<azel_offset()> documentation for whether this class'
C<correct_for_refraction()> method is actually called by those methods.

=cut

sub correct_for_refraction {
    my ( undef, $elevation ) = @_;	# Invocant unused
    return $elevation;
}

=item ($time, $quarter, $desc) = $moon->next_quarter ($want);

This method calculates the time of the next quarter-phase of the Moon
after the current time setting of the $moon object. The returns are the
time, which quarter-phase it is as a number from 0 (new moon) to
3 (last quarter), and a string describing the phase. If called in
scalar context, you just get the time.

The optional $want argument says which phase you want.

As a side effect, the time of the $moon object ends up set to the
returned time.

The method of calculation is successive approximation, and actually
returns the second B<after> the quarter.

=cut

use constant NEXT_QUARTER_INCREMENT => 86400 * 6;	# 6 days.

*__next_quarter_coordinate = __PACKAGE__->can( 
    'phase' );

use Astro::Coord::ECI::Mixin qw{ next_quarter };

=item $hash_reference = $moon->next_quarter_hash($want);

This convenience method wraps $moon->next_quarter(), but returns the
data in a hash reference, sort of like Astro::Coord::ECI::TLE->pass()
does. The hash contains the following keys:

  {body} => the original object ($moon);
  {almanac} => {
    {event} => 'quarter',
    {detail} => the quarter number (0 through 3);
    {description} => the quarter description;
  }
  {time} => the time the quarter occurred.

The {time}, {detail}, and {description} keys correspond to elements 0
through 2 of the list returned by next_quarter().

=cut

use Astro::Coord::ECI::Mixin qw{ next_quarter_hash };

=item $period = $moon->period ()

This method returns the sidereal period of the Moon, per Appendix I
(pg 408) of Jean Meeus' "Astronomical Algorithms," 2nd edition.

=cut

sub period {return 2360591.5968}	# 27.321662 * 86400

sub __horizon_name_tplt {
    my ( $self ) = @_;
    return $self->__object_is_self_named() ?
	[ '%s set', '%s rise' ] :
	$self->SUPER::__horizon_name_tplt();
}

sub __quarter_name {
    my ( $self, $event, $tplt ) = @_;
    $tplt ||= [ 'New %s', 'First quarter %s', 'Full %s', 'Last quarter %s' ];
    return $self->__event_name( $event, $tplt );
}

=item ($phase, $illum) = $moon->phase ($time);

This method calculates the current phase of the moon and its illuminated
fraction. If the time is omitted, the current time of the $moon object
is used.

The phase is returned as a number from C<0> to C<2 * PI> radians, with
C<0> being New Moon, C<PI / 2> being First Quarter, and so on. The
illuminated fraction is a number from C<0> (New Moon) to C<1> (Full
Moon).

If called in scalar context, you get the phase.

This can be called as a class method, but if you do this the time
must be specified.

Jean Meeus' "Astronomical Algorithms", 2nd Edition, Chapter 49 page
349, defines the phases of the moon in terms of the difference between
the geocentric longitudes of the Moon and Sun - specifically, that
new, first quarter, full, and last quarter are the moments when this
difference is 0, 90, 180, and 270 degrees respectively.

Not quite above reproach, this module simply defines the phase of the
Moon as the difference between these two quantities, even if it is not
a multiple of 90 degrees. This is different than the "phase angle" of
the Moon, which Meeus defines as the elongation of the Earth from the
Sun, as seen from the Moon. Because we take the "phase angle" as just
pi - the phase (in radians), we introduce an error of about 0.3% in
the illumination calculation.

=cut

sub phase {
    my ($self, $time) = @_;

    (ref $self || $time) or croak <<eod;
Error - You must specify a time if you call phase() as a class method.
eod

    $self = $self->new () unless ref $self;

    $self->universal ($time) if $time;

    my $sun = $self->get( 'sun' )->universal( $self->universal() );

    my (undef, $longs) = $sun->ecliptic ();
    my (undef, $longm) = $self->ecliptic ();

    my $phase = mod2pi ($longm - $longs);
    return wantarray ? ($phase, (1 + cos ($self->PI - $phase)) / 2) : $phase;
}



( run in 1.915 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )