//     ____   ______ __
//    / __ \ / ____// /
//   / /_/ // /    / /
//  / ____// /___ / /___   PixInsight Class Library
// /_/     \____//_____/   PCL 2.4.23
// ----------------------------------------------------------------------------
// pcl/AstrometricMetadata.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_AstrometricMetadata_h
#define __PCL_AstrometricMetadata_h

/// \file pcl/AstrometricMetadata.h

#include <pcl/Defs.h>

#include <pcl/AutoPointer.h>
#include <pcl/Optional.h>
#include <pcl/Point.h>
#include <pcl/ProjectionBase.h>
#include <pcl/Property.h>
#include <pcl/SphericalRotation.h>
#include <pcl/TimePoint.h>
#include <pcl/WCSKeywords.h>
#include <pcl/WorldTransformation.h>

/*
 * Based on original work contributed by Andrés del Pozo.
 */

#ifdef __PCL_BUILDING_PIXINSIGHT_APPLICATION
namespace pi
{
   class ImageWindow;
}
#endif

namespace pcl
{

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

/*!
 * \defgroup astrometry_support Astrometry Support Classes
 *
 * This group of classes implement the core astrometry support on the
 * PixInsight platform.
 *
 * The original implementation of these classes has been contributed by Spanish
 * software developer and PTeam member Andrés del Pozo, who is also the author
 * of all astrometry scripts available in the standard PixInsight distribution,
 * including the ImageSolver and AnnotateImage scripts, among others.
 */

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

#ifndef __PCL_BUILDING_PIXINSIGHT_APPLICATION
class PCL_CLASS ImageWindow;
#endif

class PCL_CLASS XISFReader;
class PCL_CLASS XISFWriter;

/*!
 * \class AstrometricMetadata
 * \brief Astrometric metadata
 *
 * \ingroup astrometry_support
 */
class PCL_CLASS AstrometricMetadata
{
public:

   /*!
    * \struct pcl::AstrometricMetadata::DescriptionItems
    * \brief A collection of strings describing the properties and parameters
    * of an astrometric solution.
    */
   struct DescriptionItems
   {
      String referenceMatrix;
      String wcsTransformationType;
      String controlPoints;
      String splineLengths;
      String projectionName;
      String projectionOrigin;
      String resolution;
      String rotation;
      String referenceSystem;
      String observationStartTime;
      String observationEndTime;
      String observerLocation;
      String focalDistance;
      String pixelSize;
      String fieldOfView;
      String centerCoordinates;
      String topLeftCoordinates;
      String topRightCoordinates;
      String bottomLeftCoordinates;
      String bottomRightCoordinates;
   };

   /*!
    * Default constructor. Constructs an uninitialized %AstrometricMetadata
    * object.
    */
   AstrometricMetadata() = default;

   /*!
    * Copy constructor.
    */
   AstrometricMetadata( const AstrometricMetadata& x )
      : m_projection( x.m_projection->Clone() )
      , m_transformWI( x.m_transformWI->Clone() )
      , m_refSys( x.m_refSys )
      , m_width( x.m_width )
      , m_height( x.m_height )
      , m_pixelSize( x.m_pixelSize )
      , m_obsStartTime( x.m_obsStartTime )
      , m_obsEndTime( x.m_obsEndTime )
      , m_geoLongitude( x.m_geoLongitude )
      , m_geoLatitude( x.m_geoLatitude )
      , m_geoHeight( x.m_geoHeight )
      , m_resolution( x.m_resolution )
      , m_focalLength( x.m_focalLength )
   {
   }

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

   /*!
    * Constructor from minimal data.
    *
    * \param projection          Pointer to a dynamically allocated projection
    *                            system.
    *
    * \param worldTransformation Pointer to a dynamically allocated world
    *                            transformation.
    *
    * \param width               Width in pixels of the image with which this
    *                            astrometric solution is associated.
    *
    * \param height              Height in pixels of the image with which this
    *                            astrometric solution is associated.
    *
    * Ownership of the specified \a projection and \a worldTransformation
    * objects will be transferred to this object, which will destroy and
    * deallocate them when appropriate.
    */
   AstrometricMetadata( ProjectionBase* projection, WorldTransformation* worldTransformation, int width, int height );

#ifdef __PCL_BUILDING_PIXINSIGHT_APPLICATION
   // Implemented in /core/Components/ImageWindow.cpp
   AstrometricMetadata( const pi::ImageWindow* );
#else
   /*!
    * Constructs an %AstrometricMetadata object from the existing keywords and
    * properties of an image \a window.
    */
   AstrometricMetadata( const ImageWindow& window );
#endif

