File indexing completed on 2024-03-24 15:17:42

0001 /*
0002     SPDX-FileCopyrightText: 2010 Henry de Valence <hdevalence@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #pragma once
0008 
0009 #ifdef KSTARS_LITE
0010 #include "skymaplite.h"
0011 #else
0012 #include "skymap.h"
0013 #endif
0014 #include "skyobjects/skypoint.h"
0015 
0016 #if __GNUC__ > 5
0017 #pragma GCC diagnostic push
0018 #pragma GCC diagnostic ignored "-Wignored-attributes"
0019 #endif
0020 #if __GNUC__ > 6
0021 #pragma GCC diagnostic ignored "-Wint-in-bool-context"
0022 #endif
0023 #include <Eigen/Core>
0024 #if __GNUC__ > 5
0025 #pragma GCC diagnostic pop
0026 #endif
0027 
0028 #include <QPointF>
0029 
0030 #include <cstddef>
0031 #include <cmath>
0032 
0033 class KStarsData;
0034 
0035 /** This is just a container that holds information needed to do projections. */
0036 class ViewParams
0037 {
0038     public:
0039         float width, height;
0040         float zoomFactor;
0041         CachingDms rotationAngle;
0042         bool useRefraction;
0043         bool useAltAz;
0044         bool fillGround; ///<If the ground is filled, then points below horizon are invisible
0045         SkyPoint *focus;
0046         ViewParams() : width(0), height(0), zoomFactor(0), rotationAngle(0),
0047             useRefraction(false), useAltAz(false), fillGround(false),
0048             focus(nullptr) {}
0049 };
0050 
0051 /**
0052  * @class Projector
0053  *
0054  * The Projector class is the primary class that serves as an interface to handle projections.
0055  */
0056 class Projector
0057 {
0058         Q_GADGET
0059     public:
0060         /**
0061          * Constructor.
0062          *
0063          * @param p the ViewParams for this projection
0064          */
0065         explicit Projector(const ViewParams &p);
0066 
0067         virtual ~Projector() = default;
0068 
0069         /** Update cached values for projector */
0070         void setViewParams(const ViewParams &p);
0071         ViewParams viewParams() const
0072         {
0073             return m_vp;
0074         }
0075 
0076         enum Projection
0077         {
0078             Lambert,
0079             AzimuthalEquidistant,
0080             Orthographic,
0081             Equirectangular,
0082             Stereographic,
0083             Gnomonic,
0084             UnknownProjection
0085         };
0086         Q_ENUM(Projection)
0087 
0088         /** Return the type of this projection */
0089         Q_INVOKABLE virtual Projection type() const = 0;
0090 
0091         /** Return the FOV of this projection */
0092         double fov() const;
0093 
0094         /**
0095          * Check if the current point on screen is a valid point on the sky. This is needed
0096          * to avoid a crash of the program if the user clicks on a point outside the sky (the
0097          * corners of the sky map at the lowest zoom level are the invalid points).
0098          * @param p the screen pixel position
0099          */
0100         virtual bool unusablePoint(const QPointF &p) const;
0101 
0102         /**
0103          * Given the coordinates of the SkyPoint argument, determine the
0104          * pixel coordinates in the SkyMap.
0105          *
0106          * Since most of the projections used by KStars are very similar,
0107          * if this function were to be reimplemented in each projection subclass
0108          * we would end up changing maybe 5 or 6 lines out of 150.
0109          * Instead, we have a default implementation that uses the projectionK
0110          * and projectionL functions to take care of the differences between
0111          * e.g. Orthographic and Stereographic. There is also the cosMaxFieldAngle
0112          * function, which is used for testing whether a point is on the visible
0113          * part of the projection, and the radius function which gives the radius of
0114          * the projection in screen coordinates.
0115          *
0116          * While this seems ugly, it is less ugly than duplicating 150 loc to change 5.
0117          *
0118          * @return Eigen::Vector2f containing screen pixel x, y coordinates of SkyPoint.
0119          * @param o pointer to the SkyPoint for which to calculate x, y coordinates.
0120          * @param oRefract true = use Options::useRefraction() value.
0121          *   false = do not use refraction.  This argument is only needed
0122          *   for the Horizon, which should never be refracted.
0123          * @param onVisibleHemisphere pointer to a bool to indicate whether the point is
0124          *   on the visible part of the Celestial Sphere.
0125          */
0126         virtual Eigen::Vector2f toScreenVec(const SkyPoint *o, bool oRefract = true,
0127                                             bool *onVisibleHemisphere = nullptr) const;
0128 
0129         /**
0130          * This is exactly the same as toScreenVec but it returns a QPointF.
0131          * It just calls toScreenVec and converts the result.
0132          * @see toScreenVec()
0133          */
0134         QPointF toScreen(const SkyPoint *o, bool oRefract = true, bool *onVisibleHemisphere = nullptr) const;
0135 
0136         /**
0137          * @short Determine RA, Dec coordinates of the pixel at (dx, dy), which are the
0138          * screen pixel coordinate offsets from the center of the Sky pixmap.
0139          * @param p the screen pixel position to convert
0140          * @param LST pointer to the local sidereal time, as a dms object.
0141          * @param lat pointer to the current geographic laitude, as a dms object
0142          * @param onlyAltAz the returned SkyPoint's RA & DEC are not computed, only Alt/Az.
0143          */
0144         virtual SkyPoint fromScreen(const QPointF &p, dms *LST, const dms *lat, bool onlyAltAz = false) const;
0145 
0146         /**
0147          * ASSUMES *p1 did not clip but *p2 did.  Returns the QPointF on the line
0148          * between *p1 and *p2 that just clips.
0149          */
0150         QPointF clipLine(SkyPoint *p1, SkyPoint *p2) const;
0151 
0152         /**
0153          * ASSUMES *p1 did not clip but *p2 did.  Returns the Eigen::Vector2f on the line
0154          * between *p1 and *p2 that just clips.
0155          */
0156         Eigen::Vector2f clipLineVec(SkyPoint *p1, SkyPoint *p2) const;
0157 
0158         /** Check whether the projected point is on-screen */
0159         bool onScreen(const QPointF &p) const;
0160         bool onScreen(const Eigen::Vector2f &p) const;
0161 
0162         /**
0163          * @short Determine if the skypoint p is likely to be visible in the display window.
0164          *
0165          * checkVisibility() is an optimization function.  It determines whether an object
0166          * appears within the bounds of the skymap window, and therefore should be drawn.
0167          * The idea is to save time by skipping objects which are off-screen, so it is
0168          * absolutely essential that checkVisibility() is significantly faster than
0169          * the computations required to draw the object to the screen.
0170          *
0171          * If the ground is to be filled, the function first checks whether the point is
0172          * below the horizon, because they will be covered by the ground anyways.
0173          * Importantly, it does not call the expensive EquatorialToHorizontal function.
0174          * This means that the horizontal coordinates MUST BE CORRECT! The vast majority
0175          * of points are already synchronized, so recomputing the horizontal coordinates is
0176          * a waste.
0177          *
0178          * The function then checks the difference between the Declination/Altitude
0179          * coordinate of the Focus position, and that of the point p.  If the absolute
0180          * value of this difference is larger than fov, then the function returns false.
0181          * For most configurations of the sky map window, this simple check is enough to
0182          * exclude a large number of objects.
0183          *
0184          * Next, it determines if one of the poles of the current Coordinate System
0185          * (Equatorial or Horizontal) is currently inside the sky map window.  This is
0186          * stored in the member variable 'bool SkyMap::isPoleVisible, and is set by the
0187          * function SkyMap::setMapGeometry(), which is called by SkyMap::paintEvent().
0188          * If a Pole is visible, then it will return true immediately.  The idea is that
0189          * when a pole is on-screen it is computationally expensive to determine whether
0190          * a particular position is on-screen or not: for many valid Dec/Alt values, *all*
0191          * values of RA/Az will indeed be onscreen, but for other valid Dec/Alt values,
0192          * only *most* RA/Az values are onscreen.  It is cheaper to simply accept all
0193          * "horizontal" RA/Az values, since we have already determined that they are
0194          * on-screen in the "vertical" Dec/Alt coordinate.
0195          *
0196          * Finally, if no Pole is onscreen, it checks the difference between the Focus
0197          * position's RA/Az coordinate and that of the point p.  If the absolute value of
0198          * this difference is larger than XMax, the function returns false.  Otherwise,
0199          * it returns true.
0200          *
0201          * @param p pointer to the skypoint to be checked.
0202          * @return true if the point p was found to be inside the Sky map window.
0203          * @see SkyMap::setMapGeometry()
0204          * @see SkyMap::fov()
0205          * @note If you are creating skypoints using equatorial coordinates, then
0206          * YOU MUST CALL EQUATORIALTOHORIZONTAL BEFORE THIS FUNCTION!
0207          */
0208         bool checkVisibility(const SkyPoint *p) const;
0209 
0210         /**
0211          * Determine the on-screen position angle of a SkyPont with recept with NCP.
0212          * This is the object's sky position angle (w.r.t. North).
0213          * of "North" at the position of the object (w.r.t. the screen Y-axis).
0214          * The latter is determined by constructing a test point with the same RA but
0215          * a slightly increased Dec as the object, and calculating the angle w.r.t. the
0216          * Y-axis of the line connecting the object to its test point.
0217          */
0218         double findNorthPA(const SkyPoint *o, float x, float y) const;
0219 
0220         /**
0221          * Determine the on-screen position angle of a SkyObject.  This is the sum
0222          * of the object's sky position angle (w.r.t. North), and the position angle
0223          * of "North" at the position of the object (w.r.t. the screen Y-axis).
0224          * The latter is determined by constructing a test point with the same RA but
0225          * a slightly increased Dec as the object, and calculating the angle w.r.t. the
0226          * Y-axis of the line connecting the object to its test point.
0227          */
0228         double findPA(const SkyObject *o, float x, float y) const;
0229 
0230         /**
0231          * Determine the on-screen angle of a SkyPoint with respect to Zenith.
0232          *
0233          * @note Similar to @see findNorthPA
0234          * @note It is assumed that EquatorialToHorizontal has been called on @p o
0235          *
0236          * @description This is determined by constructing a test
0237          * point with the same Azimuth but a slightly increased
0238          * Altitude, and calculating the angle w.r.t. the Y-axis of
0239          * the line connecting the object to its test point.
0240          */
0241         double findZenithPA(const SkyPoint *o, float x, float y) const;
0242 
0243         /**
0244          * Get the ground polygon
0245          * @param labelpoint This point will be set to something suitable for attaching a label
0246          * @param drawLabel this tells whether to draw a label.
0247          * @return the ground polygon
0248          */
0249         virtual QVector<Eigen::Vector2f> groundPoly(SkyPoint *labelpoint = nullptr, bool *drawLabel = nullptr) const;
0250 
0251         /**
0252          * @brief updateClipPoly calculate the clipping polygen given the current FOV.
0253          */
0254         virtual void updateClipPoly();
0255 
0256         /**
0257          * @return the clipping polygen covering the visible sky area. Anything outside this polygon is
0258          * clipped by QPainter.
0259          */
0260         virtual QPolygonF clipPoly() const;
0261 
0262     protected:
0263         /**
0264          * Get the radius of this projection's sky circle.
0265          * @return the radius in radians
0266          */
0267         virtual double radius() const
0268         {
0269             return 2 * M_PI;
0270         }
0271 
0272         /**
0273          * This function handles some of the projection-specific code.
0274          * @see toScreen()
0275          */
0276         virtual double projectionK(double x) const
0277         {
0278             return x;
0279         }
0280 
0281         /**
0282          * This function handles some of the projection-specific code.
0283          * @see toScreen()
0284          */
0285         virtual double projectionL(double x) const
0286         {
0287             return x;
0288         }
0289 
0290         /**
0291          * This function returns the cosine of the maximum field angle, i.e., the maximum angular
0292          * distance from the focus for which a point should be projected. Default is 0, i.e.,
0293          * 90 degrees.
0294          */
0295         virtual double cosMaxFieldAngle() const
0296         {
0297             return 0;
0298         }
0299 
0300         /**
0301          * Transform proj (x, y) to screen (x, y) accounting for scale and rotation
0302          *
0303          * Transforms the Cartesian position given by the projector
0304          * algorithm into the screen coordinate by applying the scale
0305          * factor, rotation and shift from SkyMap origin
0306          *
0307          * rst stands for rotate-scale-translate
0308          *
0309          */
0310         inline Eigen::Vector2f rst(double x, double y) const
0311         {
0312             return
0313             {
0314                 m_vp.width / 2 - m_vp.zoomFactor * (x * m_vp.rotationAngle.cos() - y * m_vp.rotationAngle.sin()),
0315                 m_vp.height / 2 - m_vp.zoomFactor * (x * m_vp.rotationAngle.sin() + y * m_vp.rotationAngle.cos())
0316             };
0317         }
0318 
0319         /**
0320          * Transform screen (x, y) to projector (x, y) accounting for scale, rotation
0321          *
0322          * Transforms the Cartesian position on the screen to the
0323          * Cartesian position accepted by the projector algorithm by
0324          * applying th escale factor, rotation and shift from SkyMap
0325          * origin
0326          *
0327          * rst stands for rotate-scale-translate
0328          *
0329          * @see rst
0330          */
0331         inline Eigen::Vector2f derst(double x, double y) const
0332         {
0333             const double X = (m_vp.width / 2 - x) / m_vp.zoomFactor;
0334             const double Y = (m_vp.height / 2 - y) / m_vp.zoomFactor;
0335             return
0336             {
0337                 m_vp.rotationAngle.cos() * X + m_vp.rotationAngle.sin() * Y,
0338                 -m_vp.rotationAngle.sin() * X + m_vp.rotationAngle.cos() * Y
0339             };
0340         }
0341 
0342         /**
0343          * Helper function for drawing ground.
0344          * @return the point with Alt = 0, az = @p az
0345          */
0346         static SkyPoint pointAt(double az);
0347 
0348         KStarsData *m_data { nullptr };
0349         ViewParams m_vp;
0350         double m_sinY0 { 0 };
0351         double m_cosY0 { 0 };
0352         double m_fov { 0 };
0353         QPolygonF m_clipPolygon;
0354 
0355     private:
0356         //Used by CheckVisibility
0357         double m_xrange { 0 };
0358         bool m_isPoleVisible { false };
0359 };