//     ____   ______ __
//    / __ \ / ____// /
//   / /_/ // /    / /
//  / ____// /___ / /___   PixInsight Class Library
// /_/     \____//_____/   PCL 2.4.23
// ----------------------------------------------------------------------------
// pcl/EphemerisFile.h - Released 2022-03-12T18:59:29Z
// ----------------------------------------------------------------------------
// This file is part of the PixInsight Class Library (PCL).
// PCL is a multiplatform C++ framework for development of PixInsight modules.
//
// Copyright (c) 2003-2022 Pleiades Astrophoto S.L. All Rights Reserved.
//
// Redistribution and use in both source and binary forms, with or without
// modification, is permitted provided that the following conditions are met:
//
// 1. All redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. All redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// 3. Neither the names "PixInsight" and "Pleiades Astrophoto", nor the names
//    of their contributors, may be used to endorse or promote products derived
//    from this software without specific prior written permission. For written
//    permission, please contact info@pixinsight.com.
//
// 4. All products derived from this software, in any form whatsoever, must
//    reproduce the following acknowledgment in the end-user documentation
//    and/or other materials provided with the product:
//
//    "This product is based on software from the PixInsight project, developed
//    by Pleiades Astrophoto and its contributors (https://pixinsight.com/)."
//
//    Alternatively, if that is where third-party acknowledgments normally
//    appear, this acknowledgment must be reproduced in the product itself.
//
// THIS SOFTWARE IS PROVIDED BY PLEIADES ASTROPHOTO AND ITS CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PLEIADES ASTROPHOTO OR ITS
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, BUSINESS
// INTERRUPTION; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; AND LOSS OF USE,
// DATA OR PROFITS) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// ----------------------------------------------------------------------------

#ifndef __PCL_EphemerisFile_h
#define __PCL_EphemerisFile_h

/// \file pcl/EphemerisFile.h

#include <pcl/Defs.h>

#include <pcl/Atomic.h>
#include <pcl/AutoLock.h>
#include <pcl/AutoPointer.h>
#include <pcl/ChebyshevFit.h>
#include <pcl/File.h>
#include <pcl/Mutex.h>
#include <pcl/Optional.h>
#include <pcl/Thread.h>
#include <pcl/TimePoint.h>

#ifdef __PCL_BUILDING_PIXINSIGHT_APPLICATION
namespace pi
{
   class JSEphemerisFileObject;
   class JSEphemerisHandleObject;
}
#endif

namespace pcl
{

// ----------------------------------------------------------------------------

class PCL_CLASS XMLElement;

// ----------------------------------------------------------------------------

/*!
 * \struct EphemerisMetadata
 * \brief %Metadata items available in ephemeris files (XEPH format).
 *
 * This structure holds metadata items that can be stored in ephemeris files
 * (current XEPH format version 1.0). For an existing ephemeris file, available
 * metadata are extracted directly from %XML file headers. Currently all items
 * are optional, so all data members of this structure can be empty strings.
 *
 * For generation of new XEPH files, the creationTime and creatorOS members of
 * this structure will be ignored, since the corresponding metadata items will
 * always be defined automatically by the EphemerisFile::Serialize() routine.
 * The specified creatorApplication member, if empty, will be replaced in the
 * same routine with a default value identifying the current PCL version.
 *
 * \ingroup solar_system_ephemerides
 */
struct PCL_CLASS EphemerisMetadata
{
   TimePoint creationTime;       //!< The date this file was created.
   String    creatorOS;          //!< The operating system on which this file was created.
   String    creatorApplication; //!< The software application or program that created this file.
   String    title;              //!< A title that represents or identifies this XEPH file.
   String    briefDescription;   //!< A brief (single-line) description of this XEPH file.
   String    description;        //!< A full description of the data stored in this XEPH file.
   String    organizationName;   //!< The name of the organization responsible for this file.
   String    authors;            //!< The names of one or more persons or groups that have created the data in this file.
   String    copyright;          //!< Copyright information applicable to the data stored in this XEPH file.
};

// ----------------------------------------------------------------------------

/*!
 * \struct EphemerisConstant
 * \brief A numerical constant defined in an ephemeris file (XEPH format).
 *
 * This structure implements a simple name/value pair used to store and
 * manipulate numerical constants defined in ephemeris files (current XEPH
 * format version 1.0). These constants are primarily intended to support JPL
 * DE/LE fundamental ephemerides, where we need access to the set of numerical
 * integration constants defined by JPL.
 *
 * Comparisons between %EphemerisConstant instances are performed on the their
 * names and are case-insensitive.
 *
 * \ingroup solar_system_ephemerides
 */
struct PCL_CLASS EphemerisConstant
{
   IsoString name;   //!< The constant name (case-insensitive).
   double    value;  //!< The constant value.

   /*!
    * Memberwise constructor.
    */
   EphemerisConstant( const IsoString& n = IsoString(), double v = 0 )
      : name( n )
      , value( v )
   {
   }

   /*!
    * Copy constructor.
    */
   EphemerisConstant( const EphemerisConstant& ) = default;

   /*!
    * Move constructor.
    */
   EphemerisConstant( EphemerisConstant&& ) = default;

   /*!
    * Copy assignment operator. Returns a reference to this object.
    */
   EphemerisConstant& operator =( const EphemerisConstant& ) = default;

   /*!
    * Move assignment operator. Returns a reference to this object.
    */
   EphemerisConstant& operator =( EphemerisConstant&& ) = default;

   /*!
    * Equality operator. Returns the result of the case-insensitive equality
    * operator applied to the \a name members.
    */
   bool operator ==( const EphemerisConstant& x ) const
   {
      return name.CompareIC( x.name ) == 0;
   }

   /*!
    * Less-than relational operator. Returns the result of the case-insensitive
    * less-than operator applied to the \a name members of this object and the
    * specified instance \a x.
    */
   bool operator <( const EphemerisConstant& x ) const
   {
      return name.CompareIC( x.name ) < 0;
   }
};

/*!
 * \class pcl::EphemerisConstantList
 * \brief Dynamic list of ephemeris numerical constants.
 *
 * %EphemerisConstantList is used as the internal implementation of numerical
 * constants associated with an %EphemerisFile instance, that is, with an
 * ephemeris file in the XEPH format. %EphemerisConstantList is a template
 * instantiation of Array\<\> for the EphemerisConstant class.
 *
 * \ingroup solar_system_ephemerides
 */
typedef Array<EphemerisConstant> EphemerisConstantList;

// ----------------------------------------------------------------------------

/*!
 * \struct SerializableEphemerisData
 * \brief Chebyshev polynomial expansion coefficients for ephemeris serialization.
 *
 * This structure holds (possibly truncated) Chebyshev polynomial expansions
 * suitable for generation of an ephemeris file in XEPH format with the
 * EphemerisFile::Serialize() static member function.
 *
 * In an ephemeris file, the complete time span covered by the file is usually
 * subdivided into many small chunks or subspans, each of them with a
 * relatively short polynomial expansion. The duration of each subspan is
 * defined in a way such that the movement of the object for which positions
 * are being calculated within the time subspan is sufficiently smooth to be
 * fitted by truncated Chebyshev polynomials with relatively few coefficients
 * (typically in the range of 15 to 30 coefficients) to achieve a prescribed
 * accuracy.
 *
 * \sa EphemerisFile::Serialize()
 * \ingroup solar_system_ephemerides
 */
struct PCL_CLASS SerializableEphemerisData
{
   /*!
    * Starting point of the time span covered by this Chebyshev polynomial
    * expansion.
    */
   TimePoint    startTime;

   /*!
    * Chebyshev polynomial expansion. If the coefficient series has been
    * truncated, only the truncated series will be stored in the XEPH file,
    * that is, truncated coefficients will be ignored.
    *
    * \sa ChebyshevFit::Truncate()
    */
   ChebyshevFit expansion;

   /*!
    * Memberwise constructor.
    */
   SerializableEphemerisData( TimePoint t, const ChebyshevFit& T )
      : startTime( t )
      , expansion( T )
   {
   }

   /*!
    * Copy constructor.
    */
   SerializableEphemerisData( const SerializableEphemerisData& ) = default;

   /*!
    * Move constructor.
    */
   SerializableEphemerisData( SerializableEphemerisData&& ) = default;

   /*!
    * Copy assignment operator. Returns a reference to this object.
    */
   SerializableEphemerisData& operator =( const SerializableEphemerisData& ) = default;