   /*!
    * Constructs an %AstrometricMetadata object from the existing keywords and
    * properties of the currently selected image in an XISF \a reader.
    */
   AstrometricMetadata( XISFReader& reader );

   /*!
    * Virtual destructor.
    */
   virtual ~AstrometricMetadata()
   {
   }

   /*!
    * Copy constructor. Returns a reference to this object.
    */
   AstrometricMetadata& operator =( const AstrometricMetadata& x )
   {
      m_projection   = x.m_projection->Clone();
      m_transformWI  = x.m_transformWI->Clone();
      m_refSys       = x.m_refSys;
      m_width        = x.m_width;
      m_height       = x.m_height;
      m_pixelSize    = x.m_pixelSize;
      m_obsStartTime = x.m_obsStartTime;
      m_obsEndTime   = x.m_obsEndTime;
      m_geoLongitude = x.m_geoLongitude;
      m_geoLatitude  = x.m_geoLatitude;
      m_geoHeight    = x.m_geoHeight;
      m_resolution   = x.m_resolution;
      m_focalLength  = x.m_focalLength;
      return *this;
   }

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

   /*!
    * Returns true iff this object is valid. A valid %AstrometricMetadata
    * instance defines a projection system and a world transformation.
    */
   bool IsValid() const
   {
      return !m_projection.IsNull() && !m_transformWI.IsNull();
   }

   /*!
    * Checks that this astrometric solution is valid and can perform coherent
    * coordinate transformations, and returns the reprojection errors
    * measured at the center and the four corners of the image.
    *
    * \param[out] centerErrors      Reprojection errors at the geometric center
    *                               of the image, or (Width()/2,Height()/2)
    *                               image coordinates.
    *
    * \param[out] topLeftErrors     Reprojection errors at (0,0) image
    *                               coordinates.
    *
    * \param[out] topRightErrors    Reprojection errors at (Width(),0) image
    *                               coordinates.
    *
    * \param[out] bottomLeftErrors  Reprojection errors at (0,Height()) image
    *                               coordinates.
    *
    * \param[out] bottomRightErrors Reprojection errors at (Width(),Height())
    *                               image coordinates.
    *
    * This routine performs five successive coordinate transformations, from
    * image to celestial and from celestial to image coordinates, computed for
    * the four corners and the geometric center of the image. The reported
    * values in the specified point variables are the differences between the
    * initial and final image coordinates, on the X and Y axes, in pixels.
    *
    * This function throws an Error exception if the solution has not been
    * initialized, or if it cannot perform valid coordinate transformations.
    */
   void Verify( DPoint& centerErrors,
                DPoint& topLeftErrors, DPoint& topRightErrors,
                DPoint& bottomLeftErrors, DPoint& bottomRightErrors ) const;

   /*!
    * Checks that this astrometric solution is valid and can perform coherent
    * coordinate transformations.
    *
    * \param tolerance     Maximum difference in pixels allowed for validation.
    *                      The default value is 0.01 pixels.
    *
    * This routine performs two successive coordinate transformations, from
    * image to celestial and from celestial to image coordinates, computed for
    * the geometric center of the image. If the absolute value of the
    * difference between the initial and final image coordinates is greater
    * than the specified tolerance in pixels, for one or both axes, an Error
    * exception is thrown.
    *
    * This function also throws an Error exception if the solution has not been
    * initialized, or if it cannot perform valid coordinate transformations.
    */
   void Validate( double tolerance = 0.01 ) const;

