File indexing completed on 2025-03-23 03:31:40
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 };