   /*!
    * Move assignment operator. Returns a reference to this object.
    */
   SerializableEphemerisData& operator =( SerializableEphemerisData&& ) = default;
};

/*!
 * \class pcl::SerializableEphemerisDataList
 * \brief Dynamic list of Chebyshev polynomial expansions for ephemeris serialization.
 *
 * %SerializableEphemerisDataList is a dynamic list of
 * SerializableEphemerisData instances to cover the entire time span of an
 * ephemeris file being serialized. Each instance in the list must cover a time
 * subspan in ascending time order without gaps. The value of the \a startTime
 * member of the first instance in the list must be equal to the lower bound of
 * the time span covered by the file, and the \a startTime member of each
 * subsequent instance in the list is equal to the upper bound of the subspan
 * covered by the previous instance. These conditions are enforced by the
 * EphemerisFile::Serialize() static member function, which is the standard
 * resource for generation of ephemeris files in XEPH format.
 *
 * \ingroup solar_system_ephemerides
 */
typedef Array<SerializableEphemerisData> SerializableEphemerisDataList;

// ----------------------------------------------------------------------------

/*!
 * \struct SerializableEphemerisObjectData
 * \brief A set of Chebyshev polynomial expansions and associated ancillary
 * data for ephemeris serialization.
 *
 * This structure associates a unique pair of object and origin identifiers, an
 * optional object name, an optional descriptive text, and one or two ordered
 * sequences of Chebyshev polynomial expansions for calculation of ephemerides
 * within a prescribed time span. These data are suitable for generation of an
 * ephemeris file in XEPH format with the EphemerisFile::Serialize() static
 * member function.
 *
 * The first sequence of Chebyshev polynomial expansions is mandatory for
 * calculation of ephemeris function values (typically, the components of a
 * position vector as a function of time). An additional sequence of expansions
 * for the first derivative (typically, the components of a velocity vector) is
 * optional. Derivative expansions should be used when the velocity of an
 * object is known very accurately upon ephemeris generation, and this high
 * accuracy must be preserved.
 *
 * All ephemeris data stored in XEPH files must be referred to the
 * International Celestial Reference System (ICRS/J2000.0).
 *
 * \sa EphemerisFile::Serialize()
 * \ingroup solar_system_ephemerides
 */
struct PCL_CLASS SerializableEphemerisObjectData
{
   /*!
    * Unique identifier of the object or item for which ephemeris data have
    * been generated. This should be a short string to uniquely identify the
    * object in the context of the ephemeris file being serialized. For
    * example, standard XEPH files serializing JPL DE/LE ephemerides use a
    * two/three letter convention for object identifiers: 'Me', 'Ve', 'EMB',
    * 'Ea', 'Ma', etc, respectively for Mercury, Venus, Earth-Moon barycenter,
    * Earth, Mars, etc. Object identifiers are considered as case-sensitive in
    * XEPH files.
    */
   IsoString                     objectId;

   /*!
    * Identifier of the origin of coordinates (case-sensitive). This can be the
    * unique identifier of a solar system object, or the identifier of an
    * arbitrary object or location, which will be treated as
    * implementation-specific, that is, specific to the data being serialized
    * and to the procedures employed to generate them.
    *
    * For example, if the ephemeris data being serialized are geocentric, this
    * member should be "Ea", which is the unique identifier of the Earth's
    * center in standard fundamental ephemerides files. Similarly, if the
    * ephemerides are heliocentric, this member should be "Sn". If the
    * ephemeris data are with respect to the solar system barycenter, this
    * member must be "SSB", also for coherence with fundamental ephemerides.
    */
   IsoString                     originId;

   /*!
    * Optional Name of the object or item for which ephemeris data have been
    * generated. This can be an arbitrary string, such as 'Saturn', 'Iris', or
    * 'C/1996 B2'. However, bear in mind that ephemeris data can be accessed
    * both by object identifiers and object names, so there should be no
    * duplicate names. Object names are considered as case-insensitive in XEPH
    * files, so all of 'Jupiter', 'jupiter' and 'JUPITER' are equivalent.
    */
   String                        objectName;

   /*!
    * Optional information about the object and/or the ephemeris data being
    * serialized.
    */
   String                        description;

   /*!
    * Two dynamic lists of Chebyshev polynomial expansions. The first list is
    * mandatory for approximation of function values (typically, the components
    * of a position vector). The second list is optional for approximation of
    * first derivatives (typically, the components of a velocity vector). Both
    * sequences must cover the entire ephemeris time span with arbitrary
    * subspans in ascending time order without any gaps.
    */
   SerializableEphemerisDataList data[ 2 ];

   /*
    * Absolute magnitude. H is the visual magnitude of the object as seen at 1
    * au of the Earth, 1 au from the Sun, and with a phase angle of 0 degrees.
    *
    * <b>References</b>
    *
    * E. Bowell et al., <em>Asteroids II</em>, R. P. Binzel et al. (eds.), The
    * University of Arizona Press, Tucson, 1989, pp. 549-554.
    *
    * Urban, Sean E., Kenneth Seidelmann, P., ed. (2013), <em>The Explanatory
    * Supplement to the Astronomical Almanac</em> 3rd Edition, Section 10.4.3.
    */
   Optional<double>              H;

   /*
    * Slope parameter. See the H data member for references.
    */
   Optional<double>              G;

   /*
    * Color index B-V in magnitudes.
    */
   Optional<double>              B_V;

   /*
    * Diameter of the object in km.
    */
   Optional<double>              D;

   /*!
    * Memberwise constructor.
    */
   SerializableEphemerisObjectData( const IsoString& id,
                                    const IsoString& origin,
                                    const String& name = String(),
                                    const String& desc = String() )
      : objectId( id.Trimmed() )
      , originId( origin.Trimmed() )
      , objectName( name.Trimmed() )
      , description( desc )
   {
   }

   /*!
    * Copy constructor.
    */
   SerializableEphemerisObjectData( const SerializableEphemerisObjectData& ) = default;

   /*!
    * Move constructor.
    */
   SerializableEphemerisObjectData( SerializableEphemerisObjectData&& ) = default;

   /*!
    * Copy assignment operator. Returns a reference to this object.
    */
   SerializableEphemerisObjectData& operator =( const SerializableEphemerisObjectData& ) = default;

   /*!
    * Move assignment operator. Returns a reference to this object.
    */
   SerializableEphemerisObjectData& operator =( SerializableEphemerisObjectData&& ) = default;
};

/*!
 * \class pcl::SerializableEphemerisObjectDataList
 * \brief Dynamic list of per-object data for ephemeris serialization.
 * \ingroup solar_system_ephemerides
 */
typedef Array<SerializableEphemerisObjectData> SerializableEphemerisObjectDataList;

// ----------------------------------------------------------------------------

/*!
 * \struct EphemerisObject
 * \brief Identifiers and descriptive data of an object available in an
 * ephemeris file.
 *
 * This structure associates the unique identifier, origin identifier, name and
 * description of an object or item available for ephemeris calculations. All
 * of the data members in this structure are specific to a particular ephemeris
 * file. A dynamic list of %EphemerisObject instances (EphemerisObjectList) is
 * returned by the EphemerisFile::Objects() member function.
 *
 * \sa EphemerisFile::Objects()
 * \ingroup solar_system_ephemerides
 */
struct PCL_CLASS EphemerisObject
{
   /*!
    * Unique identifier of an object or item for which ephemeris data are
    * available. This is a short string that uniquely identifies the object in
    * the context of an ephemeris file.
    *
    * For example, standard XEPH files serializing JPL DE/LE ephemerides use a
    * two/three letter convention for object identifiers: 'Me', 'Ve', 'EMB',
    * 'Ea', 'Ma', etc, respectively for Mercury, Venus, Earth-Moon barycenter,
    * Earth, Mars, etc. Object identifiers are considered as case-sensitive in
    * XEPH files.
    */
   IsoString                     objectId;

   /*!
    * Identifier of the origin of coordinates (case-sensitive). This can be the
    * unique identifier of a solar system object, or the identifier of an
    * arbitrary object or location, specific to a particular ephemeris file.
    *
    * For example, if the object provides geocentric coordinates, this member
    * should be "Ea", which is the unique identifier of the Earth's center in
    * standard fundamental ephemerides files. Similarly, if the ephemerides are
    * heliocentric, this member should be "Sn". If the ephemeris data are with
    * respect to the solar system barycenter, this member must be "SSB", also
    * for coherence with fundamental ephemerides.
    */
   IsoString                     originId;

   /*!
    * Optional Name of the object or item for which ephemeris data are
    * available. This can be an arbitrary string, such as 'Saturn', 'Iris', or
    * 'C/1996 B2'. Object names are considered as case-insensitive in XEPH
    * files, so all of 'Jupiter', 'jupiter' and 'JUPITER' are equivalent.
    */
   String                        objectName;

   /*!
    * Optional information about the object and/or the ephemeris data.
    */
   String                        objectDescription;

   /*!
    * Absolute magnitude. H is the visual magnitude of the object as seen at 1
    * au of the Earth, 1 au from the Sun, and with a phase angle of 0 degrees.
    *
    * <b>References</b>
    *
    * E. Bowell et al., <em>Asteroids II</em>, R. P. Binzel et al. (eds.), The
    * University of Arizona Press, Tucson, 1989, pp. 549-554.
    *
    * Urban, Sean E., Kenneth Seidelmann, P., ed. (2013), <em>The Explanatory
    * Supplement to the Astronomical Almanac</em> 3rd Edition, Section 10.4.3.
    */
   Optional<double>              H;

   /*!
    * Slope parameter. See the H data member for references.
    */
   Optional<double>              G;

   /*!
    * Color index B-V in magnitudes.
    */
   Optional<double>              B_V;

   /*!
    * Diameter of the object in km.
    */
   Optional<double>              D;

   /*!
    * Memberwise constructor.
    */
   EphemerisObject( const IsoString& objectId_,
                    const IsoString& originId_,
                    const String&    objectName_ = String(),
                    const String&    objectDescription_ = String(),
                    Optional<double> H_ = Optional<double>(),
                    Optional<double> G_ = Optional<double>(),
                    Optional<double> B_V_ = Optional<double>(),
                    Optional<double> D_ = Optional<double>() )
      : objectId( objectId_ )
      , originId( originId_ )
      , objectName( objectName_ )
      , objectDescription( objectDescription_ )
      , H( H_ )
      , G( G_ )
      , B_V( B_V_ )
      , D( D_ )
   {
   }