   /*!
    * Returns true if this object uses a world transformation based on 2-D
    * surface splines (or <em>thin plates</em>), false if a WCS linear
    * transformation is being used.
    */
   bool HasSplineWorldTransformation() const
   {
      return dynamic_cast<const SplineWorldTransformation*>( m_transformWI.Pointer() ) != nullptr;
   }

#ifdef __PCL_BUILDING_PIXINSIGHT_APPLICATION
   // Implemented in /core/Components/ImageWindow/ImageWindow.cpp
   void Write( pi::ImageWindow* window, bool notify = true ) const;
#else
   /*!
    * Updates the keywords and properties of an image \a window to represent
    * this astrometric solution.
    *
    * The pixel dimensions of the image in the target \a window must be
    * coherent with this astrometric solution. If that is not true, this member
    * function will throw an Error exception. An Error exception will also be
    * thrown if this object does not define a valid astrometric solution.
    *
    * See the UpdateBasicKeywords(), UpdateWCSKeywords() and UpdateProperties()
    * member functions for information on the metadata items modified by this
    * function.
    */
   void Write( ImageWindow& window, bool notify = true ) const;
#endif

   /*!
    * Updates the keywords and properties of the current image in an XISF
    * \a writer to store the astrometric solution represented by this object.
    *
    * The caller must ensure that the pixel dimensions of the current image in
    * the target %XISF \a writer, that is, the width and height of the image
    * being generated, are coherent with this astrometric solution. Currently
    * this condition cannot be verified or enforced by this member function.
    *
    * This function will throw an Error exception if this object does not
    * define a valid astrometric solution.
    *
    * See the UpdateBasicKeywords(), UpdateWCSKeywords() and UpdateProperties()
    * member functions for information on the metadata items modified by this
    * function.
    */
   void Write( XISFWriter& writer ) const;

   /*!
    * Returns the name of the coordinate reference system to which this
    * astrometric solution has been referred.
    *
    * This corresponds to the reference system of the coordinates of the
    * reference point sources (usually stars with data acquired from
    * astrometric catalogs) used to compute the astrometric solution. The
    * current implementation supports the following reference systems:
    *
    * \b ICRS
    *
    * Reference point source positions are \e astrometric: they include
    * corrections for space motion (proper motions, annual parallax and radial
    * velocity, when available) and gravitational deflection of light, but
    * \e not annual aberration. This is the default reference system that is
    * assumed when none is specified or acquired from existing metadata.
    *
    * \b GCRS
    *
    * Reference point source positions are \e proper: they include corrections
    * for space motion (proper motions, annual parallax and radial velocity,
    * when available), gravitational deflection of light, and annual aberration
    * (rigorous relativistic model).
    *
    * Other nonstandard values can be returned by this function, such as
    * different values of the RADESYS FITS keyword. Such values will be
    * preserved but won't be supported by standard platform implementations.
    */
   IsoString ReferenceSystem() const
   {
      return m_refSys.IsEmpty() ? IsoString( "ICRS" ) : m_refSys;
   }

   /*!
    * Sets the coordinate reference system. See ReferenceSystem() for more
    * information and supported values.
    */
   void SetReferenceSystem( const IsoString& refSys )
   {
      m_refSys = refSys.Trimmed();
   }

   /*!
    * Returns the name of the reference system of celestial coordinates
    * derived from the specified image metadata.
    *
    * \param properties       A list of XISF image properties.
    *
    * \param keywords         A list of FITS header keywords.
    *
    * The reference system will be inferred from the following metadata items:
    *
    * \li The standard XISF property 'Observation:CelestialReferenceSystem'
    *
    * \li The standard FITS keyword 'RADESYS'.
    *
    * In case of metadata duplicity, XISF properties always take precedence
    * over FITS keywords.
    *
    * Typical returned values are "ICRS" and "GCRS". If no valid reference
    * system can be deduced from the specified metadata, this function returns
    * an empty string.
    */
   static IsoString ReferenceSystemFromMetadata( const PropertyArray& properties, const FITSKeywordArray& keywords );

   /*!
    * Returns the width in pixels of the image associated with the astrometric
    * solution represented by this object.
    */
   int Width() const
   {
      return m_width;
   }

   /*!
    * Returns the height in pixels of the image associated with the astrometric
    * solution represented by this object.
    */
   int Height() const
   {
      return m_height;
   }

   /*!
    * Returns a pointer to the projection system of this astrometric solution.
    * Returns \c nullptr if this object is not valid.
    */
   const ProjectionBase* Projection() const
   {
      return m_projection.Pointer();
   }

