File indexing completed on 2024-04-28 07:32:13
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 }