   /*!
    * Copy constructor.
    */
   EphemerisObject( const EphemerisObject& ) = default;

   /*!
    * Move constructor.
    */
   EphemerisObject( EphemerisObject&& ) = default;

   /*!
    * Copy assignment operator. Returns a reference to this object.
    */
   EphemerisObject& operator =( const EphemerisObject& ) = default;

   /*!
    * Move assignment operator. Returns a reference to this object.
    */
   EphemerisObject& operator =( EphemerisObject&& ) = default;
};

/*!
 * \class pcl::EphemerisObjectList
 * \brief Dynamic list of object identifiers and descriptions.
 * \ingroup solar_system_ephemerides
 */
typedef Array<EphemerisObject> EphemerisObjectList;

// ----------------------------------------------------------------------------

/*!
 * \class EphemerisFile
 * \brief Solar system ephemerides from XEPH files.
 *
 * This class implements ephemerides of solar system bodies computed from data
 * stored in XEPH (Extensible Ephemeris Data format) files. It also implements
 * serialization of ephemeris data in the XEPH file format.
 *
 * On the PixInsight/PCL platform, the XEPH file format allows for efficient
 * ephemeris calculations through Chebyshev polynomial expansions stored as raw
 * binary data. An XEPH file stores multiple arrays of Chebyshev coefficient
 * series accessible by means of fast indexed file search algorithms and
 * structures, along with auxiliary data and metadata required for ephemeris
 * calculations.
 *
 * Calculation of state vectors (such as position and velocity vectors) for
 * specific objects is performed through a dedicated client subclass, namely
 * EphemerisFile::Handle. This class implements transparent file seek and read
 * operations, as well as fast, lock-free multithreaded evaluation of Chebyshev
 * polynomials.
 *
 * XEPH ephemeris files allow for calculation of rectangular coordinates
 * referred to the axes of the International Celestial Reference System
 * (ICRS/J2000.0). Positions are given in au and velocities in au/day for all
 * solar system objects, except planetocentric coordinates of natural
 * satellites, including the geocentric Moon, for which positions and
 * velocities are given in kilometers and km/day, respectively. Angles
 * (nutations and librations) are given in radians, and time differences (such
 * as TT-TDB) in seconds.
 *
 * An XEPH file storing up-to-date JPL DE/LE ephemeris data is part of all
 * standard PixInsight distributions since 1.8.5 versions released Fall 2018.
 * As of writing this documentation, the standard XEPH file provides the
 * complete JPL DE440/LE440 ephemerides. See the
 * EphemerisFile::FundamentalEphemerides() static member function for detailed
 * information.
 *
 * \ingroup solar_system_ephemerides
 */
class PCL_CLASS EphemerisFile
{
public:

   /*!
    * Default constructor.
    *
    * Constructs an invalid instance that cannot be used until initialized by
    * calling the Open() member function.
    */
   EphemerisFile() = default;

   /*!
    * Constructs an &EphemerisFile instance initialized from the specified
    * ephemeris data file in XEPH format.
    *
    * In the event of errors or invalid data, this constructor will throw the
    * appropriate Error exception.
    */
   EphemerisFile( const String& filePath )
   {
      Open( filePath );
   }

   /*!
    * Move constructor.
    */
   EphemerisFile( EphemerisFile&& x )
      : m_file( std::move( x.m_file ) )
      , m_startTime( x.m_startTime )
      , m_endTime( x.m_endTime )
      , m_constants( std::move( x.m_constants ) )
      , m_index( std::move( x.m_index ) )
   {
   }

   /*!
    * Move assignment operator. Returns a reference to this object.
    */
   EphemerisFile& operator =( EphemerisFile&& x )
   {
      m_file = std::move( x.m_file );
      m_startTime = x.m_startTime;
      m_endTime = x.m_endTime;
      m_constants = std::move( x.m_constants );
      m_index = std::move( x.m_index );
      return *this;
   }

   /*!
    * Deleted copy constructor. %EphemerisFile instances are unique, hence
    * cannot be copied.
    */
   EphemerisFile( const EphemerisFile& ) = delete;

   /*!
    * Deleted copy assignment operator. %EphemerisFile instances are unique,
    * hence cannot be copied.
    */
   EphemerisFile& operator =( const EphemerisFile& ) = delete;

   /*!
    * Virtual destructor.
    */
   virtual ~EphemerisFile() noexcept( false );

   /*!
    * Initializes this object to give access to the specified ephemeris data
    * file in XEPH format.
    *
    * This member function opens an existing file at the specified \a filePath,
    * loads and parses its XML header, and loads the file indexes ready for
    * fast access to ephemeris data. The file will remain open until this
    * object is destroyed, or until a new call to this function is made.
    *
    * If a previous file was already opened by this instance, it will be closed
    * and all associated control and file indexing structures will be destroyed
    * and deallocated, before accessing the new file.
    *
    * \warning If this object has active ephemeris calculation handles, no
    * action will be taken and an Error exception will be thrown. See
    * EphemerisFile::Handle and NumberOfHandles().
    */
   void Open( const String& filePath );

   /*!
    * Closes the ephemeris file represented by this object and resets all
    * internal structures to a default, uninitialized state.
    *
    * If a previous file was already opened by this instance, it will be closed
    * and all associated control and file indexing structures will be destroyed
    * and deallocated. If no file is currently open, calling this member has no
    * effect.
    *
    * \warning If this object has active ephemeris calculation handles, no
    * action will be taken and an Error exception will be thrown. See
    * EphemerisFile::Handle and NumberOfHandles().
    */
   void Close();

   /*!
    * Returns true iff this object has an open ephemeris file and is ready for
    * ephemeris data retrieval.
    */
   bool IsOpen() const
   {
      return m_file.IsOpen();
   }

   /*!
    * Returns the path of the ephemeris file represented by this object.
    * Returned file paths are always absolute, full file paths.
    */
   const String& FilePath() const
   {
      return m_file.FilePath();
   }

   /*!
    * Starting point of the time span covered by this ephemeris, or the
    * earliest time point for which ephemerides can be calculated using this
    * object.
    */
   TimePoint StartTime() const
   {
      return m_startTime;
   }

   /*!
    * Ending point of the time span covered by this ephemeris, or the latest
    * time point for which ephemerides can be calculated using this object.
    */
   TimePoint EndTime() const
   {
      return m_endTime;
   }

   /*!
    * Returns a reference to the dynamic list of constants stored in the
    * ephemeris data file represented by this object. These constants are
    * name/value pairs, where keys are 8-bit strings and values are double
    * precision floating point numbers.
    *
    * Typically, for XEPH files that store fundamental JPL DE/LE ephemerides
    * this member function returns the list of constants used by the
    * corresponding DE/LE numerical integration.
    *
    * The returned list is sorted by constant name in ascending order.
    */
   const EphemerisConstantList& Constants() const
   {
      return m_constants;
   }

   /*!
    * Returns true iff this instance knows a numerical constant with the
    * specified \a name.
    */
   bool IsConstantAvailable( const IsoString& name ) const
   {
      return BinarySearch( m_constants.Begin(), m_constants.End(),
                           EphemerisConstant( name ) ) != m_constants.End();
   }

   /*!
    * Returns the value of an ephemeris constant given by its \a name.
    *
    * If no constant is available with the specified \a name
    * (case-insensitive) in the XEPH file represented by this object, this
    * function throws an Error exception.
    */
   double ConstantValue( const IsoString& name ) const
   {
      EphemerisConstantList::const_iterator i =
         BinarySearch( m_constants.Begin(), m_constants.End(), EphemerisConstant( name ) );
      if ( i == m_constants.End() )
         throw Error( "Undefined ephemeris constant '" + name + '\'' );
      return i->value;
   }

   /*!
    * Returns a dynamic list of EphemerisObject instances describing all of the
    * objects available in this file for ephemeris calculations.
    *
    * The returned list is sorted by object and origin identifiers (in that
    * order of precedence) in ascending order.
    */
   EphemerisObjectList Objects() const
   {
      EphemerisObjectList objects;
      for ( const Index& ix : m_index )
         objects << EphemerisObject( ix.objectId, ix.originId,
                                     ix.objectName, ix.objectDescription,
                                     ix.H, ix.G, ix.B_V, ix.D );
      return objects;
   }

   /*!
    * Returns true iff this instance contains ephemeris data for the specified
    * \a object, given by its identifier or name, with respect to the specified
    * \a origin.
    *
    * The specified \a object string can be either an object identifier
    * (case-sensitive), or an object name (case-insensitive) encoded as UTF-8.
    * For example, all of 'Ju', 'Jupiter', 'jupiter' and 'JUPITER' refer to
    * Jupiter in a standard XEPH file storing fundamental JPL DE/LE
    * ephemerides. In this example, 'Ju' is a case-sensitive object identifier,
    * thus 'ju' and 'JU' are not valid and this function would return false for
    * both of them.
    *
    * \a origin is the identifier of an origin of coordinates. If an empty
    * string is specified (which is the default parameter value), this function
    * will return true if the file contains any ephemeris data for the
    * specified \a object, irrespective of the origin. Otherwise an exact match
    * of the \a origin identifier will be required.
    *
    * See the FundamentalEphemerides() static member function for information
    * about the objects supported by standard XEPH files storing fundamental
    * ephemerides.
    */
   bool IsObjectAvailable( const IsoString& object, const IsoString& origin = IsoString() ) const
   {
      Array<Index>::const_iterator i = BinarySearch( m_index.Begin(), m_index.End(), Index( object, origin ) );
      if ( i != m_index.End() )
         return true;
      String name = object.UTF8ToUTF16();
      for ( const Index& index : m_index )
         if ( index.objectName.CompareIC( name ) == 0 )
            if ( origin.IsEmpty() || index.originId == origin )
               return true;
      return false;
   }

