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

0001 /*
0002     SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "skyobject.h"
0008 
0009 #include "geolocation.h"
0010 #include "ksnumbers.h"
0011 #include "kspaths.h"
0012 #ifdef KSTARS_LITE
0013 #include "skymaplite.h"
0014 #else
0015 #include "kspopupmenu.h"
0016 #include "skymap.h"
0017 #endif
0018 #include "kstarsdata.h"
0019 #include "Options.h"
0020 #include "starobject.h"
0021 #include "skycomponents/skylabeler.h"
0022 
0023 
0024 const SkyObject::UID SkyObject::invalidUID   = ~0;
0025 const SkyObject::UID SkyObject::UID_STAR     = 0;
0026 const SkyObject::UID SkyObject::UID_GALAXY   = 1;
0027 const SkyObject::UID SkyObject::UID_DEEPSKY  = 2;
0028 const SkyObject::UID SkyObject::UID_SOLARSYS = 3;
0029 
0030 SkyObject::SkyObject(int t, dms r, dms d, float m, const QString &n, const QString &n2, const QString &lname)
0031     : SkyPoint(r, d)
0032 {
0033     setType(t);
0034     sortMagnitude = m;
0035     setName(n);
0036     setName2(n2);
0037     setLongName(lname);
0038 }
0039 
0040 SkyObject::SkyObject(int t, double r, double d, float m, const QString &n, const QString &n2, const QString &lname)
0041     : SkyPoint(r, d)
0042 {
0043     setType(t);
0044     sortMagnitude = m;
0045     setName(n);
0046     setName2(n2);
0047     setLongName(lname);
0048 }
0049 
0050 SkyObject *SkyObject::clone() const
0051 {
0052     Q_ASSERT(typeid(this) == typeid(static_cast<const SkyObject *>(this))); // Ensure we are not slicing a derived class
0053     return new SkyObject(*this);
0054 }
0055 
0056 void SkyObject::showPopupMenu(KSPopupMenu *pmenu, const QPoint &pos)
0057 {
0058 #if defined(KSTARS_LITE)
0059     Q_UNUSED(pos)
0060     Q_UNUSED(pmenu);
0061 #else
0062     initPopupMenu(pmenu);
0063     pmenu->popup(pos);
0064 #endif
0065 }
0066 
0067 void SkyObject::initPopupMenu(KSPopupMenu *pmenu)
0068 {
0069 #ifdef KSTARS_LITE
0070     Q_UNUSED(pmenu)
0071 #else
0072     pmenu->createEmptyMenu(this);
0073 #endif
0074 }
0075 
0076 void SkyObject::setLongName(const QString &longname)
0077 {
0078     if (longname.isEmpty())
0079     {
0080         if (hasName())
0081             LongName = name();
0082         else if (hasName2())
0083             LongName = name2();
0084         else
0085             LongName.clear();
0086     }
0087     else
0088     {
0089         LongName = longname;
0090     }
0091 }
0092 
0093 QTime SkyObject::riseSetTime(const KStarsDateTime &dt, const GeoLocation *geo, bool rst, bool exact) const
0094 {
0095     // If this object does not rise or set, return an invalid time
0096     SkyPoint p = recomputeCoords(dt, geo);
0097     if (p.checkCircumpolar(geo->lat()))
0098         return QTime();
0099 
0100     //First of all, if the object is below the horizon at date/time dt, adjust the time
0101     //to bring it above the horizon
0102     KStarsDateTime dt2 = dt;
0103     dms lst(geo->GSTtoLST(dt.gst()));
0104     p.EquatorialToHorizontal(&lst, geo->lat());
0105     if (p.alt().Degrees() < 0.0)
0106     {
0107         if (p.az().Degrees() < 180.0) //object has not risen yet
0108         {
0109             dt2 = dt.addSecs(12. * 3600.); // Move forward 12 hours, to a time when it has already risen
0110         }
0111         else //object has already set
0112         {
0113             dt2 = dt.addSecs(-12. * 3600.); // Move backward 12 hours, to a time when it has not yet set
0114         }
0115     }
0116     // The addition / subtraction of 12 hours ensures that we always
0117     // compute the _closest_ rise time and the _closest_ set time to
0118     // the current time.
0119 
0120     QTime rstUt = riseSetTimeUT(dt2, geo, rst, exact);
0121     if (!rstUt.isValid())
0122         return QTime();
0123 
0124     return geo->UTtoLT(KStarsDateTime(dt2.date(), rstUt)).time();
0125 }
0126 
0127 QTime SkyObject::riseSetTimeUT(const KStarsDateTime &dt, const GeoLocation *geo, bool riseT, bool exact) const
0128 {
0129     // First trial to calculate UT
0130     QTime UT = auxRiseSetTimeUT(dt, geo, &ra(), &dec(), riseT);
0131 
0132     // We iterate once more using the calculated UT to compute again
0133     // the ra and dec for that time and hence the rise/set time.
0134     // Also, adjust the date by +/- 1 day, if necessary
0135 
0136     // By adding this +/- 1 day, we are double-checking that the
0137     // reported rise-time is the _already_ (last) risen time, and that
0138     // the reported set-time is the _future_ (next) set time
0139     //
0140     // However, issues with this are taken care of in
0141     // SkyObject::riseSetTime()
0142 
0143     KStarsDateTime dt0 = dt;
0144     dt0.setTime(UT);
0145     if (riseT && dt0 > dt)
0146     {
0147         dt0 = dt0.addDays(-1);
0148     }
0149     else if (!riseT && dt0 < dt)
0150     {
0151         dt0 = dt0.addDays(1);
0152     }
0153 
0154     SkyPoint sp = recomputeCoords(dt0, geo);
0155     UT          = auxRiseSetTimeUT(dt0, geo, &sp.ra(), &sp.dec(), riseT);
0156 
0157     if (exact)
0158     {
0159         // We iterate a second time (For the Moon the second iteration changes
0160         // aprox. 1.5 arcmin the coordinates).
0161         dt0.setTime(UT);
0162         sp = recomputeCoords(dt0, geo);
0163         UT = auxRiseSetTimeUT(dt0, geo, &sp.ra(), &sp.dec(), riseT);
0164     }
0165 
0166     return UT;
0167 }
0168 
0169 QTime SkyObject::auxRiseSetTimeUT(const KStarsDateTime &dt, const GeoLocation *geo, const dms *righta, const dms *decl,
0170                                   bool riseT) const
0171 {
0172     dms LST = auxRiseSetTimeLST(geo->lat(), righta, decl, riseT);
0173     return dt.GSTtoUT(geo->LSTtoGST(LST));
0174 }
0175 
0176 dms SkyObject::auxRiseSetTimeLST(const dms *gLat, const dms *righta, const dms *decl, bool riseT) const
0177 {
0178     dms h0   = elevationCorrection();
0179     double H = approxHourAngle(&h0, gLat, decl);
0180     dms LST;
0181 
0182     if (riseT)
0183         LST.setH(24.0 + righta->Hours() - H / 15.0);
0184     else
0185         LST.setH(righta->Hours() + H / 15.0);
0186 
0187     return LST.reduce();
0188 }
0189 
0190 dms SkyObject::riseSetTimeAz(const KStarsDateTime &dt, const GeoLocation *geo, bool riseT) const
0191 {
0192     dms Azimuth;
0193     double AltRad, AzRad;
0194     double sindec, cosdec, sinlat, coslat, sinHA, cosHA;
0195     double sinAlt, cosAlt;
0196 
0197     QTime UT           = riseSetTimeUT(dt, geo, riseT);
0198     KStarsDateTime dt0 = dt;
0199     dt0.setTime(UT);
0200     SkyPoint sp = recomputeCoords(dt0, geo);
0201 
0202     dms LST       = auxRiseSetTimeLST(geo->lat(), &sp.ra0(), &sp.dec0(), riseT);
0203     dms HourAngle = dms(LST.Degrees() - sp.ra0().Degrees());
0204 
0205     geo->lat()->SinCos(sinlat, coslat);
0206     dec().SinCos(sindec, cosdec);
0207     HourAngle.SinCos(sinHA, cosHA);
0208 
0209     sinAlt = sindec * sinlat + cosdec * coslat * cosHA;
0210     AltRad = asin(sinAlt);
0211     cosAlt = cos(AltRad);
0212 
0213     AzRad = acos((sindec - sinlat * sinAlt) / (coslat * cosAlt));
0214     if (sinHA > 0.0)
0215         AzRad = 2.0 * dms::PI - AzRad; // resolve acos() ambiguity
0216     Azimuth.setRadians(AzRad);
0217 
0218     return Azimuth;
0219 }
0220 
0221 QTime SkyObject::transitTimeUT(const KStarsDateTime &dt, const GeoLocation *geo) const
0222 {
0223     dms LST = geo->GSTtoLST(dt.gst());
0224 
0225     //dSec is the number of seconds until the object transits.
0226     dms HourAngle = dms(LST.Degrees() - ra().Degrees());
0227     int dSec      = static_cast<int>(-3600. * HourAngle.Degrees() / 15.0);
0228 
0229     //dt0 is the first guess at the transit time.
0230     KStarsDateTime dt0 = dt.addSecs(dSec);
0231     //recompute object's position at UT0 and then find transit time of this refined position
0232     SkyPoint sp = recomputeCoords(dt0, geo);
0233     HourAngle = dms(LST.Degrees() - sp.ra().Degrees());
0234     dSec      = static_cast<int>(-3600. * HourAngle.Degrees() / 15.0);
0235 
0236     return dt.addSecs(dSec).time();
0237 }
0238 
0239 QTime SkyObject::transitTime(const KStarsDateTime &dt, const GeoLocation *geo) const
0240 {
0241     return geo->UTtoLT(KStarsDateTime(dt.date(), transitTimeUT(dt, geo))).time();
0242 }
0243 
0244 dms SkyObject::transitAltitude(const KStarsDateTime &dt, const GeoLocation *geo) const
0245 {
0246     KStarsDateTime dt0 = dt;
0247     dt0.setTime(transitTimeUT(dt, geo));
0248     SkyPoint sp = recomputeCoords(dt0, geo);
0249 
0250     double delta = 90 - geo->lat()->Degrees() + sp.dec().Degrees();
0251     if (delta > 90)
0252         delta = 180 - delta;
0253     return dms(delta);
0254 }
0255 
0256 double SkyObject::approxHourAngle(const dms *h0, const dms *gLat, const dms *dec) const
0257 {
0258     double sh0 = sin(h0->radians());
0259     double r   = (sh0 - sin(gLat->radians()) * sin(dec->radians())) / (cos(gLat->radians()) * cos(dec->radians()));
0260 
0261     double H = acos(r) / dms::DegToRad;
0262 
0263     return H;
0264 }
0265 
0266 dms SkyObject::elevationCorrection(void) const
0267 {
0268     /* The atmospheric refraction at the horizon shifts altitude by
0269      * - 34 arcmin = 0.5667 degrees. This value changes if the observer
0270      * is above the horizon, or if the weather conditions change much.
0271      *
0272      * For the sun we have to add half the angular sie of the body, since
0273      * the sunset is the time the upper limb of the sun disappears below
0274      * the horizon, and dawn, when the upper part of the limb appears
0275      * over the horizon. The angular size of the sun = angular size of the
0276      * moon = 31' 59''.
0277      *
0278      * So for the sun the correction is = -34 - 16 = 50 arcmin = -0.8333
0279      *
0280      * This same correction should be applied to the moon however parallax
0281      * is important here. Meeus states that the correction should be
0282      * 0.7275 P - 34 arcmin, where P is the moon's horizontal parallax.
0283      * He proposes a mean value of 0.125 degrees if no great accuracy
0284      * is needed.
0285      */
0286 
0287     if (name() == i18n("Sun") || name() == i18n("Moon") || name() == i18n("Earth Shadow"))
0288         return dms(-0.8333);
0289     //  else if ( name() == "Moon" )
0290     //      return dms(0.125);
0291     else // All sources point-like.
0292         return dms(-0.5667);
0293 }
0294 
0295 SkyPoint SkyObject::recomputeCoords(const KStarsDateTime &dt, const GeoLocation *geo) const
0296 {
0297     // Create a clone
0298     SkyObject *c = this->clone();
0299 
0300     // compute coords of the copy for new time jd
0301     KSNumbers num(dt.djd());
0302 
0303     // Note: isSolarSystem() below should give the same result on this
0304     // and c. The only very minor reason to prefer this is so that we
0305     // have an additional layer of warnings about subclasses of
0306     // KSPlanetBase that do not implement SkyObject::clone() due to
0307     // the passing of lat and LST
0308 
0309     if (isSolarSystem() && geo)
0310     {
0311         CachingDms LST = geo->GSTtoLST(dt.gst());
0312         c->updateCoords(&num, true, geo->lat(), &LST);
0313     }
0314     else
0315     {
0316         c->updateCoords(&num);
0317     }
0318 
0319     // Transfer the coordinates into a SkyPoint
0320     SkyPoint p = *c;
0321 
0322     // Delete the clone
0323     delete c;
0324 
0325     // Return the SkyPoint
0326     return p;
0327 }
0328 
0329 SkyPoint SkyObject::recomputeHorizontalCoords(const KStarsDateTime &dt, const GeoLocation *geo) const
0330 {
0331     Q_ASSERT(geo);
0332     SkyPoint ret   = recomputeCoords(dt, geo);
0333     CachingDms LST = geo->GSTtoLST(dt.gst());
0334     ret.EquatorialToHorizontal(&LST, geo->lat());
0335     return ret;
0336 }
0337 
0338 QString SkyObject::typeName(int t)
0339 {
0340     switch (t)
0341     {
0342         case STAR:
0343             return i18n("Star");
0344         case CATALOG_STAR:
0345             return i18n("Catalog Star");
0346         case PLANET:
0347             return i18n("Planet");
0348         case OPEN_CLUSTER:
0349             return i18n("Open Cluster");
0350         case GLOBULAR_CLUSTER:
0351             return i18n("Globular Cluster");
0352         case GASEOUS_NEBULA:
0353             return i18n("Gaseous Nebula");
0354         case PLANETARY_NEBULA:
0355             return i18n("Planetary Nebula");
0356         case SUPERNOVA_REMNANT:
0357             return i18n("Supernova Remnant");
0358         case GALAXY:
0359             return i18n("Galaxy");
0360         case COMET:
0361             return i18n("Comet");
0362         case ASTEROID:
0363             return i18n("Asteroid");
0364         case CONSTELLATION:
0365             return i18n("Constellation");
0366         case MOON:
0367             return i18n("Moon");
0368         case GALAXY_CLUSTER:
0369             return i18n("Galaxy Cluster");
0370         case SATELLITE:
0371             return i18n("Satellite");
0372         case SUPERNOVA:
0373             return i18n("Supernova");
0374         case RADIO_SOURCE:
0375             return i18n("Radio Source");
0376         case ASTERISM:
0377             return i18n("Asterism");
0378         case DARK_NEBULA:
0379             return i18n("Dark Nebula");
0380         case QUASAR:
0381             return i18n("Quasar");
0382         case MULT_STAR:
0383             return i18n("Multiple Star");
0384         default:
0385             return i18n("Unknown Type");
0386     }
0387 }
0388 
0389 QString SkyObject::typeName() const
0390 {
0391     return typeName(Type);
0392 }
0393 
0394 QString SkyObject::messageFromTitle(const QString &imageTitle) const
0395 {
0396     QString message = imageTitle;
0397 
0398     //HST Image
0399     if (imageTitle == i18n("Show HST Image") || imageTitle.contains("HST"))
0400     {
0401         message = i18n("%1: Hubble Space Telescope, operated by STScI for NASA [public domain]", longname());
0402 
0403         //Spitzer Image
0404     }
0405     else if (imageTitle.contains(i18n("Show Spitzer Image")))
0406     {
0407         message = i18n("%1: Spitzer Space Telescope, courtesy NASA/JPL-Caltech [public domain]", longname());
0408 
0409         //SEDS Image
0410     }
0411     else if (imageTitle == i18n("Show SEDS Image"))
0412     {
0413         message = i18n("%1: SEDS, http://www.seds.org [free for non-commercial use]", longname());
0414 
0415         //Kitt Peak AOP Image
0416     }
0417     else if (imageTitle == i18n("Show KPNO AOP Image"))
0418     {
0419         message = i18n("%1: Advanced Observing Program at Kitt Peak National Observatory [free for non-commercial use; "
0420                        "no physical reproductions]",
0421                        longname());
0422 
0423         //NOAO Image
0424     }
0425     else if (imageTitle.contains(i18n("Show NOAO Image")))
0426     {
0427         message =
0428             i18n("%1: National Optical Astronomy Observatories and AURA [free for non-commercial use]", longname());
0429 
0430         //VLT Image
0431     }
0432     else if (imageTitle.contains("VLT"))
0433     {
0434         message = i18n("%1: Very Large Telescope, operated by the European Southern Observatory [free for "
0435                        "non-commercial use; no reproductions]",
0436                        longname());
0437 
0438         //All others
0439     }
0440     else if (imageTitle.startsWith(i18n("Show")))
0441     {
0442         message = imageTitle.mid(imageTitle.indexOf(" ") + 1); //eat first word, "Show"
0443         message = longname() + ": " + message;
0444     }
0445 
0446     return message;
0447 }
0448 
0449 QString SkyObject::labelString() const
0450 {
0451     return translatedName();
0452 }
0453 
0454 double SkyObject::labelOffset() const
0455 {
0456     return SkyLabeler::ZoomOffset();
0457 }
0458 
0459 SkyObject::UID SkyObject::getUID() const
0460 {
0461     return invalidUID;
0462 }