The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Build.PL 11
Changes 057
MANIFEST 15
MANIFEST.SKIP 01
META.json 1111
META.yml 2424
bin/satpass 887
eg/README 08
eg/almanac 11
eg/azimuth 11
eg/closest 11
eg/convert_tle 11
eg/iridium 11
eg/passes 11
eg/visual 0191
eg/xml 11
inc/Astro/Coord/ECI/Test.pm 125
lib/Astro/Coord/ECI/Mixin.pm 11
lib/Astro/Coord/ECI/Moon.pm 11
lib/Astro/Coord/ECI/Star.pm 11
lib/Astro/Coord/ECI/Sun.pm 615
lib/Astro/Coord/ECI/TLE/Iridium.pm 2370
lib/Astro/Coord/ECI/TLE/Set.pm 11
lib/Astro/Coord/ECI/TLE.pm 34706
lib/Astro/Coord/ECI/Utils.pm 834
lib/Astro/Coord/ECI.pm 1323
t/basic.t 03
t/mcnames.mag 09
t/quicksat.mag 010
t/tle_flare.t 016
t/tle_json.t 897
t/tle_mag.t 0201
t/tle_pass.t 213
xt/author/basic.t 03
xt/author/iridium_status.t 22
xt/author/magnitude_status.t 061
xt/author/pod_spelling.t 03
37 files changed (This is a version diff) 1531687
@@ -38,7 +38,7 @@ $mbv >= 0.28 and $attr{meta_merge} = {
     },
     resources => {
 	bugtracker => 'https://rt.cpan.org/Public/Dist/Display.html?Name=Astro-satpass',
-	license => 'http://dev.perl.org/licenses/',
+	license => [ 'http://dev.perl.org/licenses/' ],
     },
 };
 
@@ -1,3 +1,60 @@
+0.062		2014-04-01	T. R. Wyant
+  Update canned status for Iridium 29 (24944). Kelso says it's tumbling.
+  t/tle_json.t now only dumps versions on an error.
+
+0.061_06	2014-03-27	T. R. Wyant
+  Require correct Perl version in t/tle_mag.t
+
+0.061_05	2014-03-26	T. R. Wyant
+  In t/tle_json.t, work around problem that occurs when JSON::XS encodes
+    a number in a locale that uses a comma as the decimal point, such as
+    'de_DE.UTF-8'. Make a note of this in Astro::Coord::ECI::TLE, since
+    there is nothing I can do in my code to actually fix the problem.
+
+0.061_04	2014-03-25	T. R. Wyant
+  Add diagnostics to t/tle_json.t, and robustify, to try to track down
+    sporadic failures.
+
+0.061_03	2014-03-24	T. R. Wyant
+  The 'unpack' built-in does not default to $_ as the buffer to unpack
+    under Perls before 5.10.
+
+0.061_02	2014-03-23	T. R. Wyant
+  Have Astro::Coord::ECI::TLE magnitude() method set the station's time.
+  Correct logic for building the fold_case() utliity subroutine under
+    Perls before 5.16.
+
+0.061_01	2014-03-20	T. R. Wyant
+  Computation of (perhaps very) approximate magnitudes for satellites is
+    now supported. Changes to support this include:
+    - A magnitude() method on all classes that represent satellites;
+    - An intrinsic_magnitude attribute (defaulting to undef) on
+      Astro::Coord::ECI::TLE objects;
+    - Default intrinsic_magnitude to 7 on
+      Astro::Coord::ECI::TLE::Iridium objects;
+    - A canned table of intrinsic magnitudes in Astro::Coord::ECI::TLE
+      which includes most of the objects in the Celestrak visual list,
+      with magnitudes from Mike McCants' mcnames.zip and vsnames.zip
+      files;
+    - Support in the Astro::Coord::ECI::TLE parse() method to set the
+      magnitude of satellites that are in the table;
+    - Static method magnitude_table() on Astro::Coord::ECI::TLE to
+      maintain the canned magnitude table;
+    - Modify the Astro::Coord::ECI::Sun magnitude() method to take an
+      observing station argument (which is ignored) to be consistent
+      with the same-named Astro::Coord::ECI::TLE method.
+  The precess() and precess_dynamical() methods now do nothing to
+    objects set to Earth-fixed coordinates. Previously they were
+    converted to Ecliptic (and therefore inertial) coordinates, which
+    manifested as a bug when the observing station was an attribute of a
+    star.
+  Support new (to us) Space Track JSON keys FILE, OBJECT_TYPE, ORDINAL
+    and ORIGINATOR with same-named (except for being lower case)
+    attributes.
+  Method body_type() now returns the 'object_type' attribute if that is
+    defined. If not it computes its return from the object name, like it
+    always has.
+
 0.061		2014-01-13	T. R. Wyant
   On install, warn that I intend to remove the 'install satpass'
     question asked by Build.PL and Makefile.PL. The only way to install
@@ -12,6 +12,7 @@ eg/positions
 eg/README
 eg/sh_script
 eg/tle_period.t
+eg/visual
 eg/xml
 inc/Astro/Coord/ECI/Build.pm
 inc/Astro/Coord/ECI/Meta.pm
@@ -41,8 +42,10 @@ README.MacOS
 t/basic.t
 t/eci.t
 t/eci_maidenhead.t
+t/mcnames.mag
 t/moon.t
 t/parse_time.t
+t/quicksat.mag
 t/set.t
 t/sgp4-ver.tle
 t/star.t
@@ -53,6 +56,7 @@ t/tle_effective.t
 t/tle_exception.t
 t/tle_flare.t
 t/tle_json.t
+t/tle_mag.t
 t/tle_misc.t
 t/tle_parse.t
 t/tle_pass.t
@@ -63,6 +67,7 @@ xt/author/changes.t
 xt/author/critic.t
 xt/author/executable.t
 xt/author/iridium_status.t
+xt/author/magnitude_status.t
 xt/author/kwalitee.t
 xt/author/manifest.t
 xt/author/perlcriticrc
@@ -71,4 +76,3 @@ xt/author/pod_coverage.t
 xt/author/pod_spelling.t
 xt/author/satpass.t
 xt/author/satpass_geocode.t
-META.json
@@ -1,5 +1,6 @@
 ^\.#
 /\.#
+^\.ackrc
 ^Astro-satpass-
 ^attic/
 ^_build/
@@ -4,7 +4,7 @@
       "Tom Wyant (wyant at cpan dot org)"
    ],
    "dynamic_config" : 1,
-   "generated_by" : "Module::Build version 0.4005, CPAN::Meta::Converter version 2.133380",
+   "generated_by" : "Module::Build version 0.4205",
    "license" : [
       "perl_5"
    ],