   /*!
    * Returns a pointer to the world coordinate transformation of this
    * astrometric solution. Returns \c nullptr if this object is not valid.
    */
   const WorldTransformation* WorldTransform() const
   {
      return m_transformWI.Pointer();
   }

   /*!
    * Returns the image resolution in seconds of arc per pixel, calculated from
    * the specified \a focal distance in millimeters.
    */
   double ResolutionFromFocal( double focal ) const
   {
      return (focal > 0) ? m_pixelSize.OrElse( 0 )/focal * 0.18/Const<double>::pi() : 0.0;
   }

   /*!
    * Returns the focal distance in millimeters, calculated from the specified
    * image \a resolution expressed in seconds of arc per pixel.
    */
   double FocalFromResolution( double resolution ) const
   {
      return ResolutionFromFocal( resolution );
   }

   /*!
    * Returns the angle of rotation of the Y axis of the image with respect to
    * the north direction, as represented on a flat projection of the celestial
    * sphere. A rotation angle of zero aligns north along the positive Y axis.
    * A positive rotation angle rotates the Y axis from north to east.
    *
    * The returned value is the rotation angle of the projection in degrees.
    * The \a flipped variable will be true iff the projection is mirrored on
    * the east-west direction.
    */
   double Rotation( bool& flipped ) const;

   /*!
    * Returns the resolution of the image in degrees per pixel.
    *
    * This value usually is an approximation since it changes across the image.
    * It should only be used for informative purposes.
    */
   double Resolution() const
   {
      return m_resolution;
   }

   /*!
    * Returns the observation start time, if available. When defined, the
    * returned value should be represented in the UTC timescale.
    */
   Optional<TimePoint> ObservationStartTime() const
   {
      return m_obsStartTime;
   }

   /*!
    * Sets the observation start time. The specified \a startTime value should
    * be represented in the UTC timescale.
    */
   void SetObservationStartTime( TimePoint startTime )
   {
      m_obsStartTime = startTime;
   }

   /*!
    * Returns the observation end time, if available. When defined, the
    * returned value should be represented in the UTC timescale.
    */
   Optional<TimePoint> ObservationEndTime() const
   {
      return m_obsEndTime;
   }

   /*!
    * Sets the observation end time. The specified \a endTime value should be
    * represented in the UTC timescale.
    */
   void SetObservationEndTime( TimePoint endTime )
   {
      m_obsEndTime = endTime;
   }

   /*!
    * Returns an estimate of the observation middle time. If both the start and
    * end times are defined, returns the time point between them. If only the
    * start time is defined, it is returned. Otherwise an undefined object is
    * returned.
    */
   Optional<TimePoint> ObservationMiddleTime() const
   {
      if ( !m_obsStartTime.IsDefined() )
         return Optional<TimePoint>();
      if ( !m_obsEndTime.IsDefined() )
         return m_obsStartTime;
      return m_obsStartTime() + (m_obsEndTime() - m_obsStartTime())/2;
   }

   /*!
    * Returns the geodetic longitude of the observation location, if available.
    * The returned value is expressed in degrees in the range (-180,+180],
    * reckoned positive eastward of the reference meridian.
    */
   Optional<double> LocationLongitude() const
   {
      return m_geoLongitude;
   }

   /*!
    * Sets the geodetic longitude of the observation location. The specified
    * \a longitude must be expressed in degrees and can be either in the range
    * [0,360] or [-180,+180], reckoned positive eastward.
    *
    * If a value out of the valid range is specified, this function will throw
    * an Error exception.
    */
   void SetLocationLongitude( double longitude )
   {
      if ( longitude > 180 )
         longitude -= 360;
      else if ( longitude <= -180 )
         longitude += 360;
      if ( longitude < -180 || longitude > +180 )
         throw Error( "AstrometricMetadata::SetLocationLongitude(): Geographic longitude out of range." );
      m_geoLongitude = longitude;
   }

   /*!
    * Returns the geodetic latitude of the observation location, if available.
    * The returned value is expressed in degrees in the range [-90,+90],
    * reckoned positive north of the equator.
    */
   Optional<double> LocationLatitude() const
   {
      return m_geoLatitude;
   }

