1 /*
2     SPDX-FileCopyrightText: 2001 Jason Harris <jharris@30doradus.org>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "skyobject.h"
8 
9 #include "geolocation.h"
10 #include "ksnumbers.h"
11 #include "kspaths.h"
12 #ifdef KSTARS_LITE
13 #include "skymaplite.h"
14 #else
15 #include "kspopupmenu.h"
16 #include "skymap.h"
17 #endif
18 #include "kstarsdata.h"
19 #include "Options.h"
20 #include "starobject.h"
21 #include "skycomponents/skylabeler.h"
22 
23 
24 QString SkyObject::emptyString;
25 QString SkyObject::unnamedString       = QString(I18N_NOOP("unnamed"));
26 QString SkyObject::unnamedObjectString = QString(I18N_NOOP("unnamed object"));
27 QString SkyObject::starString          = QString("star");
28 
29 const SkyObject::UID SkyObject::invalidUID   = ~0;
30 const SkyObject::UID SkyObject::UID_STAR     = 0;
31 const SkyObject::UID SkyObject::UID_GALAXY   = 1;
32 const SkyObject::UID SkyObject::UID_DEEPSKY  = 2;
33 const SkyObject::UID SkyObject::UID_SOLARSYS = 3;
34 
SkyObject(int t,dms r,dms d,float m,const QString & n,const QString & n2,const QString & lname)35 SkyObject::SkyObject(int t, dms r, dms d, float m, const QString &n, const QString &n2, const QString &lname)
36     : SkyPoint(r, d)
37 {
38     setType(t);
39     sortMagnitude = m;
40     setName(n);
41     setName2(n2);
42     setLongName(lname);
43 }
44 
SkyObject(int t,double r,double d,float m,const QString & n,const QString & n2,const QString & lname)45 SkyObject::SkyObject(int t, double r, double d, float m, const QString &n, const QString &n2, const QString &lname)
46     : SkyPoint(r, d)
47 {
48     setType(t);
49     sortMagnitude = m;
50     setName(n);
51     setName2(n2);
52     setLongName(lname);
53 }
54 
clone() const55 SkyObject *SkyObject::clone() const
56 {
57     Q_ASSERT(typeid(this) == typeid(static_cast<const SkyObject *>(this))); // Ensure we are not slicing a derived class
58     return new SkyObject(*this);
59 }
60 
showPopupMenu(KSPopupMenu * pmenu,const QPoint & pos)61 void SkyObject::showPopupMenu(KSPopupMenu *pmenu, const QPoint &pos)
62 {
63 #if defined(KSTARS_LITE)
64     Q_UNUSED(pos)
65     Q_UNUSED(pmenu);
66 #else
67     initPopupMenu(pmenu);
68     pmenu->popup(pos);
69 #endif
70 }
71 
initPopupMenu(KSPopupMenu * pmenu)72 void SkyObject::initPopupMenu(KSPopupMenu *pmenu)
73 {
74 #ifdef KSTARS_LITE
75     Q_UNUSED(pmenu)
76 #else
77     pmenu->createEmptyMenu(this);
78 #endif
79 }
80 
setLongName(const QString & longname)81 void SkyObject::setLongName(const QString &longname)
82 {
83     if (longname.isEmpty())
84     {
85         if (hasName())
86             LongName = name();
87         else if (hasName2())
88             LongName = name2();
89         else
90             LongName.clear();
91     }
92     else
93     {
94         LongName = longname;
95     }
96 }
97 
riseSetTime(const KStarsDateTime & dt,const GeoLocation * geo,bool rst,bool exact) const98 QTime SkyObject::riseSetTime(const KStarsDateTime &dt, const GeoLocation *geo, bool rst, bool exact) const
99 {
100     // If this object does not rise or set, return an invalid time
101     SkyPoint p = recomputeCoords(dt, geo);
102     if (p.checkCircumpolar(geo->lat()))
103         return QTime();
104 
105     //First of all, if the object is below the horizon at date/time dt, adjust the time
106     //to bring it above the horizon
107     KStarsDateTime dt2 = dt;
108     dms lst(geo->GSTtoLST(dt.gst()));
109     p.EquatorialToHorizontal(&lst, geo->lat());
110     if (p.alt().Degrees() < 0.0)
111     {
112         if (p.az().Degrees() < 180.0) //object has not risen yet
113         {
114             dt2 = dt.addSecs(12. * 3600.); // Move forward 12 hours, to a time when it has already risen
115         }
116         else //object has already set
117         {
118             dt2 = dt.addSecs(-12. * 3600.); // Move backward 12 hours, to a time when it has not yet set
119         }
120     }
121     // The addition / subtraction of 12 hours ensures that we always
122     // compute the _closest_ rise time and the _closest_ set time to
123     // the current time.
124 
125     QTime rstUt = riseSetTimeUT(dt2, geo, rst, exact);
126     if (!rstUt.isValid())
127         return QTime();
128 
129     return geo->UTtoLT(KStarsDateTime(dt2.date(), rstUt)).time();
130 }
131 
riseSetTimeUT(const KStarsDateTime & dt,const GeoLocation * geo,bool riseT,bool exact) const132 QTime SkyObject::riseSetTimeUT(const KStarsDateTime &dt, const GeoLocation *geo, bool riseT, bool exact) const
133 {
134     // First trial to calculate UT
135     QTime UT = auxRiseSetTimeUT(dt, geo, &ra(), &dec(), riseT);
136 
137     // We iterate once more using the calculated UT to compute again
138     // the ra and dec for that time and hence the rise/set time.
139     // Also, adjust the date by +/- 1 day, if necessary
140 
141     // By adding this +/- 1 day, we are double-checking that the
142     // reported rise-time is the _already_ (last) risen time, and that
143     // the reported set-time is the _future_ (next) set time
144     //
145     // However, issues with this are taken care of in
146     // SkyObject::riseSetTime()
147 
148     KStarsDateTime dt0 = dt;
149     dt0.setTime(UT);
150     if (riseT && dt0 > dt)
151     {
152         dt0 = dt0.addDays(-1);
153     }
154     else if (!riseT && dt0 < dt)
155     {
156         dt0 = dt0.addDays(1);
157     }
158 
159     SkyPoint sp = recomputeCoords(dt0, geo);
160     UT          = auxRiseSetTimeUT(dt0, geo, &sp.ra(), &sp.dec(), riseT);
161 
162     if (exact)
163     {
164         // We iterate a second time (For the Moon the second iteration changes
165         // aprox. 1.5 arcmin the coordinates).
166         dt0.setTime(UT);
167         sp = recomputeCoords(dt0, geo);
168         UT = auxRiseSetTimeUT(dt0, geo, &sp.ra(), &sp.dec(), riseT);
169     }
170 
171     return UT;
172 }
173 
auxRiseSetTimeUT(const KStarsDateTime & dt,const GeoLocation * geo,const dms * righta,const dms * decl,bool riseT) const174 QTime SkyObject::auxRiseSetTimeUT(const KStarsDateTime &dt, const GeoLocation *geo, const dms *righta, const dms *decl,
175                                   bool riseT) const
176 {
177     dms LST = auxRiseSetTimeLST(geo->lat(), righta, decl, riseT);
178     return dt.GSTtoUT(geo->LSTtoGST(LST));
179 }
180 
auxRiseSetTimeLST(const dms * gLat,const dms * righta,const dms * decl,bool riseT) const181 dms SkyObject::auxRiseSetTimeLST(const dms *gLat, const dms *righta, const dms *decl, bool riseT) const
182 {
183     dms h0   = elevationCorrection();
184     double H = approxHourAngle(&h0, gLat, decl);
185     dms LST;
186 
187     if (riseT)
188         LST.setH(24.0 + righta->Hours() - H / 15.0);
189     else
190         LST.setH(righta->Hours() + H / 15.0);
191 
192     return LST.reduce();
193 }
194 
riseSetTimeAz(const KStarsDateTime & dt,const GeoLocation * geo,bool riseT) const195 dms SkyObject::riseSetTimeAz(const KStarsDateTime &dt, const GeoLocation *geo, bool riseT) const
196 {
197     dms Azimuth;
198     double AltRad, AzRad;
199     double sindec, cosdec, sinlat, coslat, sinHA, cosHA;
200     double sinAlt, cosAlt;
201 
202     QTime UT           = riseSetTimeUT(dt, geo, riseT);
203     KStarsDateTime dt0 = dt;
204     dt0.setTime(UT);
205     SkyPoint sp = recomputeCoords(dt0, geo);
206 
207     dms LST       = auxRiseSetTimeLST(geo->lat(), &sp.ra0(), &sp.dec0(), riseT);
208     dms HourAngle = dms(LST.Degrees() - sp.ra0().Degrees());
209 
210     geo->lat()->SinCos(sinlat, coslat);
211     dec().SinCos(sindec, cosdec);
212     HourAngle.SinCos(sinHA, cosHA);
213 
214     sinAlt = sindec * sinlat + cosdec * coslat * cosHA;
215     AltRad = asin(sinAlt);
216     cosAlt = cos(AltRad);
217 
218     AzRad = acos((sindec - sinlat * sinAlt) / (coslat * cosAlt));
219     if (sinHA > 0.0)
220         AzRad = 2.0 * dms::PI - AzRad; // resolve acos() ambiguity
221     Azimuth.setRadians(AzRad);
222 
223     return Azimuth;
224 }
225 
transitTimeUT(const KStarsDateTime & dt,const GeoLocation * geo) const226 QTime SkyObject::transitTimeUT(const KStarsDateTime &dt, const GeoLocation *geo) const
227 {
228     dms LST = geo->GSTtoLST(dt.gst());
229 
230     //dSec is the number of seconds until the object transits.
231     dms HourAngle = dms(LST.Degrees() - ra().Degrees());
232     int dSec      = static_cast<int>(-3600. * HourAngle.Degrees() / 15.0);
233 
234     //dt0 is the first guess at the transit time.
235     KStarsDateTime dt0 = dt.addSecs(dSec);
236     //recompute object's position at UT0 and then find transit time of this refined position
237     SkyPoint sp = recomputeCoords(dt0, geo);
238     HourAngle = dms(LST.Degrees() - sp.ra().Degrees());
239     dSec      = static_cast<int>(-3600. * HourAngle.Degrees() / 15.0);
240 
241     return dt.addSecs(dSec).time();
242 }
243 
transitTime(const KStarsDateTime & dt,const GeoLocation * geo) const244 QTime SkyObject::transitTime(const KStarsDateTime &dt, const GeoLocation *geo) const
245 {
246     return geo->UTtoLT(KStarsDateTime(dt.date(), transitTimeUT(dt, geo))).time();
247 }
248 
transitAltitude(const KStarsDateTime & dt,const GeoLocation * geo) const249 dms SkyObject::transitAltitude(const KStarsDateTime &dt, const GeoLocation *geo) const
250 {
251     KStarsDateTime dt0 = dt;
252     dt0.setTime(transitTimeUT(dt, geo));
253     SkyPoint sp = recomputeCoords(dt0, geo);
254 
255     double delta = 90 - geo->lat()->Degrees() + sp.dec().Degrees();
256     if (delta > 90)
257         delta = 180 - delta;
258     return dms(delta);
259 }
260 
approxHourAngle(const dms * h0,const dms * gLat,const dms * dec) const261 double SkyObject::approxHourAngle(const dms *h0, const dms *gLat, const dms *dec) const
262 {
263     double sh0 = sin(h0->radians());
264     double r   = (sh0 - sin(gLat->radians()) * sin(dec->radians())) / (cos(gLat->radians()) * cos(dec->radians()));
265 
266     double H = acos(r) / dms::DegToRad;
267 
268     return H;
269 }
270 
elevationCorrection(void) const271 dms SkyObject::elevationCorrection(void) const
272 {
273     /* The atmospheric refraction at the horizon shifts altitude by
274      * - 34 arcmin = 0.5667 degrees. This value changes if the observer
275      * is above the horizon, or if the weather conditions change much.
276      *
277      * For the sun we have to add half the angular sie of the body, since
278      * the sunset is the time the upper limb of the sun disappears below
279      * the horizon, and dawn, when the upper part of the limb appears
280      * over the horizon. The angular size of the sun = angular size of the
281      * moon = 31' 59''.
282      *
283      * So for the sun the correction is = -34 - 16 = 50 arcmin = -0.8333
284      *
285      * This same correction should be applied to the moon however parallax
286      * is important here. Meeus states that the correction should be
287      * 0.7275 P - 34 arcmin, where P is the moon's horizontal parallax.
288      * He proposes a mean value of 0.125 degrees if no great accuracy
289      * is needed.
290      */
291 
292     if (name() == i18n("Sun") || name() == i18n("Moon") || name() == i18n("Earth Shadow"))
293         return dms(-0.8333);
294     //	else if ( name() == "Moon" )
295     //		return dms(0.125);
296     else // All sources point-like.
297         return dms(-0.5667);
298 }
299 
recomputeCoords(const KStarsDateTime & dt,const GeoLocation * geo) const300 SkyPoint SkyObject::recomputeCoords(const KStarsDateTime &dt, const GeoLocation *geo) const
301 {
302     // Create a clone
303     SkyObject *c = this->clone();
304 
305     // compute coords of the copy for new time jd
306     KSNumbers num(dt.djd());
307 
308     // Note: isSolarSystem() below should give the same result on this
309     // and c. The only very minor reason to prefer this is so that we
310     // have an additional layer of warnings about subclasses of
311     // KSPlanetBase that do not implement SkyObject::clone() due to
312     // the passing of lat and LST
313 
314     if (isSolarSystem() && geo)
315     {
316         CachingDms LST = geo->GSTtoLST(dt.gst());
317         c->updateCoords(&num, true, geo->lat(), &LST);
318     }
319     else
320     {
321         c->updateCoords(&num);
322     }
323 
324     // Transfer the coordinates into a SkyPoint
325     SkyPoint p = *c;
326 
327     // Delete the clone
328     delete c;
329 
330     // Return the SkyPoint
331     return p;
332 }
333 
recomputeHorizontalCoords(const KStarsDateTime & dt,const GeoLocation * geo) const334 SkyPoint SkyObject::recomputeHorizontalCoords(const KStarsDateTime &dt, const GeoLocation *geo) const
335 {
336     Q_ASSERT(geo);
337     SkyPoint ret   = recomputeCoords(dt, geo);
338     CachingDms LST = geo->GSTtoLST(dt.gst());
339     ret.EquatorialToHorizontal(&LST, geo->lat());
340     return ret;
341 }
342 
typeName(int t)343 QString SkyObject::typeName(int t)
344 {
345     switch (t)
346     {
347         case STAR:
348             return i18n("Star");
349         case CATALOG_STAR:
350             return i18n("Catalog Star");
351         case PLANET:
352             return i18n("Planet");
353         case OPEN_CLUSTER:
354             return i18n("Open Cluster");
355         case GLOBULAR_CLUSTER:
356             return i18n("Globular Cluster");
357         case GASEOUS_NEBULA:
358             return i18n("Gaseous Nebula");
359         case PLANETARY_NEBULA:
360             return i18n("Planetary Nebula");
361         case SUPERNOVA_REMNANT:
362             return i18n("Supernova Remnant");
363         case GALAXY:
364             return i18n("Galaxy");
365         case COMET:
366             return i18n("Comet");
367         case ASTEROID:
368             return i18n("Asteroid");
369         case CONSTELLATION:
370             return i18n("Constellation");
371         case MOON:
372             return i18n("Moon");
373         case GALAXY_CLUSTER:
374             return i18n("Galaxy Cluster");
375         case SATELLITE:
376             return i18n("Satellite");
377         case SUPERNOVA:
378             return i18n("Supernova");
379         case RADIO_SOURCE:
380             return i18n("Radio Source");
381         case ASTERISM:
382             return i18n("Asterism");
383         case DARK_NEBULA:
384             return i18n("Dark Nebula");
385         case QUASAR:
386             return i18n("Quasar");
387         case MULT_STAR:
388             return i18n("Multiple Star");
389         default:
390             return i18n("Unknown Type");
391     }
392 }
393 
typeName() const394 QString SkyObject::typeName() const
395 {
396     return typeName(Type);
397 }
398 
messageFromTitle(const QString & imageTitle) const399 QString SkyObject::messageFromTitle(const QString &imageTitle) const
400 {
401     QString message = imageTitle;
402 
403     //HST Image
404     if (imageTitle == i18n("Show HST Image") || imageTitle.contains("HST"))
405     {
406         message = i18n("%1: Hubble Space Telescope, operated by STScI for NASA [public domain]", longname());
407 
408         //Spitzer Image
409     }
410     else if (imageTitle.contains(i18n("Show Spitzer Image")))
411     {
412         message = i18n("%1: Spitzer Space Telescope, courtesy NASA/JPL-Caltech [public domain]", longname());
413 
414         //SEDS Image
415     }
416     else if (imageTitle == i18n("Show SEDS Image"))
417     {
418         message = i18n("%1: SEDS, http://www.seds.org [free for non-commercial use]", longname());
419 
420         //Kitt Peak AOP Image
421     }
422     else if (imageTitle == i18n("Show KPNO AOP Image"))
423     {
424         message = i18n("%1: Advanced Observing Program at Kitt Peak National Observatory [free for non-commercial use; "
425                        "no physical reproductions]",
426                        longname());
427 
428         //NOAO Image
429     }
430     else if (imageTitle.contains(i18n("Show NOAO Image")))
431     {
432         message =
433             i18n("%1: National Optical Astronomy Observatories and AURA [free for non-commercial use]", longname());
434 
435         //VLT Image
436     }
437     else if (imageTitle.contains("VLT"))
438     {
439         message = i18n("%1: Very Large Telescope, operated by the European Southern Observatory [free for "
440                        "non-commercial use; no reproductions]",
441                        longname());
442 
443         //All others
444     }
445     else if (imageTitle.startsWith(i18n("Show")))
446     {
447         message = imageTitle.mid(imageTitle.indexOf(" ") + 1); //eat first word, "Show"
448         message = longname() + ": " + message;
449     }
450 
451     return message;
452 }
453 
labelString() const454 QString SkyObject::labelString() const
455 {
456     return translatedName();
457 }
458 
labelOffset() const459 double SkyObject::labelOffset() const
460 {
461     return SkyLabeler::ZoomOffset();
462 }
463 
getUID() const464 SkyObject::UID SkyObject::getUID() const
465 {
466     return invalidUID;
467 }
468