File indexing completed on 2024-04-14 14:11:17

0001 /*
0002     SPDX-FileCopyrightText: 2010 Henry de Valence <hdevalence@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "equirectangularprojector.h"
0008 
0009 #include "ksutils.h"
0010 #include "kstarsdata.h"
0011 #include "skycomponents/skylabeler.h"
0012 
0013 EquirectangularProjector::EquirectangularProjector(const ViewParams &p) : Projector(p)
0014 {
0015     updateClipPoly();
0016 }
0017 
0018 Projector::Projection EquirectangularProjector::type() const
0019 {
0020     return Equirectangular;
0021 }
0022 
0023 double EquirectangularProjector::radius() const
0024 {
0025     return 1.0;
0026 }
0027 
0028 Eigen::Vector2f EquirectangularProjector::toScreenVec(const SkyPoint *o, bool oRefract, bool *onVisibleHemisphere) const
0029 {
0030     double Y, dX;
0031     Eigen::Vector2f p;
0032     double x, y;
0033 
0034     oRefract &= m_vp.useRefraction;
0035     if (m_vp.useAltAz)
0036     {
0037         double Y0;
0038         Y = SkyPoint::refract(o->alt(), oRefract).radians(); //account for atmospheric refraction
0039         Y0 = SkyPoint::refract(m_vp.focus->alt(), oRefract).radians();
0040         dX = m_vp.focus->az().reduce().radians() - o->az().reduce().radians();
0041 
0042         y = (Y - Y0);
0043     }
0044     else
0045     {
0046         dX   = o->ra().reduce().radians() - m_vp.focus->ra().reduce().radians();
0047         Y    = o->dec().radians();
0048         y = (Y - m_vp.focus->dec().radians());
0049     }
0050 
0051     dX = KSUtils::reduceAngle(dX, -dms::PI, dms::PI);
0052 
0053     x = dX;
0054 
0055     p = rst(x, y);
0056 
0057     if (onVisibleHemisphere)
0058         *onVisibleHemisphere = (p[0] > 0 && p[0] < m_vp.width);
0059 
0060     return p;
0061 }
0062 
0063 SkyPoint EquirectangularProjector::fromScreen(const QPointF &p, dms *LST, const dms *lat, bool onlyAltAz) const
0064 {
0065     SkyPoint result;
0066 
0067     //Convert pixel position to x and y offsets in radians
0068     auto p_ = derst(p.x(), p.y());
0069     double dx = p_[0];
0070     double dy = p_[1];
0071 
0072     if (m_vp.useAltAz)
0073     {
0074         dms az, alt;
0075         dx = -1.0 * dx; //Azimuth goes in opposite direction compared to RA
0076         az.setRadians(dx + m_vp.focus->az().radians());
0077         alt.setRadians(dy + SkyPoint::refract(m_vp.focus->alt(), m_vp.useRefraction).radians());
0078         result.setAz(az.reduce());
0079         if (m_vp.useRefraction)
0080             alt = SkyPoint::unrefract(alt);
0081         result.setAlt(alt);
0082         if (!onlyAltAz)
0083             result.HorizontalToEquatorial(LST, lat);
0084         return result;
0085     }
0086     else
0087     {
0088         dms ra, dec;
0089         ra.setRadians(dx + m_vp.focus->ra().radians());
0090         dec.setRadians(dy + m_vp.focus->dec().radians());
0091         result.set(ra.reduce(), dec);
0092         result.EquatorialToHorizontal(LST, lat);
0093         return result;
0094     }
0095 }
0096 
0097 bool EquirectangularProjector::unusablePoint(const QPointF &p) const
0098 {
0099     auto p_ = derst(p.x(), p.y());
0100     double dx = p_[0];
0101     double dy = p_[1];
0102     return (dx * dx > M_PI * M_PI / 4.0) || (dy * dy > M_PI * M_PI / 4.0);
0103 }
0104 
0105 QVector<Eigen::Vector2f> EquirectangularProjector::groundPoly(SkyPoint *labelpoint, bool *drawLabel) const
0106 {
0107     float x0 = m_vp.width / 2.;
0108     if (m_vp.useAltAz)
0109     {
0110         float dX = M_PI;
0111 
0112         // N.B. alt ranges from -π/2 to π/2, but the focus can be at
0113         // either extreme, so the Y-range of the map is actually -π to
0114         // π -- asimha
0115         float dY = M_PI;
0116 
0117         SkyPoint belowFocus;
0118         belowFocus.setAz(m_vp.focus->az().Degrees());
0119         belowFocus.setAlt(0.0);
0120 
0121         // Compute the ends of the horizon line
0122         Eigen::Vector2f obf = toScreenVec(&belowFocus, false);
0123         auto obf_derst = derst(obf.x(), obf.y());
0124         auto corner1 = rst(obf_derst[0] - dX,
0125                            obf_derst[1]);
0126         auto corner2 = rst(obf_derst[0] + dX,
0127                            obf_derst[1]);
0128 
0129         auto corner3 = rst(obf_derst[0] + dX,
0130                            -dY);
0131         auto corner4 = rst(obf_derst[0] - dX,
0132                            -dY);
0133 
0134         QVector<Eigen::Vector2f> ground;
0135         //Construct the ground polygon, which is a simple rectangle in this case
0136         ground << corner1
0137                << corner2;
0138         if (m_vp.fillGround) {
0139                ground << corner3
0140                       << corner4;
0141         }
0142 
0143         if (labelpoint)
0144         {
0145             auto pLabel_ = corner2 - 50. * (corner1 - corner2).normalized();
0146             QPointF pLabel(pLabel_[0], pLabel_[1]);
0147             KStarsData *data = KStarsData::Instance();
0148             *labelpoint      = fromScreen(pLabel, data->lst(), data->geo()->lat());
0149         }
0150         if (drawLabel)
0151             *drawLabel = true;
0152 
0153         return ground;
0154     }
0155     else
0156     {
0157         float dX = m_vp.zoomFactor * M_PI; // RA ranges from 0 to 2π, so half-length is π
0158         float dY = m_vp.zoomFactor * M_PI;
0159         QVector<Eigen::Vector2f> ground;
0160 
0161         static const QString horizonLabel = i18n("Horizon");
0162         float marginLeft, marginRight, marginTop, marginBot;
0163         SkyLabeler::Instance()->getMargins(horizonLabel, &marginLeft, &marginRight, &marginTop, &marginBot);
0164 
0165         double daz = 180.;
0166         double faz = m_vp.focus->az().Degrees();
0167         double az1 = faz - daz;
0168         double az2 = faz + daz;
0169 
0170         bool inverted = ((m_vp.rotationAngle + 90.0_deg).reduce().Degrees() > 180.);
0171         bool allGround = true;
0172         bool allSky    = true;
0173 
0174         double inc = 1.0;
0175         //Add points along horizon
0176         std::vector<Eigen::Vector2f> groundPoints;
0177         for (double az = az1; az <= az2 + inc; az += inc)
0178         {
0179             SkyPoint p   = pointAt(az);
0180             bool visible = false;
0181             Eigen::Vector2f o   = toScreenVec(&p, false, &visible);
0182             if (visible)
0183             {
0184                 groundPoints.push_back(o);
0185                 //Set the label point if this point is onscreen
0186                 if (labelpoint && o.x() < marginRight && o.y() > marginTop && o.y() < marginBot)
0187                     *labelpoint = p;
0188 
0189                 if (o.y() > 0.)
0190                     allGround = false;
0191                 if (o.y() < m_vp.height)
0192                     allSky = false;
0193             }
0194         }
0195 
0196         if (inverted)
0197             std::swap(allGround, allSky);
0198 
0199         if (allSky)
0200         {
0201             if (drawLabel)
0202                 *drawLabel = false;
0203             return QVector<Eigen::Vector2f>();
0204         }
0205 
0206         const Eigen::Vector2f slope {m_vp.rotationAngle.cos(), m_vp.rotationAngle.sin()};
0207         std::sort(groundPoints.begin(), groundPoints.end(), [&](const Eigen::Vector2f & a,
0208                   const Eigen::Vector2f & b)
0209         {
0210             return a.dot(slope) < b.dot(slope);
0211         });
0212 
0213         for (auto point : groundPoints)
0214         {
0215             ground.append(point);
0216         }
0217 
0218         // if (allGround)
0219         // {
0220         //     ground.clear();
0221         //     ground.append(Eigen::Vector2f(x0 - dX, y0 - dY));
0222         //     ground.append(Eigen::Vector2f(x0 + dX, y0 - dY));
0223         //     ground.append(Eigen::Vector2f(x0 + dX, y0 + dY));
0224         //     ground.append(Eigen::Vector2f(x0 - dX, y0 + dY));
0225         //     if (drawLabel)
0226         //         *drawLabel = false;
0227         //     return ground;
0228         // }
0229 
0230         if (labelpoint)
0231         {
0232             QPointF pLabel(x0 - dX - 50., ground.last().y());
0233             KStarsData *data = KStarsData::Instance();
0234             *labelpoint      = fromScreen(pLabel, data->lst(), data->geo()->lat());
0235         }
0236         if (drawLabel)
0237             *drawLabel = true;
0238 
0239         const auto lat = KStarsData::Instance()->geo()->lat();
0240         const Eigen::Vector2f perpendicular {-m_vp.rotationAngle.sin(), m_vp.rotationAngle.cos()};
0241         const double sgn = (lat->Degrees() > 0 ? 1. : -1.);
0242         if (m_vp.fillGround)
0243         {
0244             ground.append(groundPoints.back() + perpendicular * sgn * dY);
0245             ground.append(groundPoints.front() + perpendicular * sgn * dY);
0246         }
0247         return ground;
0248     }
0249 }
0250 
0251 void EquirectangularProjector::updateClipPoly()
0252 {
0253     m_clipPolygon.clear();
0254 
0255     m_clipPolygon << QPointF(0, 0) << QPointF(m_vp.width, 0) << QPointF(m_vp.width, m_vp.height)
0256                   << QPointF(0, m_vp.height);
0257 }