   /*!
    * Sets the geodetic latitude of the observation location. The specified
    * \a latitude must be expressed in degrees and must be in the range
    * [-90,+90], reckoned positive north of the equator.
    *
    * If a value out of the valid range is specified, this function will throw
    * an Error exception.
    */
   void SetLocationLatitude( double latitude )
   {
      if ( latitude < -90 || latitude > +90 )
         throw Error( "AstrometricMetadata::SetLocationLatitude(): Geographic latitude out of range." );
      m_geoLatitude = latitude;
   }

   /*!
    * Returns the geodetic height of the observation location in meters, if
    * available.
    */
   Optional<double> LocationHeight() const
   {
      return m_geoHeight;
   }

   /*!
    * Sets the geodetic height of the observation location in meters.
    */
   void SetLocationHeight( double height )
   {
      m_geoHeight = height;
   }

   /*!
    * Returns the physical pixel size in micrometers, if available.
    */
   Optional<double> PixelSize() const
   {
      return m_pixelSize;
   }

   /*!
    * Redefines the physical pixel size in micrometers. Recalculates the focal
    * distance as a function of the pixel size.
    */
   void SetPixelSize( double pixelSize )
   {
      m_pixelSize = pixelSize;
      m_focalLength = FocalFromResolution( m_resolution );
   }

   /*!
    * Transformation from image coordinates to celestial coordinates.
    *
    * \param[out] pRD   Reference to a point where the output equatorial
    *                   spherical coordinates will be stored, expressed in
    *                   degrees. \a pRD.x will be the right ascension and
    *                   \a pRD.y the declination. Output right ascensions are
    *                   constrained to the [0,360) range. Output declinations
    *                   are in the range [-90,+90].
    *
    * \param pI         Input image coordinates in pixels. The specified
    *                   location can legally lie outside the image bounds
    *                   defined by [0,0]-[Width(),Height()].
    *
    * Returns true iff the specified point \a pI can be projected on the
    * celestial sphere using this astrometric solution.
    *
    * The output coordinates stored in \a pRD will be referred to the reference
    * system of this astrometric solution, either ICRS or GCRS, as returned by
    * the ReferenceSystem() member function.
    *
    * \sa RawImageToCelestial(), CelestialToImage()
    */
   bool ImageToCelestial( DPoint& pRD, const DPoint& pI ) const
   {
      if ( !IsValid() )
         throw Error( "Invalid call to AstrometricMetadata::ImageToCelestial(): No astrometric solution." );
      if ( m_projection->Inverse( pRD, m_transformWI->Inverse( pI ) ) )
      {
         // Constrain right ascension to the [0,360) range.
         if ( pRD.x < 0 )
            pRD.x += 360;
         else if ( pRD.x >= 360 )
            pRD.x -= 360;
         return true;
      }
      return false;
   }

   /*!
    * Transformation from image coordinates to celestial coordinates, without
    * enforcing a valid range of right ascensions.
    *
    * \param[out] pRD   Reference to a point where the output equatorial
    *                   spherical coordinates will be stored, expressed in
    *                   degrees. \a pRD.x will be the right ascension and
    *                   \a pRD.y the declination. Output right ascensions are
    *                   \e not constrained to the [0,360) range. Output
    *                   declinations are in the range [-90,+90].
    *
    * \param pI         Input image coordinates in pixels. The specified
    *                   location can legally lie outside the image bounds
    *                   defined by [0,0]-[Width(),Height()].
    *
    * Returns true iff the specified point \a pI can be projected on the
    * celestial sphere using this astrometric solution.
    *
    * The output coordinates stored in \a pRD will be referred to the reference
    * system of this astrometric solution, either ICRS or GCRS, as returned by
    * the ReferenceSystem() member function.
    *
    * This function is useful for interpolation schemes where discontinuities
    * caused by zero crossings in right ascension, i.e. abrupt changes from 360
    * to 0 degrees, are not admissible numerically. Right ascensions returned
    * by this function can be larger than 360 degrees or less than zero,
    * ensuring smooth transitions.
    *
    * \sa ImageToCelestial(), CelestialToImage()
    */
   bool RawImageToCelestial( DPoint& pRD, const DPoint& pI ) const
   {
      if ( !IsValid() )
         throw Error( "Invalid call to AstrometricMetadata::RawImageToCelestial(): No astrometric solution." );
      return m_projection->Inverse( pRD, m_transformWI->Inverse( pI ) );
   }