   /*!
    * Returns the name of the specified \a object, with respect to the
    * specified \a origin.
    *
    * Both \a object and \a origin must be object identifiers. If \a origin is
    * an empty string (which is the default parameter value), this function
    * will return the name of the first object found with the specified
    * identifier, irrespective of the origin. Otherwise an exact match of the
    * \a origin identifier will be required.
    *
    * If no object with the required conditions is available in this ephemeris
    * file, this function returns an empty string.
    */
   String ObjectName( const IsoString& object, const IsoString& origin = IsoString() ) const
   {
      Array<Index>::const_iterator i = BinarySearch( m_index.Begin(), m_index.End(), Index( object, origin ) );
      if ( i != m_index.End() )
         return i->objectName;
      return String();
   }

   /*!
    * Returns a reference to the (immutable) metadata items available in the
    * ephemeris data file loaded by this object.
    */
   const EphemerisMetadata& Metadata() const
   {
      return m_metadata;
   }

   /*!
    * Returns the number of handles currently active for this ephemeris file.
    *
    * If an %EphemerisFile object has active handles, also known as \e child
    * \e handles, destroying it will most likely lead to a crash, since any
    * activity performed by a child handle will make reference to a nonexistent
    * parent object.
    *
    * This function is thread-safe.
    *
    * \sa pcl::EphemerisFile::Handle
    */
   int NumberOfHandles() const
   {
      return m_handleCount.Load();
   }

   /*!
    * Generates a file to store solar system ephemeris data in XEPH format.
    *
    * \param filePath      Path to the file that will be generated in the local
    *                      filesystem. The file name should carry the '.xeph'
    *                      suffix.
    *
    * \param startTime     Lower bound of the entire time span for which the
    *                      ephemeris data being serialized is valid.
    *
    * \param endTime       Upper bound of the entire time span for which the
    *                      ephemeris data being serialized is valid.
    *
    * \param data          Reference to an array of per-object ephemeris data,
    *                      including Chebyshev polynomial expansions for the
    *                      ephemeris function (such as position) and,
    *                      optionally, for its first derivative (such as
    *                      velocity), as well as object identifiers and object
    *                      names.
    *
    * \param metadata      Reference to an EphemerisMetadata structure with
    *                      optional metadata information that will be included
    *                      in the generated XEPH file.
    *
    * \param constants     Reference to an array of name/value pairs used to
    *                      represent a set of numerical constants relevant to
    *                      the ephemerides being serialized If an empty array
    *                      is specified (as the default parameter value), no
    *                      numerical constant will be included in the generated
    *                      XEPH file.
    *
    * The entire time span covered by an ephemeris file, from \a startTime to
    * \a endTime, is usually subdivided into many small chunks or subspans,
    * each of them with a relatively short polynomial expansion. The duration
    * of each subspan is defined in a way such that the motion of the object
    * for which positions are being calculated is sufficiently smooth to be
    * fitted within the time subspan by truncated Chebyshev polynomials with
    * relatively few coefficients (typically in the range of 15 to 30
    * coefficients) to achieve a prescribed accuracy. The faster and more
    * perturbed the object's motion is, the more and shorter subspans are
    * necessary to fit an accurate representation of the object's orbit.
    *
    * In the event of invalid, incongruent or malformed data, or if an I/O
    * error occurs, this function will throw an Error exception.
    *
    * \warning If a file already exists at the specified path, its previous
    * contents will be lost after calling this function.
    */
   static void Serialize( const String& filePath,
                          TimePoint startTime, TimePoint endTime,
                          const SerializableEphemerisObjectDataList& data,
                          const EphemerisMetadata& metadata = EphemerisMetadata(),
                          const EphemerisConstantList& constants = EphemerisConstantList() );