@@ -45,39 +45,39 @@
    "provides" : {
       "Astro::Coord::ECI" : {
          "file" : "lib/Astro/Coord/ECI.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::Mixin" : {
          "file" : "lib/Astro/Coord/ECI/Mixin.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::Moon" : {
          "file" : "lib/Astro/Coord/ECI/Moon.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::Star" : {
          "file" : "lib/Astro/Coord/ECI/Star.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::Sun" : {
          "file" : "lib/Astro/Coord/ECI/Sun.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::TLE" : {
          "file" : "lib/Astro/Coord/ECI/TLE.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::TLE::Iridium" : {
          "file" : "lib/Astro/Coord/ECI/TLE/Iridium.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::TLE::Set" : {
          "file" : "lib/Astro/Coord/ECI/TLE/Set.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       },
       "Astro::Coord::ECI::Utils" : {
          "file" : "lib/Astro/Coord/ECI/Utils.pm",
-         "version" : "0.061"
+         "version" : "0.062"
       }
    },
    "release_status" : "stable",
@@ -89,5 +89,5 @@
          "http://dev.perl.org/licenses/"
       ]
    },
-   "version" : "0.061"
+   "version" : "0.062"
 }
@@ -3,13 +3,13 @@ abstract: 'Classes and app to compute satellite visibility'
 author:
   - 'Tom Wyant (wyant at cpan dot org)'
 build_requires:
-  Test::More: 0.88
+  Test::More: '0.88'
 dynamic_config: 1
-generated_by: 'Module::Build version 0.4005, CPAN::Meta::Converter version 2.133380'
+generated_by: 'Module::Build version 0.4205, CPAN::Meta::Converter version 2.140640'
 license: perl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
-  version: 1.4
+  version: '1.4'
 name: Astro-satpass
 no_index:
   directory:
@@ -19,44 +19,44 @@ no_index:
 provides:
   Astro::Coord::ECI:
     file: lib/Astro/Coord/ECI.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::Mixin:
     file: lib/Astro/Coord/ECI/Mixin.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::Moon:
     file: lib/Astro/Coord/ECI/Moon.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::Star:
     file: lib/Astro/Coord/ECI/Star.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::Sun:
     file: lib/Astro/Coord/ECI/Sun.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::TLE:
     file: lib/Astro/Coord/ECI/TLE.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::TLE::Iridium:
     file: lib/Astro/Coord/ECI/TLE/Iridium.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::TLE::Set:
     file: lib/Astro/Coord/ECI/TLE/Set.pm
-    version: 0.061
+    version: '0.062'
   Astro::Coord::ECI::Utils:
     file: lib/Astro/Coord/ECI/Utils.pm
-    version: 0.061
+    version: '0.062'
 requires:
-  Carp: 0
-  Data::Dumper: 0
-  Exporter: 5.64
-  IO::File: 0
-  POSIX: 0
-  Scalar::Util: 0
-  Storable: 0
-  constant: 0
-  perl: 5.006002
-  strict: 0
-  warnings: 0
+  Carp: '0'
+  Data::Dumper: '0'
+  Exporter: '5.64'
+  IO::File: '0'
+  POSIX: '0'
+  Scalar::Util: '0'
+  Storable: '0'
+  constant: '0'
+  perl: '5.006002'
+  strict: '0'
+  warnings: '0'
 resources:
   bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=Astro-satpass
   license: http://dev.perl.org/licenses/
-version: 0.061
+version: '0.062'
@@ -37,7 +37,7 @@ $clipboard_unavailable = $IO::Clipboard::clipboard_unavailable;
 #	Initialization
 #
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use constant LINFMT => <<eod;
 %s %s %8.4f %9.4f %7.1f %-4s %s
@@ -1478,6 +1478,31 @@ eod
 
 ########################################################################
 #
+#	magnitude_table() - Maintain magnitude table
+#
+
+sub magnitude_table {
+    my @arg = @_;
+    @arg
+	or @arg = 'show';
+    my $cmd = shift @arg;
+
+    if ( $cmd eq 'show' ) {
+	foreach my $item ( Astro::Coord::ECI::TLE->magnitude_table(
+		$cmd	=> @arg ) ) {
+	    print join( ' ', qw{ magnitude_table add }, @{ $item } ), "\n";
+	}
+    } elsif ( $cmd eq 'adjust' && ! defined $arg[0] ) {
+	print join( ' ', qw{ magnitude_table adjust },
+	    Astro::Coord::ECI::TLE->magnitude_table( 'adjust' ) ), "\n";
+    } else {
+	Astro::Coord::ECI::TLE->magnitude_table( $cmd, @arg );
+    }
+    return;
+}
+
+########################################################################
+#
 #	pass () - Predict passes over the observer's location, using
 #		the Astro::Coord::ECI::TLE->pass() method.
 #
@@ -1690,7 +1715,7 @@ foreach my $body (@sky) {
 #
 
 BEGIN {
-$cmdlgl{position} = [qw{choose=s@ questionable|spare quiet realtime}];
+$cmdlgl{position} = [qw{choose=s@ magnitude! questionable|spare quiet realtime}];
 }
 
 sub position {
@@ -1712,10 +1737,17 @@ my $sun = Astro::Coord::ECI::Sun->new ();
 
 #	Print a header.
 
-printf <<eod, _format_location (),
+if ( $cmdopt{magnitude} ) {
+    printf <<"EOD", _format_location (),
+%s            name @{[$lcfmt->()]} %-*s illum mag
+EOD
+	length (strftime $dtfmt, _mytime(time)), 'epoch of data';
+} else {
+    printf <<"EOD", _format_location (),
 %s            name @{[$lcfmt->()]} %-*s illum
-eod
+EOD
 	length (strftime $dtfmt, _mytime(time)), 'epoch of data';
+}
 
 #	Tell aggregate() how to work.
 
@@ -1743,9 +1775,20 @@ while ($time <= $endtm) {
 		($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;
-	    printf "%16s %s %s %s\n", $name, $lcfmt->( $body ),
-		strftime ($dtfmt, _mytime($body->get ('epoch'))),
-		$lighting[$illum] || '';
+	    my $line;
+	    if ( $cmdopt{magnitude} ) {
+		my $mag = $body->magnitude( $sta );
+		$mag = defined $mag ? sprintf( '%4.1f', $mag ) : '';
+		$line = sprintf '%16s %s %s %5s %4s', $name, $lcfmt->( $body ),
+		    strftime ($dtfmt, _mytime($body->get ('epoch'))),
+		    $lighting[$illum] || '', $mag;
+	    } else {
+		$line = sprintf '%16s %s %s %5s', $name, $lcfmt->( $body ),
+		    strftime ($dtfmt, _mytime($body->get ('epoch'))),
+		    $lighting[$illum] || '';
+	    }
+	    $line =~ s/ \s+ \z //smx;
+	    print $line, "\n";
 
 	    if ($body->can_flare ($cmdopt{questionable})) {
 ##		$indent ||= length (sprintf '%16s %s', $name,
@@ -2259,12 +2302,14 @@ if (!$rslt->is_success) {
     __PACKAGE__->_iridium_status (@rest);
     $cmdopt{verbose} and print $rslt->content, "\n";
     }
+  elsif ( $content eq 'molczan' || $content eq 'quicksat' ) {
+    magnitude_table( $content, \( $rslt->content() ) );
+    }
   elsif ( ! $st_suppress_output{$content} || $cmdopt{verbose}) {
     print $rslt->content, "\n";
     }
 }
 
-
 ########################################################################
 #
 #	status () - Show satellite status, fetching if necessary.
@@ -4582,6 +4627,40 @@ Be aware that there is no syntax checking done when the macro is
 defined. You only find out if your macro definition is good by
 trying to execute it.
 
+=for html <a name="magnitude_table"></a>
+
+=item magnitude_table ...
+
+This command displays or maintains the satellite magnitude table. This
+table is used to initialize satellite magnitudes.
+
+See the L<Astro::Coord::ECI::TLE|Astro::Coord::ECI::TLE> documentation
+for information on how this table is populated initially. If you have
+installed Astro::SpaceTrack, you can update the status using one of the
+commands that fetches magnitude data, such as C<'st mccants vsnames'>,
+or you can update it using the 'add', 'clear', and 'drop' subcommands,
+which are discussed in more detail below.
+
+C<add> adds the given body to the magnitude table. The arguments are OID
+and magnitude.
+
+C<clear> clears the magnitude table.
+
+C<drop> drops the given body from the magnitude table. The argument is
+the OID to be dropped.
+
+C<molczan> reloads the magnitude table with the contents of the named
+Molczan-format file. An optional second argument is a magnitude offset
+to be added to the magnitudes read.
+
+C<quicksat> reloads the magnitude table with the contents of the named
+Quicksat-format file. An optional second argument is a magnitude offset
+to be added to the magnitudes read.
+
+C<show> displays the contents of the magnitude table, as a series of
+C<'magnitude_table add'> commands. If arguments are passed, only those
+OIDs specified in the arguments are displayed.
+
 =for html <a name="pass"></a>
 
 =item pass start_time end_time increment
@@ -82,4 +82,12 @@ t/xml
     and displays the results as XML, using XML::Writer. The pass_variant
     attribute is used to control what events of a pass are displayed.
 
+visual
+    This script has nothing directly to do with this package. What it
+    does is to download the Celestrak web site's list of visual
+    satellites, and Mike McCants' visual list (with magnitudes) and
+    compares the two. With the -merge option, it also gets McCants'
+    total list and produces a magnitude table suitable for
+    Astro::Coord::ECI::TLE, which is why this script is here.
+
 # ex: set textwidth=72 autoindent :
@@ -17,7 +17,7 @@ use Pod::Usage;
 use POSIX qw{strftime};
 use Time::Local;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 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.061';
+our $VERSION = '0.062';
 
 my $time = time;
 
@@ -23,7 +23,7 @@ use Getopt::Long 2.33;
 use Pod::Usage;
 use POSIX qw{strftime};
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 my %opt;
 
@@ -9,7 +9,7 @@ use Astro::Coord::ECI::TLE;
 use Getopt::Long 2.33;
 use Pod::Usage;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 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.061';
+our $VERSION = '0.062';
 
 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.061';
+our $VERSION = '0.062';
 
 # Map -want-events values to TLE event codes.
 my %wanted_event_map = (
@@ -0,0 +1,191 @@
+#!/usr/bin/env perl
+
+use 5.010;
+
+use strict;
+use warnings;
+
+use Astro::SpaceTrack;
+use Data::Dumper;
+use Getopt::Long 2.33 qw{ :config auto_version };
+use HTTP::Date;
+use LWP::UserAgent;
+use Pod::Usage;
+
+our $VERSION = '0.062';
+
+my %opt;
+
+GetOptions( \%opt,
+    qw{ merge! },
+    help => sub { pod2usage( { -verbose => 2 } ) },
+) or pod2usage( { -verbose => 0 } );
+
+
+my $ast = Astro::SpaceTrack->new();
+my $ua = LWP::UserAgent->new();
+my $mask = 1;
+my $all = 0;
+my @sources;
+my %found;
+my $date = 0;
+my %vsnames;
+
+foreach my $code (
+    sub {
+	return (
+	    'Celestrak visual',
+	    $ua->get( 'http://celestrak.com/SpaceTrack/query/visual.txt'),
+	);
+    },
+    sub {
+	return (
+	    'McCants vsnames',
+	    $ast->mccants( 'vsnames' ),
+	);
+    }
+) {
+    my ( $source, $rslt ) = $code->();
+    process( $source, $rslt, sub {
+	    my ( $oid, $line ) = @_;
+	    $found{$oid} |= $mask;
+	    my $mag;
+	    defined( $mag = unpack_mag( $line ) )
+		and $vsnames{$oid} = $mag;
+	} );
+    $sources[$mask] = $source;
+    $all |= $mask;
+    $mask <<= 1;
+}
+
+my %mcnames;
+
+if ( $opt{merge} ) {
+    my $rslt = $ast->mccants( 'mcnames' );
+    process( 'McCants mcnames', $rslt, sub {
+	    my ( $oid, $line ) = @_;
+	    my $mag;
+	    defined( $mag = unpack_mag( $line ) )
+		and $mcnames{$oid} = $mag;
+	} );
+    foreach my $oid ( keys %found ) {
+	defined( $found{$oid} = $vsnames{$oid} // $mcnames{$oid} )
+	    or delete $found{$oid};
+    }
+    local $Data::Dumper::Terse = 1;
+    local $Data::Dumper::Sortkeys = 1;
+    my $output = Dumper ( \%found );
+    $output =~ s/ \A \s* [{] /%magnitude_table = (/smx;
+    $output =~ s/ [}] \s* \z /);\n/smx;
+    $output =~ s/ ' ( -? \d+ [.] \d+ ) ' /$1/smxg;
+    $output =~ s/ (?<= \d ) (?: (?= \n ) | \z ) /,/smxg;
+    print $output;
+} else {
+    foreach my $oid ( sort { $a <=> $b } keys %found ) {
+	$found{$oid} == $all
+	    and next;
+	say "$oid $sources[$found{$oid}]";
+    }
+}
+say 'Last-Modified: ', time2str( $date );
+
+sub last_modified {
+    my ( $resp ) = @_;
+    my ( $last_modified ) = $resp->header( 'Last-Modified' );
+    defined $last_modified
+	or return;
+    return str2time( $last_modified );
+}
+
+sub process {
+    my ( $source, $rslt, $process ) = @_;
+    $rslt->is_success()
+	or die "Failed to get $source data: ", $rslt->status_line();
+    my $last_mod = last_modified( $rslt );
+    defined $last_mod
+	and $last_mod > $date
+	and $date = $last_mod;
+    foreach my $line ( split qr{ \n }smx, $rslt->content() ) {
+	$line =~ m/ \A ( [0-9]{5} ) /smx
+	    or next;
+	chomp $line;
+	$process->( "$1", $line );
+    }
+    return;
+}
+
+sub unpack_mag {
+    my ( $line ) = @_;
+    49 > length $line
+	and $line = sprintf '%-49s', $line;
+    my ( undef, $mag ) = unpack 'a5x32a5', $line;
+    $mag =~ s/ \s+ //smxg;
+    '' eq $mag
+	and return;
+    return $mag;
+}
+__END__
+
+=head1 TITLE
+
+visual - Compare Celestrak visual to McCants vsnames
+
+=head1 SYNOPSIS
+
+ visual
+ visual -help
+ visual -version
+
+=head1 OPTIONS
+
+=head2 -help
+
+This option displays the documentation for this script. The script then
+exits.
+
+=head2 -merge
+
+If asserted, this option causes Mike McCants' mcnames.mag file to be
+fetched also, and a magnitude hash to be produced that defines
+magnitudes for any body that appears on either the Celestrak visual list
+or the McCants vsnames list, and has a magnitude in either the McCants
+vsnames list or the McCants mcnames list. The output is Data::Dumper
+format.
+
+=head2 -version
+
+This option displays the version of this script. The script then exits.
+
+=head1 DETAILS
+
+This Perl script downloads the list of visual bodies from the Celestrak
+web site and the vsnames.mag file from Mike McCants' web site and
+compares the two, reporting OIDs that are not on both sites and what
+site they B<are> on.
+
+Note, though, that you get different output if you assert C<-merge>.
+
+In either case, though, the last thing you get is a C<Last-Modified:>
+date, which is the most-recent of any of the data sources examined.
+
+=head1 AUTHOR
+
+Thomas R. Wyant, III F<wyant at cpan dot org>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2014 by Thomas R. Wyant, III
+
+This program is free software; you can redistribute it and/or modify it
+under the same terms as Perl 5.10.0. For more details, see the Artistic
+License 1.0 at
+L<http://www.perlfoundation.org/artistic_license_1_0>, and/or the Gnu
+GPL at L<http://www.gnu.org/licenses/old-licenses/gpl-1.0.txt>.
+
+This program is distributed in the hope that it will be useful, but
+without any warranty; without even the implied warranty of
+merchantability or fitness for a particular purpose.
+
+=cut
+
+# ex: set textwidth=72 :
@@ -14,7 +14,7 @@ use Getopt::Long 2.33;
 use Pod::Usage;
 use XML::Writer;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 my %opt = (
     latitude => 52.069858,	# Degrees north of Equator
@@ -5,7 +5,7 @@ use 5.006002;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use base qw{ Exporter };
 
@@ -15,6 +15,7 @@ use Test::More 0.88;
 
 our @EXPORT_OK = qw{
     format_pass format_time
+    magnitude
     tolerance tolerance_frac
     velocity_sanity
 };
@@ -112,6 +113,29 @@ sub format_time {
 	$parts[4] + 1, @parts[ 3, 2, 1, 0 ];
 }
 
+sub magnitude (@) {
+    my ( $tle, @arg ) = @_;
+    my ( $time, $want, $name ) = splice @arg, -3;
+    my $got;
+    eval {
+	$got = $tle->universal( $time )->magnitude( @arg );
+	defined $got
+	    and $got = sprintf '%.1f', $got;
+	1;
+    } or do {
+	@_ = "$name failed: $@";
+	goto &fail;
+    };
+    if ( defined $want ) {
+	$want = sprintf '%.1f', $want;
+	@_ = ( $got, 'eq', $want, $name );
+	goto &cmp_ok;
+    } else {
+	@_ = ( ! defined $got, $name );
+	goto &ok;
+    }
+}
+
 sub tolerance (@) {
     my ( $got, $want, $tolerance, $title, $fmtr ) = @_;
     $fmtr ||= sub { return $_[0] };
@@ -11,7 +11,7 @@ use Astro::Coord::ECI::Utils qw{ __default_station PIOVER2 SECSPERDAY };
 use Exporter ();
 use POSIX qw{ floor };
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 our @EXPORT_OK = qw{
     almanac almanac_hash
@@ -43,7 +43,7 @@ package Astro::Coord::ECI::Moon;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use base qw{Astro::Coord::ECI};
 
@@ -50,7 +50,7 @@ package Astro::Coord::ECI::Star;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use base qw{Astro::Coord::ECI};
 
@@ -50,7 +50,7 @@ package Astro::Coord::ECI::Sun;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use base qw{Astro::Coord::ECI};
 
@@ -58,6 +58,8 @@ use Astro::Coord::ECI::Utils qw{:all};
 use Carp;
 use POSIX qw{floor strftime};
 
+use constant MEAN_MAGNITUDE => -26.8;
+
 my %static = (
     id => 'Sun',
     name => 'Sun',
@@ -238,7 +240,7 @@ eod
 }
 
 
-=item ($point, $intens, $central) = magnitude ($theta, $omega);
+=item ($point, $intens, $central) = $sun->magnitude ($theta, $omega);
 
 This method returns the magnitude of the Sun at a point $theta radians
 from the center of its disk, given that the disk's angular radius
@@ -260,17 +262,24 @@ the average magnitude of the Sun, which is taken to be -26.8.
 The limb-darkening algorithm and the associated constants come from
 L<http://en.wikipedia.org/wiki/Limb_darkening>.
 
+For consistency's sake, an observing station can optionally be passed as
+the first argument (i.e. before C<$theta>). This is currently ignored.
+
 =cut
 
 {	# Begin local symbol block
 
     my $central_mag;
     my @limb_darkening = (.3, .93, -.23);
-    my $mean_mag = -26.8;
 
     sub magnitude {
-	my ($self, $theta, $omega) = @_;
-	return $mean_mag unless defined $theta;
+	my ( $self, @args ) = @_;
+	# We want to accept a station as the second argument for
+	# consistency's sake, though we do not (at this point) use it.
+	embodies( $args[0], 'Astro::Coord::ECI' )
+	    and shift @args;
+	my ( $theta, $omega ) = @args;
+	return MEAN_MAGNITUDE unless defined $theta;
 	unless (defined $omega) {
 	    my @eci = $self->eci ();
 	    $omega = $self->get ('diameter') / 2 /
@@ -282,7 +291,7 @@ L<http://en.wikipedia.org/wiki/Limb_darkening>.
 	    foreach my $a (@limb_darkening) {
 		$sum += $a / $quotient++;
 	    }
-	    $central_mag = $mean_mag - intensity_to_magnitude (2 * $sum);
+	    $central_mag = MEAN_MAGNITUDE - intensity_to_magnitude (2 * $sum);
 	}
 	my $intens = 0;
 	my $point;
@@ -121,7 +121,7 @@ use warnings;
 
 use base qw{Astro::Coord::ECI::TLE};
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use Astro::Coord::ECI::Sun;
 use Astro::Coord::ECI::Utils qw{:all};
@@ -167,6 +167,7 @@ my %static = (		# static values
     am => 1,
     day => 1,
     extinction => 1,
+    intrinsic_magnitude	=> 7.0,
     max_mirror_angle => DEFAULT_MAX_MIRROR_ANGLE,
     pm => 1,
     status => '',
@@ -251,25 +252,33 @@ the object is assumed capable of generating flares.
 
 =cut
 
-sub after_reblessing {
-    my ($self, $attrs) = @_;
-    if (defined $attrs) {
-	$attrs = {%$attrs};
-    } else {
-	$attrs = {};
-    }
-    ref $attrs eq 'HASH' or croak <<eod;
+{
+    # This seems to me to be a bit of a crock, but I can think of no
+    # other way to prevent the intrinsic_magnitude from being clobbered
+    # as not relevant to the class.
+    my %retain = map { $_ => 1 }
+	qw{ intrinsic_magnitude }, keys %mutator;
+
+    sub after_reblessing {
+	my ($self, $attrs) = @_;
+	if (defined $attrs) {
+	    $attrs = {%$attrs};
+	} else {
+	    $attrs = {};
+	}
+	ref $attrs eq 'HASH' or croak <<eod;
 Error - The argument of after_reblessing(), if any, must be a hash
-        reference.
+	reference.
 eod
-    foreach my $key (keys %static) {
-	$attrs->{$key} = $static{$key} unless defined $attrs->{$key};
-    }
-    foreach my $key (keys %$attrs) {
-	delete $attrs->{$key} unless exists $mutator{$key};
+	foreach my $key (keys %static) {
+	    $attrs->{$key} = $static{$key} unless defined $attrs->{$key};
+	}
+	foreach my $key (keys %$attrs) {
+	    delete $attrs->{$key} unless $retain{$key};
+	}
+	$self->set (%$attrs);
+	return;
     }
-    $self->set (%$attrs);
-    return;
 }
 
 
@@ -1398,12 +1407,40 @@ sub _make_status {
     return wantarray ? %stat : \%stat;
 }
 
+=item $mag = $tle->magnitude( $station );
+
+This override of the superclass' method method returns the magnitude of
+the body as seen from the given station at the body's currently-set
+time. If no C<$station> is specified, the object's C<'station'>
+attribute is used.  If that is not set, and exception is thrown.
+
+This method calls the superclass' C<magnitude()>, and returns C<undef>
+if the superclass does. Otherwise it adds to the magnitude of the body
+itself the magnitude of any flare in progress, and returns the result.
+
+=cut
+
+sub magnitude {
+    my ( $self, $sta ) = __default_station( @_ );
+    defined( my $mag = $self->SUPER::magnitude( $sta ) )
+	or return undef;	## no critic (ProhibitExplicitReturnUndef)
+    my $time = $self->universal();
+    my @flare = grep { defined }
+	map { $_->{magnitude} }
+	$self->reflection( $sta, $time );
+    @flare
+	and $mag = add_magnitudes( $mag, @flare );
+    return $mag;
+}
+
 
 =item @data = $tle->reflection ($station, $time)
 
 This method returns a list of references to hashes containing the same
 data as returned for a flare, calculated for the given observer and time
-for all Main Mission Antennae. Note the following differences from the
+for all Main Mission Antennae. If C<$time> is C<undef>, the current time
+setting of the invocant is used. If C<$station> is C<undef> the current
+C<station> attribute is used. Note the following differences from the
 flare() hash:
 
 If the hash contains a 'status' key which is true (in the Perl sense),
@@ -1419,19 +1456,23 @@ the 'status' key is true.
 
 If called in scalar context, a reference to the \@data list is returned.
 
+B<NOTE> that prior to 0.061_01 the C<$time> argument defaulted to the
+current time. This behavior was undocumented, and therefore I felt free
+to change it.
+
 =cut
 
 sub reflection {
-    my ($self, @args) = @_;
+    my ( $self, $station, $time ) = __default_station( @_ );
     my $method = "_reflection_$self->{&ATTRIBUTE_KEY}{algorithm}";
-    return $self->$method (@args);
+    return $self->$method( $station, $time );
 }
 
 
 sub _reflection_fixed {
-    my $self = shift;
-    my $station = shift;
-    my $time = shift || time ();
+    my ( $self, $station, $time ) = @_;
+    defined $time
+	or $time = $self->universal();
     my $debug = $self->get ('debug');
     my $illum = $self->get ('illum')->universal ($time);
 
@@ -1652,6 +1693,12 @@ value Perl considers false (e.g. undef).
 
 The default is 1 (i.e. true).
 
+=item intrinsic_magnitude (numeric or undef)
+
+This attribute is inherited from the parent class, but unlike the parent
+(which defaults it to C<undef>), this class defaults it to C<7.0>, which
+is the average intrinsic magnitude for an Iridium satellite.
+
 =item max_mirror_angle (angle in radians)
 
 This attribute is used in the flare calculation to screen passes for
@@ -140,7 +140,7 @@ our @CARP_NOT = qw{
     Astro::Coord::ECI
 };
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use constant ERR_NOCURRENT => <<eod;
 Error - Can not call %s because there is no current member. Be
@@ -90,6 +90,22 @@ indicated.
 
 =head1 NOTICE
 
+Users of JSON functionality (if any!) should be aware of a potential
+problem in the way L<JSON::XS|JSON::XS> encodes numbers. The problem
+basically is that the locale leaks into the encoded JSON, and if the
+locale uses commas for decimal points the encoded JSON can not be
+decoded. As I understand the discussion on the associated Perl ticket
+the problem has always been there, but changes introduced in Perl 5.19.8
+made it more likely to manifest.
+
+Unfortunately the nature of the JSON interface is such that I have no
+control over the issue, since the workaround needs to be applied at the
+point the JSON C<encode()> method is called. See test F<t/tle_json.t>
+for the workaround that allows tests to pass in the affected locales.
+The relevant L<JSON::XS|JSON::XS> ticket is
+L<https://rt.cpan.org/Ticket/Display.html?id=93307>. The relevant Perl
+ticket is L<https://rt.perl.org/Ticket/Display.html?id=121317>.
+
 The C<pass_threshold> attribute has undergone a slight change in
 functionality from version 0.046, in which it was introduced. In the new
 functionality, if the C<visible> attribute is true, the satellite must
@@ -213,19 +229,20 @@ package Astro::Coord::ECI::TLE;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use base qw{ Astro::Coord::ECI Exporter };
 
-use Astro::Coord::ECI::Utils qw{ :params :time deg2rad dynamical_delta
-    embodies find_first_true load_module looks_like_number
-    max min mod2pi PI PIOVER2 rad2deg
+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 };
 
 use Carp qw{carp croak confess};
 use Data::Dumper;
 use IO::File;
 use POSIX qw{ ceil floor fmod strftime };
+use Scalar::Util ();
 
 BEGIN {
     local $@;
@@ -400,10 +417,14 @@ eod
     epoch_dynamical => undef,	# Read-only
     rcs => 0,		# Radar cross-section
     tle => undef,	# Read-only
-    illum => \&_set_illum,
+    file	=> \&_set_optional_unsigned_integer_no_reinit,
+    illum	=> \&_set_illum,
     launch_year => \&_set_intldes_part,
     launch_num	=> \&_set_intldes_part,
     launch_piece	=> \&_set_intldes_part,
+    object_type	=> \&_set_object_type,
+    ordinal	=> \&_set_optional_unsigned_integer_no_reinit,
+    originator	=> 0,
     pass_threshold => sub {
 	my ($self, $name, $value) = @_;
 	not defined $value
@@ -418,6 +439,7 @@ eod
 	$doit and $_[0]->rebless ();
 	return 0;
     },
+    intrinsic_magnitude	=> \&_set_optional_float_no_reinit,
 );
 my %static = (
     appulse => deg2rad (10),	# Report appulses < 10 degrees.
@@ -441,6 +463,8 @@ foreach (keys %attrib) {
     $model_attrib{$_} = 1 if $attrib{$_} && !ref $attrib{$_}
 }
 my %status;	# Subclassing data - initialized at end
+my %magnitude_table;	# Magnitude data - initialized at end
+my $magnitude_adjust = 0;	# Adjustment to magnitude table value
 
 use constant TLE_INIT => '_init';
 
@@ -572,12 +596,13 @@ sub before_reblessing {}
 =item $type = $tle->body_type ()
 
 This method returns the type of the body as one of the BODY_TYPE_*
-constants. This is derived from the common name using an algorithm
-similar to the one used by the Space Track web site. This algorithm will
-not work if the common name is not available, or if it does not conform
-to the Space Track naming conventions. Known or suspected differences
-from the algorithm described at the bottom of the Satellite Box Score
-page include:
+constants. This is the C<'object_type'> attribute if that is defined.
+Otherwise it is derived from the common name using an algorithm similar
+to the one used by the Space Track web site. This algorithm will not
+work if the common name is not available, or if it does not conform to
+the Space Track naming conventions. Known or suspected differences from
+the algorithm described at the bottom of the Satellite Box Score page
+include:
 
 * The C<Astro::Coord::ECI::TLE> algorithm is not case-sensitive. The
 Space Track algorithm appears to assume all upper-case.
@@ -623,6 +648,9 @@ use constant BODY_TYPE_PAYLOAD => dualvar( 3, 'payload' );
 
 sub body_type {
     my ( $self ) = @_;
+    my $type;
+    $type = $self->get( 'object_type' )
+	and return $type;
     defined( my $name = $self->get( 'name' ) )
 	or return BODY_TYPE_UNKNOWN;
     $name =~ m/ \A \s* \z /smx
@@ -822,6 +850,260 @@ This method can be called as a static method, or even as a subroutine.
 
 }	# End local symbol block
 
+=item $mag = $tle->magnitude( $station );
+
+This method returns the magnitude of the body as seen from the given
+station. If no C<$station> is specified, the object's C<'station'>
+attribute is used. If that is not set, and exception is thrown.
+
+This is calculated from the C<'intrinsic_magnitude'> attribute, the
+distance from the station to the satellite, and the fraction of the
+satellite illuminated. The formula is from Mike McCants.
+
+We return C<undef> if the C<'intrinsic_magnitude'> or C<'illum'>
+attributes are C<undef>, or if the illuminating body is below the
+horizon as seen from the satellite.
+
+After this method returns the time set in the station attribute should
+be considered undefined. In fact, it will be set to the same time as the
+invocant if a defined magnitude was returned. But if C<undef> was
+returned, the station's time may not have been changed.
+
+Some very desultory investigation of International Space Station
+magnitude predictions suggests that this method produces magnitude
+estimates about half a magnitude less bright than Heavens Above.
+
+=cut
+
+sub magnitude {
+    my ( $self, $sta ) = __default_station( @_ );
+
+    # If we have no standard magnitude, just return undef.
+    defined( my $std_mag = $self->get( 'intrinsic_magnitude' ) )
+	or return undef;	## no critic (ProhibitExplicitReturnUndef)
+
+    # If we have no illuminating body for some reason, we also have to
+    # just return undef.
+    my $illum = $self->get( 'illum' )
+	or return undef;	## no critic (ProhibitExplicitReturnUndef)
+
+    # Pick up the time.
+    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)
+
+    # Compute the range amd the elevation.
+    my ( undef, $elev, $range ) = $sta->universal( $time )->azel( $self );
+
+    # If the satellite is below the horizon, just return undef
+    $elev < 0
+	and return undef;	## no critic (ProhibitExplicitReturnUndef)
+
+    # Adjust the magnitude if the illuminating body is not the Sun.
+    my $mag_adj = $illum->isa( 'Astro::Coord::ECI::Sun' ) ? 0 :
+	$illum->magnitude() - Astro::Coord::ECI::Sun->MEAN_MAGNITUDE();
+
+    # Compute the fraction of the satellite illuminated.
+    my $frac_illum = ( 1 + cos( $self->angle( $illum, $sta ) ) ) / 2;
+
+    # Finally we get to McCants' algorithm
+    return $std_mag + $mag_adj - 15.75 +
+	2.5 * log( $range ** 2 / $frac_illum ) / log( 10 );
+
+}
+
+=item Astro::Coord::ECI::TLE->magnitude_table( command => arguments ...)
+
+This method maintains the internal magnitude table, which is used by the
+parse() method to fill in magnitudes, since they are not normally
+available from the usual sources.  The first argument determines what is
+done to the status table; subsequent arguments depend on the first
+argument. Valid commands and arguments are:
+
+C<magnitude_table( add => $id, $mag )> adds a magnitude entry to the
+table, replacing the existing entry for the given OID if any.
+
+C<magnitude_table( adjust => $adjustment )> maintains a magnitude
+adjustment to be added to the value in the magnitude table before
+setting the C<intrinsic_magnitude> of an object. If the argument is
+C<undef> the current adjustment is returned; otherwise the argument
+becomes the new adjustment. Actual magnitude table entries are not
+modified by this operation; the adjustment is done in the C<parse()>
+method.
+
+C<magnitude_table( 'clear' )> clears the magnitude table.
+
+C<magnitude_table( drop => $id )> removes the given OID from the table
+if it is there.
+
+C<magnitude_table( magnitude => \%mag ) replaces the magnitude table
+with the contents of the given hash. The keys will be normalized to 5
+digits.
+
+C<magnitude_table( molczan => $file_name, $mag_offset )> replaces the
+magnitude table with the contents of the named Molczan-format file. The
+C<$file_name> argument can also be a scalar reference with the scalar
+containing the data, or an open handle. The C<$mag_offset> is an
+adjustment to be added to the magnitudes read from the file, and
+defaults to 0.
+
+C<magnitude_table( quicksat => $file_name, $mag_offset )> replaces the
+magnitude table with the contents of the named Quicksat-format file. The
+C<$file_name> argument can also be a scalar reference with the scalar
+containing the data, or an open handle. The C<$mag_offset> is an
+adjustment to be added to the magnitudes read from the file, and
+defaults to 0. In addition to this value, C<0.7> is added to the
+magnitude before storage to adjust the data from full-phase to
+half-phase.
+
+C<magnitude_table( show => ... )> returns an array which is a slice of
+the magnitude table, which is stored as a hash. In other words, it
+returns OID/magnitude pairs in no particular order. If any further
+arguments are passed, they are the OIDs to return. Otherwise all are
+returned.
+
+Examples of Molczan-format data are contained in F<mcnames.zip> and
+F<vsnames.zip> available on Mike McCants' web site; these can be fetched
+using the L<Astro::SpaceTrack|Astro::SpaceTrack> C<mccants()> method. An
+example of Quicksat-format data is contained in F<qsmag.zip>. See Mike
+McCants' web site, L<http://www.prismnet.com/~mmccants/> for an
+explanation of the differences.
+
+Note that if you have one of the reported pure Perl versions of
+L<Scalar::Util|Scalar::Util>, you can not pass open handles to
+functionality that would otherwise accept them.
+
+=cut
+
+
+{
+    my $openhandle = Scalar::Util->can( 'openhandle' ) || sub { return };
+
+    my $parse_file = sub {
+	my ( $file_name, $mag_offset, $parse_info ) = @_;
+	defined $mag_offset
+	    or $mag_offset = 0;
+	$mag_offset += $parse_info->{mag_offset};
+	my %mag;
+	my $fh;
+	if ( $openhandle->( $file_name ) ) {
+	    $fh = $file_name;
+	} else {
+	    open $fh, '<', $file_name	## no critic (RequireBriefOpen)
+		or croak "Failed to open $file_name: $!";
+	}
+	while ( <$fh> ) {
+	    chomp;
+	    m/ \A \s* (?: \# | \z ) /smx
+		and next;	# Extension to syntax.
+	    $parse_info->{pad} > length
+		and $_ = sprintf '%-*s', $parse_info->{pad}, $_;
+	    # Perl 5.8 and below require an explicit buffer to unpack.
+	    my ( $id, $mag ) = unpack $parse_info->{template}, $_;
+	    $mag =~ s/ \s+ //smxg;
+	    looks_like_number( $mag )
+		or next;
+	    $mag{ _normalize_oid( $id ) } = $mag + $parse_info->{mag_offset};
+	}
+	close $fh;
+	%magnitude_table = %mag;
+    };
+
+    my %cmd_def = (
+	add	=> sub {
+	    my ( $id, $mag ) = @_;
+	    defined $id
+		and $id =~ m/ \A [0-9]+ \z /smx
+		and defined $mag
+		and looks_like_number( $mag )
+		or croak 'magnitude_table add needs an OID and a magnitude';
+	    $magnitude_table{ _normalize_oid( $id ) } = $mag;
+	    return;
+	},
+	adjust	=> sub {
+	    my ( $adj ) = @_;
+	    if ( defined $adj ) {
+		looks_like_number( $adj )
+		    or croak 'magnitude_table adjust needs a floating point number';
+		$magnitude_adjust = $adj;
+		return;
+	    } else {
+		return $magnitude_adjust;
+	    }
+	},
+	clear	=> sub {
+	    %magnitude_table = ();
+	    return;
+	},
+	dump	=> sub {
+	    local $Data::Dumper::Terse = 1;
+	    local $Data::Dumper::Sortkeys = 1;
+	    print Dumper( \%magnitude_table );
+	    return;
+	},
+	drop	=> sub {
+	    my ( $id ) = @_;
+	    defined $id
+		and $id =~ m/ \A [0-9]+ \z /smx
+		or croak 'magnitude_table drop needs an OID';
+	    delete $magnitude_table{ _normalize_oid( $id ) };
+	    return;
+	},
+	magnitude	=> sub {
+	    my ( $tbl ) = @_;
+	    'HASH' eq ref $tbl
+		or croak 'magnitude_table magnitude needs a hash ref';
+	    my %mag;
+	    while ( my ( $key, $val ) = each %{ $tbl } ) {
+		$key =~ m/ \A [0-9]+ \z /smx
+		    or croak "OID '$key' must be numeric";
+		looks_like_number( $val )
+		    or croak "Magnitude '$val' must be numeric";
+		$mag{ _normalize_oid( $key ) } = $val;
+	    }
+	    %magnitude_table = %mag;
+	    return;
+	},
+	molczan	=> sub {
+	    my ( $file_name, $mag_factor ) = @_;
+	    $parse_file->( $file_name, $mag_factor, {
+		    mag_offset	=> 0,
+		    pad		=> 49,
+		    template	=> 'a5x32a5',
+		} );
+	    return;
+	},
+	quicksat	=> sub {
+	    my ( $file_name, $mag_factor ) = @_;
+	    $parse_file->( $file_name, $mag_factor, {
+		    mag_offset	=> 0.7,
+		    pad		=> 56,
+		    template	=> 'a5x28a5',
+		} );
+	    return;
+	},
+	show	=> sub {
+	    my ( @arg ) = @_;
+	    @arg
+		or return %magnitude_table;
+	    return (
+		map { $_ => $magnitude_table{$_} }
+		grep { defined $magnitude_table{$_} }
+		map { _normalize_oid( $_ ) } @arg
+	    );
+	},
+    );
+
+    sub magnitude_table {
+	my ( undef, $cmd, @arg ) = @_;	# Invocant not used
+	my $code = $cmd_def{$cmd}
+	    or croak "'$cmd' is not a valid magnitude_table subcommand";
+	return $code->( @arg );
+    }
+}
+
 =item $time = $tle->max_effective_date(...);
 
 This method returns the maximum date among its arguments and the
@@ -948,9 +1230,6 @@ mixture), returning a list of Astro::Coord::ECI::TLE objects. The
 L</Attributes> section identifies those attributes which will be filled
 in by this method.
 
-If the first argument is a hash reference, this will be used to set
-attributes in addition to those set by the parse operation.
-
 The input will be split into individual lines, and all blank lines and
 lines beginning with '#' will be eliminated. The remaining lines are
 assumed to represent two- or three-line element sets, in so-called
@@ -958,13 +1237,6 @@ external format. Internal format (denoted by a 'G' in column 79 of line
 1 of the set, not counting the common name if any) is not supported,
 and the presence of such data will result in an exception being thrown.
 
-There are two pieces of ad-hoc data that will be parsed from the name
-portion of a NASA TLE and put into the appropriate attribute:
-C<< --effective effective_date >> and C<< --rcs radar_cross_section >>.
-These go in the C<effective> and C<rcs> attributes, respectively. If,
-after removing these ad-hoc entries, the name is the empty string, the
-body is considered not to have a name.
-
 =cut
 
 sub parse {
@@ -1066,6 +1338,19 @@ eod
 	$body->{tle} = $tle;
 	push @rslt, $body;
     }
+
+    if ( keys %magnitude_table ) {
+	foreach my $tle ( @rslt ) {
+	    defined( my $oid = $tle->get( 'id' ) )
+		or next;
+	    defined $tle->get( 'intrinsic_magnitude' )
+		and next;
+	    defined( my $std_mag = $magnitude_table{ _normalize_oid( $oid ) } )
+		or next;
+	    $tle->set( intrinsic_magnitude => $std_mag +
+		$magnitude_adjust );
+	}
+    }
     return @rslt;
 }
 
@@ -2154,7 +2439,6 @@ sub set {
     return $self;
 }
 
-
 =item Astro::Coord::ECI::TLE->status (command => arguments ...)
 
 This method maintains the internal status table, which is used by the
@@ -6873,10 +7157,6 @@ so:
  my $json = JSON->new()->convert_blessed( 1 );
  print $json->encode( $tle );
 
-Since the Space Track REST interface is currently in beta, I have to
-assume that the keys returned may change. Any changes B<will> be tracked
-by this method.
-
 The returned keys are a mish-mash of the keys returned by the Space
 Track C<satcat> and C<tle> classes, plus others that are not maintained
 by Space Track. Since the Space Track keys are all upper case, I have
@@ -6897,10 +7177,10 @@ encoded with a four-digit year.
 	    return rad2deg( $self->get( 'argumentofperigee' ) );
 	},
 	BSTAR		=> 'bstardrag',
+	CLASSIFICATION_TYPE	=> 'classification',
 	COMMENT		=> sub {
 	    return 'Generated by ' . __PACKAGE__ . ' v' . $VERSION;
 	},
-	CLASSIFICATION_TYPE	=> 'classification',
 	CREATION_DATE	=> sub {
 	    return _format_json_time( time );
 	},
@@ -6917,6 +7197,7 @@ encoded with a four-digit year.
 	    $epoch =~ s/ [^.]* [.] //smx;
 	    return $epoch;
 	},
+	FILE		=> 'file',
 	INCLINATION	=> sub {
 	    my ( $self ) = @_;
 	    return rad2deg( $self->get( 'inclination' ) );
@@ -6947,20 +7228,27 @@ encoded with a four-digit year.
 	    my ( $self ) = @_;
 	    return $self->get( 'meanmotion' ) * SGP_XMNPDA / TWOPI;
 	},
-	MEAN_MOTION_DOT	=> sub {
-	    my ( $self ) = @_;
-	    return $self->get(
-		'firstderivative'
-	    ) * SGP_XMNPDA * SGP_XMNPDA / TWOPI;
-	},
 	MEAN_MOTION_DDOT	=> sub {
 	    my ( $self ) = @_;
 	    return $self->get(
 		'secondderivative'
 	    ) * SGP_XMNPDA * SGP_XMNPDA * SGP_XMNPDA / TWOPI;
 	},
+	MEAN_MOTION_DOT	=> sub {
+	    my ( $self ) = @_;
+	    return $self->get(
+		'firstderivative'
+	    ) * SGP_XMNPDA * SGP_XMNPDA / TWOPI;
+	},
 	NORAD_CAT_ID	=> 'id',
 	OBJECT_NAME	=> 'name',
+	OBJECT_NUMBER	=> 'id',
+	OBJECT_TYPE	=> sub {
+	    my ( $self ) = @_;
+	    return uc $self->body_type();
+	},
+	ORDINAL		=> 'ordinal',
+	ORIGINATOR	=> 'originator',
 	RA_OF_ASC_NODE	=> sub {
 	    my ( $self ) = @_;
 	    return rad2deg( $self->get( 'ascendingnode' ) );
@@ -6974,10 +7262,13 @@ encoded with a four-digit year.
 		and $name = "0 $name";
 	    return $name;
 	},
+	# TLE_LINE1 is handled programmatically
+	# TLE_LINE2 is handled programmatically
 	effective_date	=> sub {
 	    my ( $self ) = @_;
 	    return _format_json_time( $self->get( 'effective' ) );
 	},
+	intrinsic_magnitude	=> 'intrinsic_magnitude',
     );
 
     sub _format_json_time {
@@ -7074,6 +7365,7 @@ encoded with a four-digit year.
 #	    return _format_json_time( time );
 #	},
 	EPOCH		=> 'epoch',
+	FILE		=> 'file',
 	MEAN_MOTION	=> 'meanmotion',
 	ECCENTRICITY	=> 'eccentricity',
 	INCLINATION	=> 'inclination',
@@ -7087,7 +7379,11 @@ encoded with a four-digit year.
 	BSTAR		=> 'bstardrag',
 	MEAN_MOTION_DOT	=> 'firstderivative',
 	MEAN_MOTION_DDOT	=> 'secondderivative',
+	OBJECT_TYPE	=> 'object_type',
+	ORDINAL		=> 'ordinal',
+	ORIGINATOR	=> 'originator',
 	effective_date	=> 'effective',
+	intrinsic_magnitude	=> 'intrinsic_magnitude',
     );
 
     sub _decode_json_time {
@@ -7320,6 +7616,22 @@ sub __list_type {
     return $self->{inertial} ? 'inertial' : 'fixed';
 }
 
+# _looks_like_real
+#
+# This returns a boolean which is true if the input looks like a real
+# number and is false otherwise. It is based on looks_like_number, but
+# excludes things like NaN, and Inf.
+sub _looks_like_real {
+    my ( $number ) = @_;
+    looks_like_number( $number )
+	or return;
+    $number =~ m/ \A nan \z /smxi
+	and return;
+    $number =~ m/ \A [+-]? inf (?: inity )? \z /smxi
+	and return;
+    return 1;
+}
+
 # *equinox_dynamical = \&Astro::Coord::ECI::equinox_dynamical;
 
 #	$text = $self->_make_tle();
@@ -7461,6 +7773,17 @@ sub _make_tle_checksum {
     return sprintf "%-68s%i\n", substr ($buffer, 0, 68), $sum;
 }
 
+#	_normalize_oid
+#
+#	Normalize an OID by expanding it to five digits.
+
+sub _normalize_oid {
+    my ( $oid ) = @_;
+    $oid =~ m/ [^0-9] /smx
+	and return $oid;
+    return sprintf '%05d', $oid;
+}
+
 #	_set_illum
 
 #	Setting the {illum} attribute is complex enough that the code
@@ -7587,6 +7910,72 @@ sub _set_intldes {
 
 }
 
+# _set_object_type
+#
+# This acts as a mutator for the object type.
+{
+    my %name_to_type;
+    my @number_to_type;
+    foreach my $type (
+	BODY_TYPE_UNKNOWN,
+	BODY_TYPE_DEBRIS,
+	BODY_TYPE_ROCKET_BODY,
+	BODY_TYPE_PAYLOAD,
+    ) {
+	$number_to_type[$type] = $type;
+	$name_to_type{ fold_case( $type ) } = $type;
+    }
+    sub _set_object_type {
+	my ( $self, $name, $value ) = @_;
+	if ( defined $value ) {
+	    if ( $value =~ m/ \A \d+ \z /smx ) {
+		$self->{$name} = $number_to_type[$value];
+	    } else {
+		$self->{$name} = $name_to_type{ fold_case( $value ) };
+	    }
+	    unless ( defined $self->{$name} ) {
+		carp "Invalid $name '$value'; setting to unknown";
+		$self->{$name} = BODY_TYPE_UNKNOWN;
+	    }
+	} else {
+	    $self->{$name} = undef;
+	}
+	return 0;
+    }
+}
+
+# _set_optional_float_no_reinit
+#
+# This acts as a mutator for any attribute whose value is either undef
+# or a floating-point number, and which does not cause the model to be
+# renitialized when its value changes. We disallow NaN.
+
+sub _set_optional_float_no_reinit {
+    my ( $self, $name, $value ) = @_;
+    if ( defined $value && ! _looks_like_real( $value ) ) {
+	carp "Invalid $name '$value'; must be a float or undef";
+	$value = undef;
+    }
+    $self->{$name} = $value;
+    return 0;
+}
+
+# _set_optional_unsigned_integer_no_reinit
+#
+# This acts as a mutator for any attribute whose value is either undef
+# or an unsigned integer, and which does not cause the model to be
+# reinitialized when its value changes.
+
+sub _set_optional_unsigned_integer_no_reinit {
+    my ( $self, $name, $value ) = @_;
+    if ( defined $value && $value =~ m/ [^0-9] /smx ) {
+	carp "Invalid $name '$value'; must be unsigned integer or undef";
+	$value = undef;
+    }
+    $self->{$name} = $value;
+    return 0;
+}
+
 sub _next_elevation_screen {
     my ( $sta, $pass_step, @args ) = @_;
     ref $sta
@@ -8314,7 +8703,7 @@ sub _next_elevation_screen {
              },
   '24944' => {
                'comment' => '',
-               'status' => 0,
+               'status' => 2,
                'name' => 'Iridium 29',
                'class' => 'Astro::Coord::ECI::TLE::Iridium',
                'type' => 'iridium',
@@ -8354,6 +8743,250 @@ sub _next_elevation_screen {
              }
 );
 
+# The following is all the Celestrak visual list that have magnitudes in
+# either McCants' vsnames.mag or mcnames.mag files, with the former
+# being preferred. It is generated by the following:
+#
+#   $ eg/visual -merge
+#
+# Last-Modified: Tue, 07 Jan 2014 03:07:16 GMT
+
+%magnitude_table = (
+  '00694' => 3.5,
+  '00733' => 5.0,
+  '00877' => 5.0,
+  '02802' => 5.5,
+  '03230' => 6.0,
+  '03597' => 6.5,
+  '03598' => 5.0,
+  '03669' => 9.0,
+  '04327' => 6.5,
+  '04814' => 5.0,
+  '05118' => 5.0,
+  '05560' => 5.0,
+  '05730' => 5.0,
+  '06073' => 6.5,
+  '06153' => 6.0,
+  '06155' => 5.0,
+  '07004' => 5.0,
+  '08063' => 5.5,
+  '08459' => 6.0,
+  '10114' => 5.5,
+  '10861' => 5.0,
+  '10967' => 4.0,
+  '11267' => 5.5,
+  '11574' => 5.0,
+  '11672' => 5.0,
+  '11822' => 5.5,
+  '11849' => 5.5,
+  '11933' => 5.0,
+  '12054' => 4.0,
+  '12139' => 5.0,
+  '12154' => 5.5,
+  '12155' => 5.0,
+  '12389' => 4.5,
+  '12465' => 5.0,
+  '12585' => 6.0,
+  '12904' => 5.0,
+  '13068' => 5.0,
+  '13154' => 5.5,
+  '13402' => 6.0,
+  '13403' => 5.0,
+  '13819' => 5.5,
+  '14208' => 5.0,
+  '14372' => 5.5,
+  '14484' => 5.0,
+  '14699' => 5.0,
+  '14819' => 5.5,
+  '14820' => 5.5,
+  '15354' => 5.0,
+  '15483' => 5.5,
+  '15772' => 5.0,
+  '15945' => 5.5,
+  '16111' => 5.0,
+  '16182' => 4.0,
+  '16496' => 5.5,
+  '16792' => 5.5,
+  '16882' => 5.5,
+  '16908' => 5.0,
+  '17295' => 5.0,
+  '17567' => 5.5,
+  '17589' => 5.5,
+  '17590' => 4.0,
+  '17912' => 5.5,
+  '17973' => 5.0,
+  '18153' => 5.5,
+  '18187' => 5.0,
+  '18749' => 5.5,
+  '18958' => 5.5,
+  '19046' => 5.0,
+  '19120' => 3.5,
+  '19210' => 4.5,
+  '19257' => 5.5,
+  '19573' => 5.0,
+  '19574' => 5.0,
+  '19650' => 3.5,
+  '20261' => 6.0,
+  '20262' => 6.5,
+  '20323' => 5.5,
+  '20453' => 5.5,
+  '20465' => 5.0,
+  '20466' => 5.0,
+  '20511' => 5.0,
+  '20580' => 3.0,
+  '20625' => 3.5,
+  '20663' => 5.5,
+  '20666' => 5.5,
+  '20775' => 5.0,
+  '21088' => 5.0,
+  '21397' => 5.5,
+  '21422' => 5.0,
+  '21423' => 5.5,
+  '21574' => 6.0,
+  '21610' => 4.5,
+  '21819' => 5.5,
+  '21820' => 5.5,
+  '21876' => 5.5,
+  '21938' => 5.0,
+  '22220' => 3.5,
+  '22285' => 3.5,
+  '22286' => 5.0,
+  '22566' => 3.5,
+  '22626' => 5.0,
+  '22803' => 3.5,
+  '22830' => 5.0,
+  '23087' => 5.0,
+  '23088' => 3.5,
+  '23343' => 3.5,
+  '23405' => 3.5,
+  '23560' => 4.5,
+  '23561' => 4.5,
+  '23705' => 3.5,
+  '24298' => 3.5,
+  '24680' => 5.5,
+  '24883' => 6.0,
+  '25063' => 4.5,
+  '25064' => 5.0,
+  '25400' => 3.5,
+  '25407' => 3.5,
+  '25544' => -0.5,
+  '25723' => 5.0,
+  '25732' => 5.0,
+  '25860' => 4.5,
+  '25861' => 3.5,
+  '25979' => 4.5,
+  '25994' => 3.5,
+  '26070' => 3.5,
+  '26473' => 3.5,
+  '26474' => 3.5,
+  '26874' => 4.5,
+  '26906' => 3.5,
+  '26934' => 4.5,
+  '27006' => 3.5,
+  '27386' => 4.5,
+  '27387' => 4.0,
+  '27421' => 5.5,
+  '27422' => 4.5,
+  '27424' => 4.0,
+  '27432' => 4.5,
+  '27550' => 5.5,
+  '27597' => 3.5,
+  '27601' => 3.5,
+  '27640' => 5.5,
+  '27665' => 5.5,
+  '27698' => 4.5,
+  '28059' => 5.5,
+  '28096' => 3.5,
+  '28222' => 5.0,
+  '28230' => 5.0,
+  '28353' => 3.5,
+  '28376' => 5.0,
+  '28415' => 5.0,
+  '28480' => 4.5,
+  '28499' => 4.5,
+  '28538' => 3.5,
+  '28646' => 3.5,
+  '28647' => 3.5,
+  '28738' => 4.0,
+  '28773' => 5.0,
+  '28888' => 5.5,
+  '28931' => 4.0,
+  '28932' => 4.5,
+  '28939' => 5.5,
+  '29093' => 4.0,
+  '29228' => 4.5,
+  '29252' => 5.5,
+  '29393' => 4.5,
+  '29507' => 3.5,
+  '29659' => 5.5,
+  '30586' => 5.5,
+  '30587' => 5.5,
+  '30778' => 4.0,
+  '31114' => 4.0,
+  '31598' => 4.5,
+  '31702' => 3.5,
+  '31789' => 6.5,
+  '31792' => 4.0,
+  '31793' => 3.5,
+  '31798' => 5.5,
+  '32053' => 5.5,
+  '32284' => 5.5,
+  '32290' => 5.5,
+  '32376' => 5.5,
+  '32751' => 6.0,
+  '33053' => 6.0,
+  '33106' => 5.5,
+  '33245' => 5.5,
+  '33272' => 4.5,
+  '33410' => 4.0,
+  '33412' => 6.5,
+  '33500' => 3.5,
+  '33504' => 6.0,
+  '33505' => 5.5,
+  '34602' => 6.5,
+  '34840' => 5.5,
+  '36089' => 3.5,
+  '36095' => 3.5,
+  '36104' => 5.5,
+  '36105' => 4.2,
+  '36123' => 5.5,
+  '36125' => 4.5,
+  '36416' => 4.0,
+  '36520' => 5.5,
+  '36597' => 5.0,
+  '36800' => 5.5,
+  '36835' => 4.5,
+  '37181' => 5.5,
+  '37215' => 4.5,
+  '37216' => 6.5,
+  '37253' => 3.5,
+  '37348' => 4.5,
+  '37673' => 4.0,
+  '37731' => 3.5,
+  '37766' => 4.5,
+  '37782' => 4.0,
+  '37814' => 4.2,
+  '37820' => 4.0,
+  '37932' => 5.0,
+  '37942' => 4.0,
+  '37955' => 4.2,
+  '38039' => 4.5,
+  '38109' => 4.0,
+  '38341' => 4.5,
+  '38355' => 4.5,
+  '38737' => 5.0,
+  '38755' => 5.5,
+  '38758' => 4.5,
+  '38770' => 3.5,
+  '38773' => 4.5,
+  '38862' => 4.5,
+  '39000' => 4.0,
+  '39014' => 4.0,
+  '39019' => 4.5,
+  '39025' => 4.5,
+  '39130' => 5.0,
+);
+
 1;
 
 __END__
@@ -8459,6 +9092,12 @@ This attribute contains the dynamical time corresponding to the
 L<epoch|/item_epoch>. Setting the L<epoch|/item_epoch> also modifies
 this attribute.
 
+=item file (numeric)
+
+This attribute contains the file number of the TLE data. It is typically
+present only if the TLE object was parsed from a Space Track JSON file;
+otherwise it is undefined.
+
 =item firstderivative (numeric, parse)
 
 This attribute contains the first time derivative of the mean
@@ -8545,6 +9184,14 @@ the order of the launch within the year, and one to three letters
 designating the "part" of the launch, with payload(s) getting the
 first letters, and spent boosters, debris, etc getting the rest.
 
+=item intrinsic_magnitude (numeric or undef)
+
+If defined, this is the typical magnitude of the body at half phase and
+range 1000 kilometers, when illuminated by the Sun. I am not sure how
+standard this definition really is.  This is the definition used by Ted
+Molczan, but Mike McCants' Quicksat data uses maximum magnitude full
+phase.
+
 =item lazy_pass_position (boolean, static)
 
 This attribute tells the pass() method that the C<{azimuth}>,
@@ -8598,6 +9245,31 @@ The default is undef.
 
 This attribute contains the common name of the body.
 
+=item object_type
+
+This attribute contains the object type of the TLE data. It is typically
+present only if the TLE object was parsed from a Space Track JSON file;
+otherwise it is undefined. However, if it is defined it will be returned
+by the C<body_type()> method.
+
+When setting this, possible values are those returned by C<body_type()>
+-- either the dualvar constants themselves, or the numeric or
+case-insensitive strings corresponding to them. Whatever is set, what is
+stored and returned will be one of the C<BODY_TYPE_*> constants. Any
+unrecognized value will be stored as C<BODY_TYPE_UNKNOWN>, with a warning.
+
+=item ordinal (numeric)
+
+This attribute contains the ordinal number of the TLE data. It is
+typically present only if the TLE object was parsed from a Space Track
+JSON file; otherwise it is undefined.
+
+=item originator (string)
+
+This attribute contains the name of the originator of the TLE data. It
+is typically present only if the TLE object was parsed from a Space
+Track JSON file; otherwise it is undefined.
+
 =item pass_variant (bits)
 
 This attribute tweaks the pass output as specified by its value, which
@@ -104,7 +104,7 @@ package Astro::Coord::ECI::Utils;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 our @ISA = qw{Exporter};
 
 use Carp;
@@ -139,15 +139,15 @@ our @EXPORT;
 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 asin
+	SPEED_OF_LIGHT TWOPI acos add_magnitudes asin
 	atmospheric_extinction date2epoch date2jd deg2rad distsq
 	dynamical_delta embodies epoch2datetime equation_of_time
-	find_first_true 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
+	find_first_true fold_case 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 },
 	@time_routines );
 
@@ -189,6 +189,23 @@ eod
     return atan2 (sqrt (1 - $_[0] * $_[0]), $_[0])
 }
 
+=item $mag = add_magnitudes( $mag1, $mag2, ... );
+
+This subroutine computes the total magnitude of a list of individual
+magnitudes.  The algorithm comes from Jean Meeus' "Astronomical
+Algorithms", Second Edition, Chapter 56, Page 393.
+
+=cut
+
+sub add_magnitudes {
+    my @arg = @_;
+    my $sum = 0;
+    foreach my $mag ( @arg ) {
+	$sum += 10 ** ( -0.4 * $mag );
+    }
+    return -2.5 * log( $sum ) / log( 10 );
+}
+
 
 =item $angle = asin ($value)
 
@@ -563,6 +580,15 @@ sub find_first_true {
     return $end;
 }
 
+=item $folded = fold_case( $text );
+
+This function folds the case of its input, kinda sorta. It maps to
+C<CORE::fc> if that is available, otherwise it maps to C<CORE::lc>.
+
+=cut
+
+*fold_case = CORE->can( 'fc' ) || sub ($) { return lc $_[0] };
+
 
 =item $difference = intensity_to_magnitude ($ratio)
 
@@ -143,7 +143,7 @@ package Astro::Coord::ECI;
 use strict;
 use warnings;
 
-our $VERSION = '0.061';
+our $VERSION = '0.062';
 
 use Astro::Coord::ECI::Utils qw{:all};
 use Carp;
@@ -1026,6 +1026,7 @@ sub equatorial {
 ##	my ($ra, $dec, $range, @eqvel) = @args;
 	$args[0] = _check_right_ascension( 'right ascension' => $args[0] );
 	$args[1] = _check_latitude( declination => $args[1] );
+	foreach my $key (@kilatr) {delete $self->{$key}}
 	$self->{_ECI_cache}{inertial}{equatorial} = \@args;
 	$self->eci(
 	    _convert_spherical_to_cartesian( @args ) );
@@ -2339,11 +2340,9 @@ universal time.>
 =cut
 
 sub precess {
-    my $self = shift;
-    if (@_ && $_[0]) {
-	$_[0] += dynamical_delta ($_[0]);
-    }
-    return $self->precess_dynamical (@_);
+    $_[1]
+	and $_[1] += dynamical_delta( $_[1] );
+    goto &precess_dynamical;
 }
 
 
@@ -2361,6 +2360,10 @@ unaffected by this operation.>
 As a side effect, the value of the 'equinox_dynamical' attribute will be
 set to the dynamical time corresponding to the argument.
 
+As of version 0.061_01, this does nothing to non-inertial
+objects -- that is, those whose position was set in Earth-fixed
+coordinates.
+
 If the object's 'station' attribute is set, the station is also
 precessed.
 
@@ -2377,13 +2380,19 @@ sub precess_dynamical {
     $end
 	or croak "No equinox time specified";
 
-    (defined (my $start = $self->get ('equinox_dynamical'))
-	|| !$self->get ('inertial'))
+    # Non-inertial coordinate systems are not referred to the equinox,
+    # and so do not get precessed.
+    $self->get( 'inertial' )
+	or return $self;
+
+    defined ( my $start = $self->get( 'equinox_dynamical' ) )
 	or carp "Warning - Precess called with equinox_dynamical ",
 	    "attribute undefined";
     $start ||= $self->dynamical ();
 
-    if ( my $sta = $self->get( 'station' ) ) {
+    my $sta;
+    if ( $sta = $self->get( 'station' ) and $sta->get( 'inertial' ) 
+    ) {
 	$sta->get( 'station' )
 	    and croak NO_CASCADING_STATIONS;
 	$sta->universal( $self->universal() );
@@ -2424,10 +2433,6 @@ sub precess_dynamical {
     $self->equatorial ($alpha, $delta, $rho0);
     $self->set (equinox_dynamical => $end);
 
-    if ( my $sta = $self->get( 'station' ) ) {
-	$sta->precess_dynamical( $end );
-    }
-
     return $self;
 }
 
@@ -3239,6 +3244,11 @@ eod
 	$data[3] -= $data[1] * $self->{angularvelocity};
 	$data[4] += $data[0] * $self->{angularvelocity};
     }
+
+    # A bunch of digging says this was added by Subversion commit 697,
+    # which was first released with Astro::Coord::ECI version 0.014. The
+    # comment says "Handle equinox in conversion between eci and ecef.
+    # Correctly, I hope." I'm leaving it in for now, but ...
     $self->set (equinox_dynamical => $self->dynamical);
     return @{$self->{_ECI_cache}{inertial}{eci} = \@data};
 }
@@ -111,6 +111,9 @@ u_cmp_eql equation_of_time => timegm( 0, 0, 0, 13, 9, 92 ),
 u_cmp_eql obliquity => timegm( 0, 0, 0, 10, 3, 87 ), 0.409167475225493,
     '%.5f', 'obliquity: Midnight Nov 3 1987: Meeus ex 22.a';
 
+u_cmp_eql add_magnitudes => [ 4.73, 5.22, 5.60 ], 3.93, '%.2f',
+    'add_magnitudes: Meeus ex 56.b';
+
 u_cmp_eql intensity_to_magnitude => 500, -6.75, '%.2f',
     'intensity_to_magnitude: 500: Meeus ex 56.e';
 
@@ -0,0 +1,9 @@
+# The following data are an extract of Mike McCants' Molczan-format
+# magnitude and physical dimenstions data as downloaded from
+# http://www.prismnet.com/~mmccants/tles/mcnames.zip on March 7 2014 at
+# 4:25 GMT. The Last-Modified header on this data was Mon, 06 Jan 2014
+# 10:21:29 GMT
+
+20580 HST             13.3  4.3  0.0  3.0 v   29
+25544 ISS             30.0 20.0  0.0 -0.5 v  297
+37820 Tiangong 1      10.4  3.4  0.0  4.0 v   18
@@ -0,0 +1,10 @@
+# The following data are an extract of Mike McCants' Quicksat-format magnitude
+# and physical dimenstions data as downloaded from
+# http://www.prismnet.com/~mmccants/programs/qsmag.zip on March 7 2014 at 4:25
+# GMT. The Last-Modified header on this data was Mon, 06 Jan 2014 10:10:38 GMT
+
+00001 d Desig...  Name.......... Mag.  Sz1 Sz2 Sz3 RCS Comments 39486
+20580   90 37B    HST             1.5 13.3 4.3 0.0  29 occ flare to -4
+25544   98 67A    ISS            -2.0              297
+37820   11 53A    Tiangong 1      2.5               18
+99999 d Desig...  Name.......... Mag.  Sz1 Sz2 Sz3 RCS Comments
@@ -23,6 +23,10 @@ use Astro::Coord::ECI::TLE qw{ :constants };
 use Astro::Coord::ECI::TLE::Iridium;
 use Astro::Coord::ECI::Utils qw{ deg2rad PARSEC rad2deg SECSPERDAY };
 
+use lib qw{ inc };
+
+use Astro::Coord::ECI::Test qw{ magnitude };
+
 my $sta = Astro::Coord::ECI->new(
     name => 'Greenwich Observatory',
 )->geodetic(
@@ -89,10 +93,16 @@ is format_flare( $flare[0] ), <<'EOD', 'Flare 1';
 1980/10/13 05:43:26  29.9  48.1   412.9 -0.4 1 am
 EOD
 
+magnitude $tle, $sta, $flare[0]{time},
+    -0.4, 'Compute magnitude at time of flare 1';
+
 is format_flare( $flare[1] ), <<'EOD', 'Flare 2';
 1980/10/13 14:58:33  42.8 204.9   393.0 -3.0 1 day
 EOD
 
+magnitude $tle, $sta, $flare[1]{time},
+    -3.0, 'Compute magnitude at time of flare 2';
+
 note 'Flares over location specified by station attribute';
 
 if (
@@ -115,10 +125,16 @@ is format_flare( $flare[0] ), <<'EOD', 'Flare 1';
 1980/10/13 05:43:26  29.9  48.1   412.9 -0.4 1 am
 EOD
 
+magnitude $tle, $flare[0]{time},
+    -0.4, 'Compute magnitude at time of flare 1';
+
 is format_flare( $flare[1] ), <<'EOD', 'Flare 2';
 1980/10/13 14:58:33  42.8 204.9   393.0 -3.0 1 day
 EOD
 
+magnitude $tle, $flare[1]{time},
+    -3.0, 'Compute magnitude at time of flare 2';
+
 done_testing;
 
 ########################################################################
@@ -5,7 +5,7 @@ use 5.006002;
 use strict;
 use warnings;
 
-use Astro::Coord::ECI::TLE;
+use Astro::Coord::ECI::TLE qw{ BODY_TYPE_DEBRIS BODY_TYPE_PAYLOAD };
 use Astro::Coord::ECI::TLE::Iridium;
 use Test::More 0.88;	# Because of done_testing();
 
@@ -36,6 +36,13 @@ EOD
 
 my ( $tle ) = Astro::Coord::ECI::TLE->parse( $vanguard );
 
+$tle->set(
+    file	=> 42,
+    ordinal	=> 666,
+    originator	=> 'Arthur Dent',
+    intrinsic_magnitude	=> 11.0,
+);
+
 my $hash = $tle->TO_JSON();
 
 foreach my $key ( qw{
@@ -49,6 +56,7 @@ foreach my $key ( qw{
 	EPHEMERIS_TYPE
 	EPOCH
 	EPOCH_MICROSECONDS
+	FILE
 	INCLINATION
 	INTLDES
 	LAUNCH_NUM
@@ -60,6 +68,10 @@ foreach my $key ( qw{
 	MEAN_MOTION_DOT
 	NORAD_CAT_ID
 	OBJECT_NAME
+	OBJECT_NUMBER
+	OBJECT_TYPE
+	ORDINAL
+	ORIGINATOR
 	RA_OF_ASC_NODE
 	RCSVALUE
 	REV_AT_EPOCH
@@ -67,6 +79,7 @@ foreach my $key ( qw{
 	TLE_LINE1
 	TLE_LINE2
 	effective_date
+	intrinsic_magnitude
     } ) {
     ok exists $hash->{$key}, "Hash key $key is present for Vanguard 1";
 }
@@ -84,6 +97,7 @@ is_deeply $hash, {
     'EPHEMERIS_TYPE' => '0',
     'EPOCH' => '2000-06-27 18:50:19',
     'EPOCH_MICROSECONDS'	=> '733568',
+    FILE	=> '42',
     'INCLINATION' => '34.2682',
     'INTLDES' => '58002B',
     'LAUNCH_NUM' => '002',
@@ -95,21 +109,67 @@ is_deeply $hash, {
     'MEAN_MOTION_DDOT' => '0',
     'NORAD_CAT_ID' => '00005',
     'OBJECT_NAME' => 'VANGUARD 1',
+    'OBJECT_NUMBER'	=> '00005',
+    OBJECT_TYPE	=> uc BODY_TYPE_PAYLOAD,
+    ORDINAL	=> 666,
+    ORIGINATOR	=> 'Arthur Dent',
     'RA_OF_ASC_NODE' => '348.7242',
     'RCSVALUE' => '0.254',
     'REV_AT_EPOCH' => '41366',
     'TLE_LINE0' => '0 VANGUARD 1',
     'TLE_LINE1' => '1 00005U 58002B   00179.78495062  .00000023  00000-0  28098-4 0  4753',
     'TLE_LINE2' => '2 00005  34.2682 348.7242 1859667 331.7664  19.3264 10.82419157413667',
-    'effective_date' => '2000-06-27 22:00:00'
+    'effective_date' => '2000-06-27 22:00:00',
+    intrinsic_magnitude	=> 11.0,
 }, 'Test the hash generated by TO_JSON() for Vanguard 1.';
 
-my $json = JSON->new()->utf8()->convert_blessed();
-
-{
-    my $data = $json->encode( [ $tle ] );
-    my ( $tle2 ) = Astro::Coord::ECI::TLE->parse( $data );
-    is $tle2->get( 'tle' ), $vanguard, 'Vanguard 1 round-trip via JSON';
+# The canonical() is for sanity's sake in case the decode fails in the
+# following round-trip test.
+my $json = JSON->new()->utf8()->convert_blessed()->canonical();
+
+{   # Local symbol block. Also single-iteration loop.
+    my $name = 'Vanguard 1 round-trip via JSON';
+
+    my $data;
+    # The following setlocale() stuff is a workaround for JSON::XS bug
+    # https://rt.cpan.org/Public/Bug/Display.html?id=93307
+    # As of this writing, it only affects Perls 5.19.8 through 5.19.10,
+    # and only if JSON::XS is being used. The bug report relates it to
+    # commit bc8ec7cc020d0562094a551b280fd3f32bf5eb04. See
+    # https://rt.perl.org/Ticket/Display.html?id=121317 which is the
+    # related Perl ticket.
+    use POSIX qw{ setlocale LC_NUMERIC };
+    my $locale = setlocale( LC_NUMERIC );
+    eval {
+	# The following setlocale() is what makes the code work when
+	# JSON::XS is in use. Without it, the call to
+	# Astro::Coord::ECI::TLE->parse() below will fail if the
+	# LC_NUMERIC environment variable is something like
+	# 'de_DE.UTF-8'.
+	setlocale( LC_NUMERIC, "C" );
+	$data = $json->encode( [ $tle ] );
+	1;
+    } or do {
+	setlocale( LC_NUMERIC, $locale );
+	fail "$name failed to encode JSON: $@";
+	_json_config();
+	last;
+    };
+    setlocale( LC_NUMERIC, $locale );
+
+    my $tle2;
+    eval {
+	( $tle2 ) = Astro::Coord::ECI::TLE->parse( $data );
+	1;
+    } or do {
+	fail "$name failed to parse JSON: $@";
+	_json_config();
+	diag $data;
+	last;
+    };
+
+    is $tle2->get( 'tle' ), $vanguard, $name
+	or diag _json_config();
 }
 
 Astro::Coord::ECI::TLE->status( add => 5, iridium => 'S' );
@@ -124,6 +184,10 @@ FAKE IRIDIUM
 2 00005  34.2682 348.7242 1859667 331.7664  19.3264 10.82419157413667
 EOD
 
+$tle->set(
+    object_type	=> 'Debris',
+);
+
 $hash = $tle->TO_JSON();
 
 foreach my $key ( qw{
@@ -148,6 +212,8 @@ foreach my $key ( qw{
 	MEAN_MOTION_DOT
 	NORAD_CAT_ID
 	OBJECT_NAME
+	OBJECT_NUMBER
+	OBJECT_TYPE
 	RA_OF_ASC_NODE
 	REV_AT_EPOCH
 	TLE_LINE0
@@ -183,12 +249,15 @@ is_deeply $hash, {
     'MEAN_MOTION_DDOT' => '0',
     'NORAD_CAT_ID' => '00005',
     'OBJECT_NAME' => 'FAKE IRIDIUM',
+    'OBJECT_NUMBER'	=> '00005',
     'RA_OF_ASC_NODE' => '348.7242',
+    OBJECT_TYPE	=> uc BODY_TYPE_DEBRIS,
     'REV_AT_EPOCH' => '41366',
     'TLE_LINE0' => '0 FAKE IRIDIUM',
     'TLE_LINE1' => '1 00005U 58002B   00179.78495062  .00000023  00000-0  28098-4 0  4753',
     'TLE_LINE2' => '2 00005  34.2682 348.7242 1859667 331.7664  19.3264 10.82419157413667',
     'operational_status' => 'S',
+    intrinsic_magnitude	=> 7,	# Added by after_reblessing()
 }, 'Test the hash generated by TO_JSON() for Vanguard 1.';
 
 # This TLE duplicates the above, and comes from the same source. The
@@ -231,6 +300,26 @@ sub _fudge_json {
     return;
 }
 
+sub _json_config {
+    diag '';
+    foreach my $json ( qw{ JSON JSON::PP JSON::XS } ) {
+	my $version;
+	eval {
+	    $version = $json->VERSION();
+	    1;
+	};
+	defined $version
+	    or $version = 'undef';
+	diag sprintf '%-10s %s', $json, $version;
+    }
+    foreach my $name ( qw{ LC_ALL LC_NUMERIC } ) {
+	my $val = $ENV{$name};
+	$val = defined $val ? "'$val'" : 'undef';
+	diag sprintf '$ENV{%s} %s', $name, $val;
+    }
+    return;
+}
+
 1;
 
 # ex: set textwidth=72 :
@@ -0,0 +1,201 @@
+package main;
+
+use 5.006002;
+
+use strict;
+use warnings;
+
+use Astro::Coord::ECI::TLE;
+use Test::More 0.88;	# Because of done_testing();
+
+sub clear (;$);
+sub succeeds (@);
+
+my @rslt;
+
+note <<'EOD';
+This test is of the proper population of the magnitude table only.
+Actual computation tests are done elsewhere.
+EOD
+
+succeeds clear => 'Clearing the magnitude table';
+
+succeeds show => 'Retrieving the empty magnitude table';
+
+is_deeply \@rslt, [], 'There are no magnitudes in the table';
+
+succeeds magnitude => { 25544 => -1.0 },
+    'Setting the magnitude table directly';
+
+succeeds show => 'Retrieving the magnitude table';
+
+is_deeply \@rslt, [ 25544 => -1.0 ],
+    'Contents of magnitude table are as expected';
+
+succeeds show => 5,
+    'Retrieving a magnitude which is not in the table';
+
+is_deeply \@rslt, [], 'The retrieval of 00005 returned nothing';
+
+succeeds add => 00005 => 11, 'Adding OID 5';
+
+succeeds show => 5, 'Retrieving an added magnitude';
+
+is_deeply \@rslt, [ '00005' => 11 ],
+    'Got back correct magnitude for OID 5';
+
+succeeds show => 25544, 'Retrieving originally-loaded magnitude';
+
+is_deeply \@rslt, [ 25544, -1.0 ],
+    'Got back correct originally-loaded magnitude';
+
+succeeds show => 'Retrieving all loaded magnitudes';
+
+is_deeply { @rslt }, {
+    '00005'	=> 11,
+    '25544'	=> -1,
+}, 'The contents of the magnitude table are as expected';
+
+succeeds drop => 5, 'Dropping OID 5';
+
+succeeds show => 'Retrieving modified magnitudes';
+
+is_deeply \@rslt, [ 25544 => -1 ],
+    'Dropped body is gone from table';
+
+succeeds molczan => 't/mcnames.mag',
+    'Loading a Molczan-format magnitude file';
+
+succeeds show => 'Retrieving Molczan-format magnitudes';
+
+my $want = {
+    '20580'	=> 3.0,
+    '25544'	=> -0.5,
+    '37820'	=> 4.0,
+};
+
+is_deeply { @rslt }, $want, 'Got the expected data from the load';
+
+if ( open my $fh, '<', 't/mcnames.mag' ) {
+    clear;
+    succeeds molczan => $fh, 'Loading a Molczan-format file handle';
+    close $fh;
+    succeeds show => 'Retrieve data loaded from file handle';
+    is_deeply { @rslt }, $want,
+	'Got the expected data from the handle';
+} else {
+    note <<"EOD";
+Skipping load of Molczan-format data from file handle. Failed to open
+t/mcnames.mag: $!
+EOD
+}
+
+clear;
+
+succeeds quicksat => 't/quicksat.mag',
+    'Loading a Quicksat-format magnitude file';
+
+succeeds show => 'Retrieving Quicksat-format magnitudes';
+
+$want = {
+    '20580'	=> 2.2,
+    '25544'	=> -1.3,
+    '37820'	=> 3.2,
+};
+
+is_deeply { @rslt }, $want, 'Got the expected data from the load';
+
+if ( open my $fh, '<', 't/quicksat.mag' ) {
+    clear;
+    succeeds quicksat => $fh, 'Loading a Quicksat-format file handle';
+    close $fh;
+    succeeds show => 'Retrieve data loaded from file handle';
+    is_deeply { @rslt }, $want,
+	'Expected data is 0.7 mag dimmer than file contents';
+} else {
+    note <<"EOD";
+Skipping load of Quicksat-format data from file handle. Failed to open
+t/quicksat.mag: $!
+EOD
+}
+
+succeeds adjust => 'Getting the magnitude adjustment';
+
+cmp_ok $rslt[0], '==', 0, 'The default magnitude adjustment is zero';
+
+# Except for the OID (which was changed from 88888) the following OID is
+# test data distributed with Space Track Report Number 3, obtained from
+# the Celestrak web site.
+
+my $tle;
+ok eval {
+    ( $tle ) = Astro::Coord::ECI::TLE->parse( <<'EOD' );
+1 25544U          80275.98708465  .00073094  13844-3  66816-4 0    8
+2 25544  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  105
+EOD
+    1;
+}, 'Parse test TLE';
+
+cmp_ok $tle->get( 'intrinsic_magnitude' ), '==', -1.3,
+    'The intrinsic magnitude got set to -1.3';
+
+succeeds adjust => 0.7, 'Setting the magnitude adjustment to 0.7';
+
+succeeds adjust => 'Retrieving the new magnitude adjustment';
+
+cmp_ok $rslt[0], '==', 0.7, 'The magnitude adjustment is now 0.7';
+
+succeeds show => 25544, 'Retrieving the magnitude table entry for our OID';
+
+cmp_ok { @rslt }->{'25544'}, '==', -1.3,
+    'Magnitude table still has -1.3';
+
+ok eval {
+    ( $tle ) = Astro::Coord::ECI::TLE->parse( <<'EOD' );
+1 25544U          80275.98708465  .00073094  13844-3  66816-4 0    8
+2 25544  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  105
+EOD
+    1;
+}, 'Re-parse the test TLE';
+
+cmp_ok 0 + sprintf( '%.1f', $tle->get( 'intrinsic_magnitude' ) ), '==', -0.6,
+    'The intrinsic magnitude got set to -0.6';
+
+
+done_testing;
+
+sub clear (;$) {
+    my ( $name ) = @_;
+    defined $name
+	or $name = 'Clear magnitude table';
+    eval {
+	Astro::Coord::ECI::TLE->magnitude_table( 'clear' );
+	@rslt = Astro::Coord::ECI::TLE->magnitude_table( 'show' );
+	@rslt
+	    and die "Magnitude table still occupied\n";
+	1;
+    } or do {
+	@_ = "$name failed: $@";
+	goto &fail;
+    };
+    @_ = ( "$name succeeded" );
+    goto \&pass;
+}
+
+sub succeeds (@) {
+    my ( @args ) = @_;
+    my $title = pop @args;
+    eval {
+	@rslt = Astro::Coord::ECI::TLE->magnitude_table( @args );
+	1;
+    } or do {
+	@_ = ( "$title failed: $@" );
+	goto &fail;
+    };
+    @_ = ( "$title succeeded" );
+    goto &pass;
+}
+
+1;
+
+# ex: set textwidth=72 :
@@ -21,7 +21,7 @@ BEGIN {
     eval {
 	use lib qw{ inc };
 	require Astro::Coord::ECI::Test;
-	Astro::Coord::ECI::Test->import( qw{ format_pass } );
+	Astro::Coord::ECI::Test->import( qw{ format_pass magnitude } );
 	1;
     } or do {
 	plan skip_all => 'Can not load Astro::Coord::ECI::Test from inc';
@@ -87,7 +87,10 @@ my ( $tle ) = Astro::Coord::ECI::TLE->parse( <<'EOD' );
 1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    8
 2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  105
 EOD
-$tle->set( geometric => 1 );
+$tle->set(
+    geometric	=> 1,
+    intrinsic_magnitude	=> 3.0,
+);
 
 my @pass;
 
@@ -116,6 +119,14 @@ is format_pass( $pass[0] ), <<'EOD', 'Pass 1';
 1980/10/13 05:46:37   0.0  29.7  1778.5 lit   set
 EOD
 
+note <<'EOD';
+The following magnitude test is really only a regression test, since I
+have no idea what the correct magnitude is.
+EOD
+
+magnitude $tle, $sta, $pass[0]{events}[2]{time},
+    0.6, 'Magnitude at max of pass 1';
+
 is format_pass( $pass[1] ), <<'EOD', 'Pass 2';
 1980/10/14 05:32:49   0.0 204.8  1691.2 lit   rise
 1980/10/14 05:36:32  85.6 111.4   215.0 lit   max
@@ -30,6 +30,9 @@ require_ok 'File::Spec';
 
 }
 
+require_ok 'Astro::SpaceTrack';
+cmp_ok Astro::SpaceTrack->VERSION, '>=', 0.085,
+    'Need at least Astro::SpaceTrack 0.085';
 require_ok 'Date::Manip';
 require_ok 'Test::CPAN::Changes';
 require_ok 'Test::MockTime';
@@ -28,7 +28,7 @@ my %mth;
 my $fail = 0;
 my $test = 0;
 my $ua = LWP::UserAgent->new ();
-my $asof = timegm( 0, 0, 3, 7, 0, 114 );
+my $asof = timegm( 0, 0, 17, 1, 3, 114 );
 
 foreach (["Mike McCants' Iridium status",
 	'http://www.prismnet.com/~mmccants/tles/iridium.html',
@@ -172,7 +172,7 @@ EOD
 24907IRIDIUM 22 [+]
 24925DUMMY MASS 1 [-]
 24926DUMMY MASS 2 [-]
-24944IRIDIUM 29 [+]
+24944IRIDIUM 29 [-]
 24945IRIDIUM 32 [+]
 24946IRIDIUM 33 [-]
 24948IRIDIUM 28 [-]
@@ -0,0 +1,61 @@
+package main;
+
+use 5.008;
+
+use strict;
+use warnings;
+
+use Astro::SpaceTrack 0.084;
+use HTTP::Date;
+use LWP::UserAgent;
+use Test::More 0.88;	# Because of done_testing();
+use Time::Local;
+
+note <<'EOD';
+
+This test checks to see if the canned magnitude data may need updating.
+All it really does is to check file dates on the relevant files.
+
+EOD
+
+is last_modified(
+    'http://celestrak.com/SpaceTrack/query/visual.txt' ),
+'Tue, 07 Jan 2014 03:07:16 GMT',
+'Celestrak visual.txt Last-Modified';
+
+is last_modified( mccants => 'vsnames' ),
+    'Fri, 19 Apr 2013 21:54:32 GMT',
+    'McCants vsnames.mag Last-Modified';
+
+is last_modified( mccants => 'mcnames' ),
+    'Mon, 06 Jan 2014 10:21:29 GMT',
+    'McCants mcnames.mag Last-Modified';
+
+done_testing;
+
+{
+    my $st;
+    my $ua;
+
+    sub last_modified {
+	my ( $src, $catalog ) = @_;
+	my $resp;
+	if ( defined $catalog ) {
+	    $st ||= Astro::SpaceTrack->new();
+	    $resp = $st->$src( $catalog );
+	} else {
+	    $ua ||= LWP::UserAgent->new();
+	    $resp = $ua->head( $src );
+	}
+	$resp->is_success()
+	    or return $resp->status_line();
+	foreach my $val ( $resp->header( 'Last-Modified' ) ) {
+	    return $val;
+	}
+	return 'No Last-Modified header found';
+    }
+}
+
+1;
+
+# ex: set textwidth=72 :
@@ -176,6 +176,7 @@ mish
 mixin
 mma
 mmas
+Molczan
 Moon's
 MoonPhase
 MSWin
@@ -189,6 +190,7 @@ nutation
 obliquity
 Observatoire
 OID
+OIDs
 op
 oped
 orbitaux
@@ -218,6 +220,7 @@ precession
 precisions
 psiprime
 Puerto
+Quicksat
 rad
 radian
 radians