   /*!
    * Transformation from celestial coordinates to image coordinates.
    *
    * \param[out] pI    Reference to a point where the output image coordinates
    *                   will be stored.
    *
    * \param pRD        Input equatorial spherical coordinates expressed in
    *                   degrees. \a pRD.x is the right ascension and \a pRD.y
    *                   is declination.
    *
    * Returns true iff the specified celestial coordinates can be reprojected
    * on the image coordinate system. Note that the output image coordinates
    * can lie outside of the image bounds defined by [0,0]-[Width(),Height()].
    *
    * The input coordinates specified in \a pRD are expected to be referred to
    * the reference system of this astrometric solution, either ICRS or GCRS,
    * as returned by the ReferenceSystem() member function.
    *
    * \sa ImageToCelestial(), RawImageToCelestial()
    */
   bool CelestialToImage( DPoint& pI, const DPoint& pRD ) const
   {
      if ( !IsValid() )
         throw Error( "Invalid call to AstrometricMetadata::CelestialToImage(): No astrometric solution." );
      DPoint pW;
      if ( m_projection->Direct( pW, pRD ) )
      {
         pI = m_transformWI->Direct( pW );
         return true;
      }
      return false;
   }

   /*!
    * Regenerates the astrometric solution from standardized metadata.
    *
    * \param properties       A list of XISF image properties describing
    *                         critical astrometry-related metadata items.
    *
    * \param keywords         A list of FITS header keywords, which should
    *                         contain at least a minimal set of standard WCS
    *                         keywords to define a linear world transformation
    *                         from celestial to image coordinates.
    *
    * \param controlPoints    If not empty, this array must contain a list of
    *                         spline control points and generation parameters
    *                         serialized in raw binary format. See the
    *                         SplineWorldTransformation class for more
    *                         information.
    *
    * \param width            Width in pixels of the image with which this
    *                         astrometric solution is associated.
    *
    * \param height           Height in pixels of the image with which this
    *                         astrometric solution is associated.
    *
    * The following standard XISF properties will be extracted from the
    * specified \a properties array, if available:
    *
    * <pre>
    * Observation:CelestialReferenceSystem
    * Observation:Center:RA
    * Observation:Center:Dec
    * Observation:Equinox
    * Observation:Time:Start
    * Observation:Time:End
    * Observation:Location:Longitude
    * Observation:Location:Latitude
    * Observation:Location:Elevation
    * Instrument:Telescope:FocalLength
    * Instrument:Sensor:XPixelSize
    * </pre>
    *
    * %XISF properties will take precedence over equivalent %FITS keywords.
    *
    * If the specified \a controlPoints array contains a valid serialization of
    * spline control points, the astrometric solution will use a high-precision
    * world transformation based on two-dimensional surface splines, also knwon
    * as <em>thin plate splines</em>, which is capable of modeling local image
    * distortions that are intractable with WCS linear transformations.
    *
    * If this object contains valid metadata before calling this function, it
    * will be disposed as appropriate, and a completely new astrometric
    * solution will be constructed.
    *
    * This member function can throw exceptions (of the Error class) if either
    * the specified \a controlPoints array is not empty and does not contain a
    * valid raw serialization of a spline-based transformation, or if the
    * generated coordinate transformations are not invalid (in the numerical or
    * geometric sense).
    */
   void Build( const PropertyArray& properties, const FITSKeywordArray& keywords,
               const ByteArray& controlPoints, int width, int height );

   /*!
    * Updates the specified \a keywords array with basic astrometric FITS
    * header keywords. This includes a number of standard instrumental and
    * observational keywords, as well as some customary nonstandard keywords,
    * included for compatibility with third-party applications:
    *
    * <pre>
    * RA
    * OBJCTRA
    * DEC
    * OBJCTDEC
    * DATE-OBS
    * DATE-END
    * OBSGEO-L
    * LONG-OBS
    * OBSGEO-B
    * LAT-OBS
    * OBSGEO-H
    * ALT-OBS
    * FOCALLEN
    * XPIXSZ
    * YPIXSZ
    * PIXSIZE
    * </pre>
    *
    * Keywords are updated when the corresponding metadata items are available.
    */
   void UpdateBasicKeywords( FITSKeywordArray& keywords ) const;