   /*!
    * Returns a reference to the global fundamental ephemerides file currently
    * defined by the running PixInsight platform.
    *
    * Under normal running conditions, the returned object provides ephemeris
    * data for at least the following objects:
    *
    * <table border="1" cellpadding="4" cellspacing="0">
    * <tr><td>Identifier</td><td>Name</td></tr>
    * <tr><td>Me</td><td>Mercury</td></tr>
    * <tr><td>Ve</td><td>Venus</td></tr>
    * <tr><td>EMB</td><td>Earth-Moon barycenter</td></tr>
    * <tr><td>Ma</td><td>Mars' barycenter</td></tr>
    * <tr><td>Ju</td><td>Jupiter's barycenter</td></tr>
    * <tr><td>Sa</td><td>Saturn's barycenter</td></tr>
    * <tr><td>Ur</td><td>Uranus' barycenter</td></tr>
    * <tr><td>Ne</td><td>Neptune's barycenter</td></tr>
    * <tr><td>Pl</td><td>Pluto's barycenter</td></tr>
    * <tr><td>Mn</td><td>Moon's geometric center with respect to Earth's center.</td></tr>
    * <tr><td>Sn</td><td>Sun's geometric center</td></tr>
    * <tr><td>Ea</td><td>Earth's geometric center</td></tr>
    * </table>
    *
    * With the only exception of the Moon ("Mn" identifier), ephemeris data for
    * all of the objects above are provided relative to the solar system
    * barycenter ("SSB" identifier).
    *
    * Additional items may also be available, depending on specific file
    * versions and compilations:
    *
    * <table border="1" cellpadding="4" cellspacing="0">
    * <tr><td>Identifier</td><td>Name</td></tr>
    * <tr><td>Lbr</td><td>Lunar librations (Euler angles) in radians</td></tr>
    * <tr><td>Nut</td><td>Nutation angles in radians</td></tr>
    * <tr><td>TT_TDB</td><td>TT-TDB difference at the geocenter in seconds.</td></tr>
    * </table>
    *
    * As of writing this documentation, the standard fundamental ephemeris file
    * provides the complete JPL's DE440/LE440 ephemerides, but nutations,
    * librations and time differences are not included.
    *
    * The fundamental ephemeris file can be overridden by the caller module.
    * See the OverrideFundamentalEphemerides() member function for more
    * information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& FundamentalEphemerides();

   /*!
    * Returns a reference to the global short-term fundamental ephemerides file
    * currently defined by the running PixInsight platform.
    *
    * See the FundamentalEphemerides() static member function for information
    * on fundamental ephemerides and their status in current versions of
    * PixInsight.
    *
    * Under normal running conditions, the returned object should be a
    * shortened version (that is, covering a shorter time span) of the standard
    * fundamental ephemerides file. As of writing this documentation, the
    * standard short-term fundamental ephemeris file provides DE440/LE440
    * ephemerides for the period from 1850 January 1.0 to 2150 December 32.0.
    *
    * The short-term fundamental ephemeris file can be overridden by the caller
    * module. See the OverrideShortTermFundamentalEphemerides() member function
    * for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& ShortTermFundamentalEphemerides();

   /*!
    * Returns a reference to the global asteroid ephemerides file currently
    * defined by the running PixInsight platform.
    *
    * Under normal running conditions, the returned object provides ephemeris
    * data for a set of asteroids with relevant masses. In a standard asteroid
    * ephemeris file, object identifiers are asteroid numbers and object names
    * are asteroid designations; for example:
    *
    * <table border="1" cellpadding="4" cellspacing="0">
    * <tr><td>Identifier</td><td>Name</td></tr>
    * <tr><td>1</td><td>Ceres</td></tr>
    * <tr><td>2</td><td>Pallas</td></tr>
    * <tr><td>3</td><td>Juno</td></tr>
    * <tr><td>4</td><td>Vesta</td></tr>
    * <tr><td>5</td><td>Astraea</td></tr>
    * <tr><td>...</td><td>...</td></tr>
    * <tr><td>702</td><td>Alauda</td></tr>
    * <tr><td>703</td><td>Noemi</td></tr>
    * <tr><td>704</td><td>Interamnia</td></tr>
    * <tr><td>...</td><td>...</td></tr>
    * </table>
    *
    * Asteroid ephemeris data are provided relative to the solar system
    * barycenter ("SSB" identifier), with position and velocity coordinates
    * coherent with global fundamental ephemerides.
    *
    * As of writing this documentation, the standard asteroid ephemeris file
    * provides the complete set of 343 asteroids used for the numerical
    * integration of DE430 ephemerides, with barycentric coordinates coherent
    * with DE440.
    *
    * The asteroid ephemeris file can be overridden by the caller module. See
    * the OverrideAsteroidEphemerides() member function for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& AsteroidEphemerides();

   /*!
    * Returns a reference to the global short-term asteroid ephemerides file
    * currently defined by the running PixInsight platform.
    *
    * See the AsteroidEphemerides() static member function for information
    * on asteroid ephemerides and their status in current versions of
    * PixInsight.
    *
    * Under normal running conditions, the returned object should be a
    * shortened version (that is, covering a shorter time span) of the standard
    * asteroid ephemerides file. As of writing this documentation, the
    * standard short-term asteroid ephemeris file covers the period from 1950
    * January 1.0 to 2100 January 32.0.
    *
    * The short-term asteroid ephemeris file can be overridden by the caller
    * module. See the OverrideShortTermAsteroidEphemerides() member function
    * for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& ShortTermAsteroidEphemerides();

   /*!
    * Returns a reference to the global Kuiper belt objects (KBOs) ephemerides
    * file currently defined by the running PixInsight platform.
    *
    * As of writing this documentation (March 2021), the default KBO
    * ephemerides file includes the set of 30 most massive known
    * trans-Neptunian objects used in JPL's DE440 numerical integration:
    *
    * <table border="1" cellpadding="4" cellspacing="0">
    * <tr><td>Identifier</td><td>Name</td></tr>
    * <tr><td>19521</td><td>Chaos</td></tr>
    * <tr><td>20000</td><td>Varuna</td></tr>
    * <tr><td>28978</td><td>Ixion</td></tr>
    * <tr><td>42301</td><td>2001 UR163</td></tr>
    * <tr><td>50000</td><td>Quaoar</td></tr>
    * <tr><td>55565</td><td>2002 AW197</td></tr>
    * <tr><td>55637</td><td>2002 UX25</td></tr>
    * <tr><td>84522</td><td>2002 TC302</td></tr>
    * <tr><td>90377</td><td>Sedna</td></tr>
    * <tr><td>90482</td><td>Orcus</td></tr>
    * <tr><td>90568</td><td>2004 GV9</td></tr>
    * <tr><td>120347</td><td>Salacia</td></tr>
    * <tr><td>136108</td><td>Haumea</td></tr>
    * <tr><td>136199</td><td>Eris</td></tr>
    * <tr><td>136472</td><td>Makemake</td></tr>
    * <tr><td>145452</td><td>2005 RN43</td></tr>
    * <tr><td>174567</td><td>Varda</td></tr>
    * <tr><td>208996</td><td>2003 AZ84</td></tr>
    * <tr><td>225088</td><td>Gonggong</td></tr>
    * <tr><td>230965</td><td>2004 XA192</td></tr>
    * <tr><td>278361</td><td>2007 JJ43</td></tr>
    * <tr><td>307261</td><td>2002 MS4</td></tr>
    * <tr><td>455502</td><td>2003 UZ413</td></tr>
    * <tr><td>523639</td><td>2010 RE64</td></tr>
    * <tr><td>528381</td><td>2008 ST291</td></tr>
    * <tr><td></td><td>2004 XR190</td></tr>
    * <tr><td></td><td>2006 QH181</td></tr>
    * <tr><td></td><td>2010 FX86</td></tr>
    * <tr><td></td><td>2010 KZ39</td></tr>
    * <tr><td></td><td>2010 RF43</td></tr>
    * </table>
    *
    * KBO ephemeris data are provided relative to the solar system barycenter
    * ("SSB" identifier), with position and velocity coordinates coherent with
    * global fundamental ephemerides. These ephemerides have been generated by
    * numerical integration with starting state vectors provided by official
    * NASA/JPL asteroid databases.
    *
    * The KBO ephemeris file can be overridden by the caller module. See the
    * OverrideKBOEphemerides() member function for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& KBOEphemerides();

   /*!
    * Returns a reference to the global short-term Kuiper belt objects (KBOs)
    * ephemerides file currently defined by the running PixInsight platform.
    *
    * See the KBOEphemerides() static member function for information on
    * asteroid ephemerides and their status in current versions of PixInsight.
    *
    * Under normal running conditions, the returned object should be a
    * shortened version (that is, covering a shorter time span) of the standard
    * KBO ephemerides file. As of writing this documentation, the standard
    * short-term asteroid ephemeris file covers the period from 1950 January
    * 1.0 to 2100 January 32.0.
    *
    * The short-term KBO ephemeris file can be overridden by the caller module.
    * See the OverrideShortTermKBOEphemerides() member function for more
    * information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& ShortTermKBOEphemerides();

   /*!
    * Returns a reference to the global nutation model ephemeris file currently
    * defined by the running PixInsight platform.
    *
    * Under normal running conditions, the returned object provides Chebyshev
    * polynomial expansions for the current IAU nutation model. As of writing
    * this documentation, the standard nutation model file provides the IAU
    * 2006/2000A_R nutation model (MHB2000 luni-solar and planetary nutation
    * with adjustments to match the IAU 2006 precession).
    *
    * The returned object should provide at least one object with the "IAUNut"
    * identifier, which can be used to approximate the implemented nutation
    * theory with a child EphemerisFile::Handle object.
    *
    * The nutation model ephemeris file can be overridden by the caller module.
    * See the OverrideNutationModel() member function for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& NutationModel();

   /*!
    * Returns a reference to the global short-term nutation model ephemeris
    * file currently defined by the running PixInsight platform.
    *
    * See the NutationModel() static member function for information on
    * nutation model ephemerides and their status in current versions of
    * PixInsight.
    *
    * Under normal running conditions, the returned object should be a
    * shortened version (that is, covering a shorter time span) of the standard
    * nutation model ephemerides file. As of writing this documentation, the
    * standard short-term nutation model file provides the IAU 2006/2000A_R
    * nutation model for the period from 1850 January 1.0 to 2150 December
    * 32.0.
    *
    * The returned object should provide at least one object with the "IAUNut"
    * identifier, which can be used to approximate the implemented nutation
    * theory with a child EphemerisFile::Handle object.
    *
    * The short-term nutation model ephemeris file can be overridden by the
    * caller module. See the OverrideShortTermNutationModel() member function
    * for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    */
   static const EphemerisFile& ShortTermNutationModel();

   /*!
    * Returns the path to the global database file of observed Delta T values.
    *
    * Delta T is the difference TT-UT1 in seconds. In current versions of
    * PixInsight the Delta T database is a plain text file generated with values
    * taken from the following online references:
    *
    * For the period 1657-1973.0:\n
    * http://maia.usno.navy.mil/ser7/historic_deltat.data
    *
    * For the period 1973 Feb 1 to the beginning of the current year:\n
    * http://maia.usno.navy.mil/ser7/deltat.data
    *
    * For predicted Delta T values until 2025 approximately (as of writing this
    * documentation, October 2018):\n
    * http://maia.usno.navy.mil/ser7/deltat.preds
    *
    * Delta T data files are expected to follow a simple format where each text
    * line provides a TT/DeltaT pair. The exact format is described at the top
    * of the corresponding file included in the current PixInsight
    * distribution.
    *
    * Outside of the period from 1657 to the current year, the current
    * PixInsight/PCL implementation uses polynomial expressions taken from
    * <em>Five Millennium Canon of Solar Eclipses</em>, by Fred Espenak and
    * Jean Meeus (NASA/TP–2006–214141, Revision 1.0, 2007).
    *
    * The Delta T database file can be overridden by the caller module. See the
    * OverrideDeltaTDataFilePath() member function for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    *
    * \sa TimePoint::DeltaT()
    */
   static String DeltaTDataFilePath();

   /*!
    * Returns the path to the global database file of Delta AT values.
    *
    * Delta AT is the difference TAI-UTC in seconds. In current versions of
    * PixInsight the Delta AT database is a plain text file generated with
    * values taken from the following online reference:
    *
    * http://maia.usno.navy.mil/ser7/tai-utc.dat
    *
    * Delta AT data files are expected to follow a simple format where each
    * text line provides a UTC/DeltaAT pair. The exact format is described at
    * the top of the corresponding file included in the current PixInsight
    * distribution.
    *
    * The Delta AT database file can be overridden by the caller module. See
    * the OverrideDeltaATDataFilePath() member function for more information.
    *
    * This static member function is thread-safe. It can be called safely from
    * multiple execution threads running concurrently.
    *
    * \sa TimePoint::DeltaAT()
    */
   static String DeltaATDataFilePath();

   /*!
    * Returns the path to the global database file of CIP positions referred to
    * the ITRS.
    *
    * The position of the Celestial Intermediate Pole (CIP) in the
    * International Terrestrial Reference System (ITRS) is necessary to compute
    * polar motion corrections applied to topocentric coordinates of solar
    * system bodies. These corrections are relevant for the topocentric
    * position of the Moon at the milliarcsecond level.
    *
    * In current versions of PixInsight the CIP_ITRS database is a plain text
    * file generated with values provided by the IERS Rapid Service/Prediction
    * Center. As of writing this documentation, the main online reference is:
    *
    * http://hpiers.obspm.fr/iers/eop/eopc01/eopc01.iau2000.1846-now
    */
   static String CIP_ITRSDataFilePath();

   /*!
    * Override the default fundamental ephemerides file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide fundamental solar system ephemerides. See
    * the FundamentalEphemerides() member function for a comprehensive
    * description.
    *
    * After calling this member function, all fundamental ephemerides will be
    * calculated using the specified XEPH file, which will be installed
    * automatically upon the first call to FundamentalEphemerides().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideFundamentalEphemerides( const String& filePath );

   /*!
    * Override the default short-term fundamental ephemerides file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide fundamental solar system ephemerides. See
    * the FundamentalEphemerides() member function for a comprehensive
    * description.
    *
    * After calling this member function, all short-term fundamental
    * ephemerides will be calculated using the specified XEPH file, which will
    * be installed automatically upon the first call to
    * ShortTermFundamentalEphemerides().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideShortTermFundamentalEphemerides( const String& filePath );

   /*!
    * Override the default asteroid ephemerides file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide asteroid ephemerides coherent with the
    * global fundamental ephemerides being used. See the AsteroidEphemerides()
    * member function for a more comprehensive description.
    *
    * After calling this member function, all asteroid ephemerides will be
    * calculated using the specified XEPH file, which will be installed
    * automatically upon the first call to AsteroidEphemerides().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideAsteroidEphemerides( const String& filePath );

   /*!
    * Override the default short-term asteroid ephemerides file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide asteroid ephemerides coherent with the
    * global fundamental ephemerides being used. See the AsteroidEphemerides()
    * member function for a more comprehensive description.
    *
    * After calling this member function, all short-term asteroid ephemerides
    * will be calculated using the specified XEPH file, which will be installed
    * automatically upon the first call to ShortTermAsteroidEphemerides().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideShortTermAsteroidEphemerides( const String& filePath );

   /*!
    * Override the default Kuiper belt objects (KBOs) ephemerides file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide KBO ephemerides coherent with the global
    * fundamental ephemerides being used. See the KBOEphemerides() member
    * function for a more comprehensive description.
    *
    * After calling this member function, all KBO ephemerides will be
    * calculated using the specified XEPH file, which will be installed
    * automatically upon the first call to KBOEphemerides().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideKBOEphemerides( const String& filePath );

   /*!
    * Override the default short-term Kuiper belt objects (KBOs) ephemerides
    * file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide KBO ephemerides coherent with the global
    * fundamental ephemerides being used. See the KBOEphemerides() member
    * function for a more comprehensive description.
    *
    * After calling this member function, short-term KBO ephemerides will be
    * calculated using the specified XEPH file, which will be installed
    * automatically upon the first call to ShortTermKBOEphemerides().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideShortTermKBOEphemerides( const String& filePath );

   /*!
    * Override the global nutation model ephemeris file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide estimates of nutation angles compliant
    * with the current IAU nutation model. See the NutationModel() member
    * function for more details.
    *
    * After calling this member function, the nutation model used for ephemeris
    * calculations will be provided by the specified XEPH file, which will
    * be installed automatically upon the first call to NutationModel().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideNutationModel( const String& filePath );

   /*!
    * Override the global short-term nutation model ephemeris file.
    *
    * The specified \a filePath must be a valid path to an existing file in
    * XEPH format, which must provide estimates of nutation angles compliant
    * with the current IAU nutation model. See the NutationModel() member
    * function for more details.
    *
    * After calling this member function, the nutation model used for
    * short-term ephemeris calculations will be provided by the specified XEPH
    * file, which will be installed automatically upon the first call to
    * ShortTermNutationModel().
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideShortTermNutationModel( const String& filePath );

   /*!
    * Override the global Delta T database.
    *
    * The specified \a filePath must be a valid path to an existing file in a
    * plain text database format compatible with the standard Delta T database
    * included in PixInsight distributions. See the DeltaTDataFilePath() member
    * function for more details.
    *
    * After calling this member function, Delta T values will be calculated by
    * interpolation from the data provided by the specified file, which will be
    * loaded and parsed automatically upon the first call (explicit or
    * implicit) to TimePoint::DeltaT(). However, for performance and
    * modularization reasons, once a Delta T database has been loaded there is
    * no way to change it, so calling this function again will have no effect.
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideDeltaTDataFilePath( const String& filePath );

   /*!
    * Override the global Delta AT database.
    *
    * The specified \a filePath must be a valid path to an existing file in a
    * plain text database format compatible with the standard Delta AT database
    * included in PixInsight distributions. See the DeltaATDataFilePath()
    * member function for more details.
    *
    * After calling this member function, Delta AT values will be calculated by
    * interpolation from the data provided by the specified file, which will be
    * loaded and parsed automatically upon the first call (explicit or
    * implicit) to TimePoint::DeltaAT(). However, for performance and
    * modularization reasons, once a Delta AT database has been loaded there is
    * no way to change it, so calling this function again will have no effect.
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideDeltaATDataFilePath( const String& filePath );

   /*!
    * Override the global database of CIP positions in the ITRS.
    *
    * The specified \a filePath must be a valid path to an existing file in a
    * plain text database format compatible with the standard CIP_ITRS database
    * included in PixInsight distributions. See the CIP_ITRSDataFilePath()
    * member function for more details.
    *
    * After calling this member function, CIP coordinates in the ITRS will be
    * calculated by interpolation from the data provided by the specified file,
    * which will be loaded and parsed automatically upon the first call
    * (explicit or implicit) to Position::CIP_ITRS(). However, for performance
    * and modularization reasons, once a CIP_ITRS database has been loaded
    * there is no way to change it, so calling this function again will have no
    * effect.
    *
    * This function is useful to build standalone applications that don't
    * depend on a running PixInsight core application, which is necessary to
    * retrieve the default file names and directories from global settings.
    */
   static void OverrideCIP_ITRSDataFilePath( const String& filePath );

private:

   /*!
    * \internal
    * Expansion index node structure.
    */
   struct IndexNode
   {
      int32 jdi = 0;      // Starting point of time span covered by this expansion, int( JD ).
      float jdf = 0;      // Ditto, frac( JD ).
      uint8 n[ 4 ] = {};  // For each component: number of Chebyshev coefficients in this expansion.
      int64 position = 0; // File byte position of first Chebyshev coefficient.

      constexpr bool operator ==( const IndexNode& n ) const
      {
         return jdi == n.jdi && jdf == n.jdf;
      }

      constexpr bool operator <( const IndexNode& n ) const
      {
         return jdi < n.jdi || jdi == n.jdi && jdf < n.jdf;
      }

      constexpr int NumberOfComponents() const
      {
         return (n[0] > 0) ? ((n[1] > 0) ? ((n[2] > 0) ? ((n[3] > 0) ? 4 : 3) : 2) : 1) : 0;
      }

      constexpr int NumberOfCoefficients() const
      {
         return n[0] + n[1] + n[2] + n[3];
      }

      TimePoint StartTime() const
      {
         return TimePoint( jdi, jdf );
      }
   };

   /*!
    * \internal
    * Expansion index structure.
    */
   struct Index
   {
      IsoString        objectId;          // Object identifier (mandatory, case-sensitive).
      IsoString        originId;          // Identifier of the origin of coordinates (mandatory, case-sensitive).
      String           objectName;        // Object name (optional, case-insensitive).
      String           objectDescription; // Object description (optional, arbitrary)
      Optional<double> H;                 // Absolute magnitude.
      Optional<double> G;                 // Slope parameter.
      Optional<double> B_V;               // Color index B-V in magnitudes.
      Optional<double> D;                 // Diameter of the object in km.
      Array<IndexNode> nodes[ 2 ];        // Expansion indexes: 0=function 1=derivative.

      Index( const IsoString& objectId_,
             const IsoString& originId_ = IsoString(),
             const String& objectName_ = String(),
             const String& objectDescription_ = String() )
         : objectId( objectId_.Trimmed() )
         , originId( originId_.Trimmed() )
         , objectName( objectName_.Trimmed() )
         , objectDescription( objectDescription_.Trimmed() )
      {
      }

      bool operator ==( const Index& x ) const
      {
         return objectId == x.objectId && originId == x.originId;
      }

      bool operator <( const Index& x ) const
      {
         return (objectId != x.objectId) ? objectId < x.objectId : originId < x.originId;
      }

      bool HasDerivative() const
      {
         return !nodes[1].IsEmpty();
      }
   };

   mutable File                  m_file;
   mutable AtomicInt             m_handleCount;
   mutable Mutex                 m_mutex;
           TimePoint             m_startTime;
           TimePoint             m_endTime;
           EphemerisMetadata     m_metadata;
           EphemerisConstantList m_constants;
           Array<Index>          m_index;

   // Current data files.
   // If empty, i.e. if not overridden, use platform defaults.
   static String s_ephFilePath;
   static String s_ephFilePath_s;
   static String s_astFilePath;
   static String s_astFilePath_s;
   static String s_kboFilePath;
   static String s_kboFilePath_s;
   static String s_nutFilePath;
   static String s_nutFilePath_s;
   static String s_deltaTFilePath;
   static String s_deltaATFilePath;
   static String s_cipITRSFilePath;

   /*!
    * \internal
    * Returns a pointer to an Index structure for the specified object
    * identifier or name, with respect to the specified origin of coordinates.
    */
   const Index* FindObject( const IsoString& object, const IsoString& origin ) const
   {
      if ( origin.IsEmpty() )
      {
         for ( const Index& index : m_index )
            if ( index.objectId == object )
               return &index;
      }
      else
      {
         Array<Index>::const_iterator i = BinarySearch( m_index.Begin(), m_index.End(), Index( object, origin ) );
         if ( i != m_index.End() )
            return i;
      }
      String name = object.UTF8ToUTF16();
      for ( const Index& index : m_index )
         if ( index.objectName.CompareIC( name ) == 0 )
            if ( origin.IsEmpty() || index.originId == origin )
               return &index;
      throw Error( "Unavailable object '" + name + "\' with origin '" + String( origin ) + "'." );
   }

public:

   /*!
    * \class pcl::EphemerisFile::Handle
    * \brief Calculation of ephemerides from data stored in XEPH files.
    *
    * This subclass provides access to ephemeris data for a specific object
    * available in an ephemeris file. It can perform basic ephemeris
    * calculations, including state vectors and its first derivatives (such as
    * position and velocity), and performs all the low-level file seek and read
    * operations transparently and efficiently.
    *
    * Data stored in an XEPH ephemeris file generates rectangular coordinates
    * referred to the axes of the International Celestial Reference System
    * (ICRS/J2000.0). Positions are given in au and velocities in au/day for
    * all solar system objects, except planetocentric coordinates of natural
    * satellites, including the geocentric Moon, for which positions and
    * velocities are given in kilometers and km/day, respectively. Angles
    * (nutations and librations) are given in radians, and time differences
    * (such as TT-TDB) in seconds.
    *
    * For performance reasons, this class is not thread-safe. However, as far
    * as a given instance is never used concurrently from two or more threads,
    * multiple instances of this class can be used from different running
    * threads, including instances constructed to calculate ephemerides for the
    * same object. This allows for the implementation of performance-intensive,
    * lock-free multithreaded ephemeris calculation tasks.
    */
   class PCL_CLASS Handle
   {
   public:

      /*!
       * Constructs a new %Handle object.
       *
       * \param parent  Reference to an open EphemerisFile object providing
       *                access to an ephemeris data file in XEPH format.
       *
       * \param object  The identifier or name of the object for which this
       *                instance will compute ephemerides.
       *
       * \param origin  The identifier of the origin of coordinates. If an
       *                empty string is specified (which is the default
       *                parameter value), this object will be created for the
       *                first occurrence of ephemeris data available for
       *                \a object in the \a parent file, irrespective of the
       *                origin. In such case, if \a parent is a standard
       *                fundamental ephemerides file, the origin is the solar
       *                system barycenter (identified as "SSB").
       *
       * For a detailed description of object names and identifiers, see
       * EphemerisFile::IsObjectAvailable().
       *
       * If the specified \a parent ephemeris file is not open, or if no
       * ephemeris data are available for the specified \a object and \a origin
       * in the \a parent ephemeris file, this constructor will throw an Error
       * exception.
       */
      Handle( const EphemerisFile& parent, const IsoString& object, const IsoString& origin = IsoString() )
      {
         if ( !parent.IsOpen() )
            throw Error( "Cannot create a handle to a closed ephemeris file." );
         m_parent = &parent;
         m_parent->m_handleCount.Increment();
         m_index = m_parent->FindObject( object, origin );
      }

      /*!
       * Copy constructor.
       */
      Handle( const Handle& x )
      {
         m_parent = x.m_parent;
         m_index = x.m_index;
         m_node[0] = x.m_node[0];
         m_node[1] = x.m_node[1];
         m_node[2] = x.m_node[2];
         if ( m_parent != nullptr )
            m_parent->m_handleCount.Increment();
      }

      /*!
       * Move constructor.
       */
      Handle( Handle&& x )
      {
         m_parent = x.m_parent;
         x.m_parent = nullptr;
         m_index = x.m_index;
         m_node[0] = x.m_node[0];
         m_node[1] = x.m_node[1];
         m_node[2] = x.m_node[2];
      }

      /*!
       * virtual destructor.
       */
      virtual ~Handle()
      {
         if ( m_parent != nullptr )
            m_parent->m_handleCount.Decrement();
      }

      /*!
       * Copy assignment operator. Returns a reference to this object.
       */
      Handle& operator =( const Handle& x )
      {
         if ( m_parent != nullptr )
            m_parent->m_handleCount.Decrement();
         m_parent = x.m_parent;
         m_index = x.m_index;
         m_node[0] = x.m_node[0];
         m_node[1] = x.m_node[1];
         m_node[2] = x.m_node[2];
         if ( m_parent != nullptr )
            m_parent->m_handleCount.Increment();
         return *this;
      }

      /*!
       * Move assignment operator. Returns a reference to this object.
       */
      Handle& operator =( Handle&& x )
      {
         if ( m_parent != nullptr )
            m_parent->m_handleCount.Decrement();
         m_parent = x.m_parent;
         x.m_parent = nullptr;
         m_index = x.m_index;
         m_node[0] = x.m_node[0];
         m_node[1] = x.m_node[1];
         m_node[2] = x.m_node[2];
         return *this;
      }

      /*!
       * Computes a state vector for the specified time point \a t.
       *
       * \param[out] p     Reference to a vector where the components of the
       *                   computed state will be stored.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Rectangular coordinates for position are provided in au, except for
       * the geocentric Moon, for which positions are provided in km.
       *
       * Angles are provided in radians.
       *
       * TT-TDB differences are provided in seconds.
       *
       * The reference system is ICRS/J2000.0.
       *
       * If \a t is either an invalid (uninitialized) TimePoint instance, or a
       * time point outside the time span available from the parent ephemeris
       * file, this member function throws an Error exception.
       */
      void ComputeState( Vector& p, TimePoint t )
      {
         Update( t, 0 );
         p = m_node[0].expansion( t - m_node[0].startTime );
      }

      /*!
       * Computes a state vector and its first derivative for the specified
       * time point \a t.
       *
       * \param[out] p     Reference to a vector where the components of the
       *                   computed state will be stored.
       *
       * \param[out] v     Reference to a vector where the components of the
       *                   computed first derivative will be stored.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Rectangular coordinates for position and velocity are provided in au
       * and au/day, respectively, except for the geocentric Moon, for which
       * position and velocity are provided in km and km/day, respectively.
       *
       * Angles and their variations are provided in radians and radians/day.
       *
       * Time differences (such as TT-TDB) and their variations are provided in
       * seconds and seconds/day.
       *
       * The reference system is ICRS/J2000.0.
       *
       * If the parent ephemeris file provides Chebyshev expansions of state
       * vector derivatives for the object being calculated, the components of
       * \a v will be calculated directly from these expansions. Otherwise the
       * components of \a v will be approximated by numerical differentiation
       * of the Chebyshev expansions for state vectors.
       *
       * If \a t is either an invalid (uninitialized) TimePoint instance, or a
       * time point outside the time span available from the parent ephemeris
       * file, this member function throws an Error exception.
       */
      void ComputeState( Vector& p, Vector& v, TimePoint t )
      {
         ComputeState( p, t );
         ComputeFirstDerivative( v, t );
      }

      /*!
       * Computes the first derivative of the state vector for the specified
       * time point \a t.
       *
       * \param[out] v     Reference to a vector where the components of the
       *                   computed first derivative will be stored.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Rectangular coordinates for velocity are provided in au/day, except
       * for the geocentric Moon, for which velocity is provided in km/day.
       *
       * Angle variations are provided in radians/day.
       *
       * Time difference variations are provided in seconds/day.
       *
       * The reference system is ICRS/J2000.0.
       *
       * If the parent ephemeris file provides Chebyshev expansions of state
       * vector derivatives for the object being calculated, the components of
       * \a v will be calculated directly from these expansions. Otherwise the
       * components of \a v will be approximated by numerical differentiation
       * of the Chebyshev expansions for state vectors.
       *
       * If \a t is either an invalid (uninitialized) TimePoint instance, or a
       * time point outside the time span available from the parent ephemeris
       * file, this member function throws an Error exception.
       */
      void ComputeFirstDerivative( Vector& v, TimePoint t )
      {
         if ( HasDerivative() )
            Update( t, 1 );
         else if ( m_node[1].current != m_node[0].current )
         {
            m_node[1].current = m_node[0].current;
            m_node[1].startTime = m_node[0].startTime;
            m_node[1].endTime = m_node[0].endTime;
            m_node[1].expansion = m_node[0].expansion.Derivative();
         }
         v = m_node[1].expansion( t - m_node[1].startTime );
      }

      /*!
       * Computes the second derivative of the state vector for the specified
       * time point \a t.
       *
       * \param[out] a     Reference to a vector where the components of the
       *                   computed second derivative will be stored.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Rectangular coordinates for acceleration are provided in au/day^2,
       * except for the geocentric Moon, for which acceleration is provided in
       * km/day^2.
       *
       * Angular second derivatives are provided in radians/day^2.
       *
       * Time second derivatives are provided in seconds/day^2.
       *
       * The reference system is ICRS/J2000.0.
       *
       * The components of the second derivative will be approximated by
       * numerical differentiation of the Chebyshev expansions for the first
       * derivative. The latter can be either provided directly by the parent
       * ephemeris file, or also approximated by numerical differentiation.
       *
       * If \a t is either an invalid (uninitialized) TimePoint instance, or a
       * time point outside the time span available from the parent ephemeris
       * file, this member function throws an Error exception.
       */
      void ComputeSecondDerivative( Vector& a, TimePoint t )
      {
         if ( HasDerivative() )
            Update( t, 1 );
         else if ( m_node[1].current != m_node[0].current )
         {
            m_node[1].current = m_node[0].current;
            m_node[1].startTime = m_node[0].startTime;
            m_node[1].endTime = m_node[0].endTime;
            m_node[1].expansion = m_node[0].expansion.Derivative();
         }
         if ( m_node[2].current != m_node[0].current )
         {
            m_node[2].current = m_node[0].current;
            m_node[2].startTime = m_node[0].startTime;
            m_node[2].endTime = m_node[0].endTime;
            m_node[2].expansion = m_node[1].expansion.Derivative();
         }
         a = m_node[2].expansion( t - m_node[2].startTime );
      }