   /*!
    * Updates the specified \a keywords array with the set of standard WCS FITS
    * header keywords:
    *
    * <pre>
    * RADESYS
    * EQUINOX
    * EPOCH
    * CTYPE1
    * CTYPE2
    * CRVAL1
    * CRVAL2
    * CRPIX1
    * CRPIX2
    * CD1_1
    * CD1_2
    * CD2_1
    * CD2_2
    * PC1_1
    * PC1_2
    * PC2_1
    * PC2_2
    * CDELT1
    * CDELT2
    * CROTA1
    * CROTA2
    * PV1_1
    * PV1_2
    * PV1_3
    * PV1_4
    * LONPOLE
    * LATPOLE
    * </pre>
    *
    * In addition, a custom nonstandard keyword is also generated to signal the
    * availability of a spline-based astrometric solution:
    *
    * <pre>
    * REFSPLIN
    * </pre>
    */
   void UpdateWCSKeywords( FITSKeywordArray& keywords ) const;

   /*!
    * Updates the specified \a properties array with a restricted set of view
    * properties to reflect the state of this astrometric solution.
    *
    * The following standard XISF properties will be created or redefined:
    *
    * <pre>
    * Instrument:Telescope:FocalLength
    * Instrument:Sensor:XPixelSize
    * Instrument:Sensor:YPixelSize
    * Observation:Center:RA
    * Observation:Center:Dec
    * Observation:CelestialReferenceSystem
    * Observation:Equinox
    * </pre>
    *
    * The following properties can be removed if the transformation is valid,
    * since the default coordinate reference point is the geometric center of
    * the image, which is used to calculate the values of the standard
    * \c Observation:Center:RA and \c Observation:Center:Dec properties:
    *
    * <pre>
    * Observation:Center:X
    * Observation:Center:Y
    * </pre>
    *
    * The following properties will be created or redefined if the
    * corresponding metadata items are available, or removed otherwise:
    *
    * <pre>
    * Observation:Time:Start
    * Observation:Time:End
    * Observation:Location:Longitude
    * Observation:Location:Latitude
    * Observation:Location:Elevation
    * </pre>
    *
    * In addition, the following nonstandard property, used by platform image
    * plate solving scripts, will be created, redefined, or removed:
    *
    * <pre>
    * Transformation_ImageToProjection
    * </pre>
    */
   void UpdateProperties( PropertyArray& properties ) const;

   /*!
    * Removes astrometry-related FITS header keywords from the specified
    * \a keywords array. This includes some basic instrumental and
    * observational keywords, as well as the following keywords:
    *
    * <pre>
    * RADESYS
    * EQUINOX
    * EPOCH
    * CTYPE1
    * CTYPE2
    * CRVAL1
    * CRVAL2
    * CRPIX1
    * CRPIX2
    * CD1_1
    * CD1_2
    * CD2_1
    * CD2_2
    * PC1_1
    * PC1_2
    * PC2_1
    * PC2_2
    * CDELT1
    * CDELT2
    * CROTA1
    * CROTA2
    * PV1_1
    * PV1_2
    * PV1_3
    * PV1_4
    * LONPOLE
    * LATPOLE
    * REFSPLIN
    * </pre>
    *
    * If \a removeCenterKeywords is true, the following keywords will also be
    * removed:
    *
    * <pre>
    * RA
    * OBJCTRA
    * DEC
    * OBJCTDEC
    * </pre>
    *
    * If \a removeScaleKeywords is true, the following keywords will also be
    * removed:
    *
    * <pre>
    * FOCALLEN
    * XPIXSZ
    * YPIXSZ
    * PIXSIZE
    * </pre>
    */
   static void RemoveKeywords( FITSKeywordArray& keywords, bool removeCenterKeywords = true, bool removeScaleKeywords = true );