      /*!
       * Computes a state vector for the specified time point \a t.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Returns the computed state function value.
       *
       * See ComputeState( Vector&, TimePoint ) for complete information.
       */
      Vector StateVector( TimePoint t )
      {
         Vector p;
         ComputeState( p, t );
         return p;
      }

      /*!
       * Computes a state vector and its first derivative for the specified
       * time point \a t.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Returns a two-component multivector, where the first component vector
       * is the state function's value and the second is its first derivative.
       *
       * See ComputeState( Vector&, Vector&, TimePoint ) for complete
       * information.
       */
      MultiVector StateVectors( TimePoint t )
      {
         Vector p, v;
         ComputeState( p, v, t );
         return MultiVector( p, v );
      }

      /*!
       * Computes the first derivative of a state vector for the specified time
       * point \a t.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Returns the computed first derivative vector.
       *
       * See ComputeFirstDerivative( Vector&, TimePoint ) for complete
       * information.
       */
      Vector FirstDerivative( TimePoint t )
      {
         Vector v;
         ComputeFirstDerivative( v, t );
         return v;
      }

      /*!
       * Computes the second derivative of a state vector for the specified
       * time point \a t.
       *
       * \param t          The requested time point in the TDB time scale
       *                   (rigorously, this is Teph as defined by JPL, but is
       *                   equivalent to TDB or TT for all practical purposes).
       *
       * Returns the computed second derivative vector.
       *
       * See ComputeSecondDerivative( Vector&, TimePoint ) for complete
       * information.
       */
      Vector SecondDerivative( TimePoint t )
      {
         Vector v;
         ComputeSecondDerivative( v, t );
         return v;
      }

      /*!
       * Returns a reference to the parent ephemeris file object. The parent
       * object was specified upon construction.
       */
      const EphemerisFile& ParentFile() const
      {
         return *m_parent;
      }

      /*!
       * Returns the unique identifier of the object for which this handle can
       * compute ephemerides.
       */
      const IsoString& ObjectId() const
      {
         return m_index->objectId;
      }

      /*!
       * Returns the unique identifier of the origin of the coordinates
       * computed by this object. For example, if this member function returns
       * "Ea" or "Sn", the computed coordinates are geocentric or heliocentric,
       * respectively. If this function returns "SSB", the origin of
       * coordinates is the solar system barycenter. This happens, for example,
       * with all standard XEPH files providing fundamental ephemerides.
       */
      const IsoString& OriginId() const
      {
         return m_index->originId;
      }

      /*!
       * Returns the name of the object for which this handle can compute
       * ephemerides. The returned value can be an empty string if no
       * information about the object name is available in the parent ephemeris
       * file.
       */
      const String& ObjectName() const
      {
         return m_index->objectName;
      }

      /*!
       * Returns the lower bound of the time span for which this instance can
       * calculate ephemerides using the ephemeris data already available. If
       * a time point outside this span is requested, new file seek and read
       * operations must be performed.
       *
       * \param i    Expansion index: 0 to select function values (such as
       *             position), 1 to select function derivatives (such as
       *             velocity).
       */
      TimePoint StartTime( int i = 0 ) const
      {
         return m_node[Range( i, 0, 1 )].startTime;
      }

      /*!
       * Returns the upper bound of the time span for which this instance can
       * calculate ephemerides using the ephemeris data already available. If
       * a time point outside this span is requested, new file seek and read
       * operations must be performed.
       *
       * \param i    Expansion index: 0 to select function values (such as
       *             position), 1 to select function derivatives (such as
       *             velocity).
       */
      TimePoint EndTime( int i = 0 ) const
      {
         return m_node[Range( i, 0, 1 )].endTime;
      }

      /*!
       * Returns true iff the parent ephemeris file provides Chebyshev
       * expansions of state vector derivatives for the object for which this
       * handle computes ephemerides.
       *
       * When no expansions for derivatives are available, derivatives are
       * approximated by numerical differentiation of the Chebyshev expansions
       * for state vectors.
       */
      bool HasDerivative() const
      {
         return m_index->HasDerivative();
      }

      /*!
       * Returns the absolute magnitude of the object. H is the visual
       * magnitude of the object as seen at 1 au of the Earth, 1 au from the
       * Sun, and with a phase angle of 0 degrees.
       *
       * <b>References</b>
       *
       * E. Bowell et al., <em>Asteroids II</em>, R. P. Binzel et al. (eds.),
       * The University of Arizona Press, Tucson, 1989, pp. 549-554.
       *
       * Urban, Sean E., Kenneth Seidelmann, P., ed. (2013), <em>The
       * Explanatory Supplement to the Astronomical Almanac</em> 3rd Edition,
       * Section 10.4.3.
       */
      const Optional<double>& H() const
      {
         return m_index->H;
      }

      /*!
       * Slope parameter. See H() for references.
       */
      const Optional<double>& G() const
      {
         return m_index->G;
      }

      /*!
       * Color index B-V, in magnitudes.
       */
      const Optional<double>& B_V() const
      {
         return m_index->B_V;
      }

      /*!
       * Diameter of the object in km. When available, this is normally an IRAS
       * diameter for an asteroid.
       */
      const Optional<double>& D() const
      {
         return m_index->D;
      }

   private:

      struct NodeInfo
      {
         int          current = -1;
         TimePoint    startTime;
         TimePoint    endTime;
         ChebyshevFit expansion;
      };

      const EphemerisFile*        m_parent = nullptr;
      const EphemerisFile::Index* m_index = nullptr;
            NodeInfo              m_node[ 3 ];
            uint64                m_uniqueId = UniqueId();

      static uint64 UniqueId();

      /*!
       * \internal
       * Updates internal data for the specified time point \a t and function
       * index \a i. Performs file seek and read operations only when required
       * to load a new set of Chebyshev polynomial coefficients.
       */
      void Update( TimePoint t, int index )
      {
         if ( !t.IsValid() )
            throw Error( "Invalid time point." );
         if ( t < m_parent->StartTime() || t > m_parent->EndTime() )
            throw Error( "Time point out of range." );

         const Array<IndexNode>& nodes = m_index->nodes[index];
         NodeInfo& info = m_node[index];

         for ( int N = int( nodes.Length() ), l = 0, r = N-1; ; )
         {
            int m = (l + r) >> 1;
            const IndexNode& node = nodes[m];
            TimePoint t0 = node.StartTime();
            if ( t < t0 )
               r = m;
            else
            {
               if ( m == N-1 || t < nodes[m+1].StartTime() )
               {
                  if ( m != info.current )
                  {
                     ChebyshevFit::coefficient_series coefficients;
                     {
                        volatile AutoLock lock( m_parent->m_mutex );
                        m_parent->m_file.SetPosition( node.position );
                        for ( int i = 0, n = node.NumberOfComponents(); i < n; ++i )
                        {
                           Vector c( node.n[i] );
                           m_parent->m_file.Read( reinterpret_cast<void*>( c.Begin() ), c.Size() );
                           coefficients << c;
                        }
                     }
                     info.current = m;
                     info.startTime = t0;
                     info.endTime = (m < N-1) ? nodes[m+1].StartTime() : m_parent->EndTime();
                     info.expansion = ChebyshevFit( coefficients, 0, info.endTime - info.startTime );
                  }
                  break;
               }

               l = m + 1;
            }
         }
      }

      friend class PCL_CLASS Position;
   };

private:

#if defined( __clang__ )
# pragma GCC diagnostic ignored "-Wunused-private-field"
#endif

   /*
    * For the core JavaScript engine we need garbage-collector-safe
    * asynchronous destruction of these objects.
    */
   mutable bool m_finalized = false;

   /*
    * In the core JavaScript engine we must identify static objects that can be
    * shared and must never be destroyed. This includes fundamental
    * ephemerides, nutation, etc.
    */
   mutable bool m_internal = false;

#if defined( __clang__ )
# pragma GCC diagnostic warning "-Wunused-private-field"
#endif

   class DeserializeObjectThread : public Thread
   {
   public:

      DeserializeObjectThread( const Array<const XMLElement*>& elements
                              , File& file, fsize_type fileSize, fsize_type minPos
                              , int startIndex, int endIndex )
         : m_elements( elements )
         , m_startIndex( startIndex )
         , m_endIndex( endIndex )
         , m_file( file )
         , m_fileSize( fileSize )
         , m_minPos( minPos )
      {
      }

      void Run() override;

      const Array<Index>& Objects() const
      {
         return m_index;
      }

      bool Succeeded() const
      {
         return m_success;
      }

      const Exception* ExceptionThrown() const
      {
         return m_exception.Ptr();
      }

   private:

      const Array<const XMLElement*>& m_elements;
            int                       m_startIndex, m_endIndex;
            File&                     m_file;
            fsize_type                m_fileSize, m_minPos;
            Array<Index>              m_index;
            AutoPointer<Exception>    m_exception;
            bool                      m_success = false;
   };

#ifdef __PCL_BUILDING_PIXINSIGHT_APPLICATION
   friend class pi::JSEphemerisFileObject;
   friend class pi::JSEphemerisHandleObject;
#endif
};

// ----------------------------------------------------------------------------

} // pcl

#endif  // __PCL_EphemerisFile_h

// ----------------------------------------------------------------------------
// EOF pcl/EphemerisFile.h - Released 2022-03-12T18:59:29Z