   /*!
    * Rescales the values of existing FITS header keywords defining physical
    * pixel dimensions in the specified \a keywords array.
    *
    * This function multiplies the values of the following keywords by the
    * specified \a scalingFactor:
    *
    * <pre>
    * XPIXSZ
    * YPIXSZ
    * PIXSIZE
    * </pre>
    *
    * This is useful in cases where a geometric transformation has to be
    * applied to an image to modify its scale, such as a resampling operation.
    */
   static void RescalePixelSizeKeywords( FITSKeywordArray& keywords, double scalingFactor );

   /*!
    * Removes astrometry-related XISF properties from the specified
    * \a properties array.
    *
    * If \a removeCenterProperties is true, the following properties will also
    * be removed:
    *
    * <pre>
    * Observation:Center:RA
    * Observation:Center:Dec
    * Observation:Center:X
    * Observation:Center:Y
    * Observation:CelestialReferenceSystem
    * Observation:Equinox
    * </pre>
    *
    * If \a removeScaleProperties is true, the following properties will also
    * be removed:
    *
    * <pre>
    * Instrument:Telescope:FocalLength
    * Instrument:Sensor:XPixelSize
    * Instrument:Sensor:YPixelSize
    * </pre>
    *
    * In addition, the following nonstandard property, used by platform image
    * plate solving scripts, will be removed:
    *
    * <pre>
    * Transformation_ImageToProjection
    * </pre>
    */
   static void RemoveProperties( PropertyArray& properties, bool removeCenterProperties = true, bool removeScaleProperties = true );

   /*!
    * Removes astrometry-related XISF properties from the specified \a window's
    * main view.
    *
    * See RemoveProperties( PropertyArray&, bool, bool ) for detailed
    * information.
    */
   static void RemoveProperties( ImageWindow& window, bool removeCenterProperties = true, bool removeScaleProperties = true );

   /*!
    * Rescales the values of existing XISF properties defining physical pixel
    * dimensions in the specified \a properties array.
    *
    * This function multiplies the values of the following properties by the
    * specified \a scalingFactor:
    *
    * <pre>
    * Instrument:Sensor:XPixelSize
    * Instrument:Sensor:YPixelSize
    * </pre>
    *
    * This is useful in cases where a geometric transformation has to be
    * applied to an image to modify its scale, such as a resampling operation.
    */
   static void RescalePixelSizeProperties( PropertyArray& properties, double scalingFactor );

   /*!
    * Rescales the values of existing XISF properties defining physical pixel
    * dimensions for the specified \a window's main view.
    *
    * See RescalePixelSizeProperties( PropertyArray&, double ) for detailed
    * information.
    */
   static void RescalePixelSizeProperties( ImageWindow& window, double scalingFactor );

   /*!
    * Returns a printable textual representation of the metadata properties and
    * parameters of the astrometric solution represented by this object.
    */
   String Summary() const;

   /*!
    * Returns a collection of strings describing the metadata properties and
    * parameters of this astrometric solution.
    *
    * The returned object is a copy of an internal structure that is generated
    * automatically as necessary and cached between successive function calls.
    * This allows for efficient real-time representations of astrometric
    * metadata and parameters.
    */
   DescriptionItems Description() const
   {
      UpdateDescription();
      return m_description.IsNull() ? DescriptionItems() : *m_description;
   }

private:

   AutoPointer<ProjectionBase>      m_projection;
   AutoPointer<WorldTransformation> m_transformWI;
   IsoString                        m_refSys;         // ICRS(default) or GCRS
   Optional<double>                 m_equinox;        // years - deprecated
   int                              m_width = 0;      // px
   int                              m_height = 0;     // px
   Optional<double>                 m_pixelSize;      // um
   Optional<TimePoint>              m_obsStartTime;   // UTC
   Optional<TimePoint>              m_obsEndTime;     // UTC
   Optional<double>                 m_geoLongitude;   // deg
   Optional<double>                 m_geoLatitude;    // deg
   Optional<double>                 m_geoHeight;      // m
   double                           m_resolution = 0; // deg/px
   Optional<double>                 m_focalLength;    // mm
   mutable
   AutoPointer<DescriptionItems>    m_description;

   WCSKeywords ComputeWCSKeywords() const;
   void UpdateDescription() const;
};

} //pcl

#endif // __AstrometricMetadata_h

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