1 /*
2     SPDX-FileCopyrightText: 2012 Samikshan Bairagya <samikshan@gmail.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "skyobjitem.h"
8 
9 #include "catalogobject.h"
10 #include "ksfilereader.h"
11 #include "kspaths.h"
12 #include "ksplanetbase.h"
13 #include "kstarsdata.h"
14 #include "ksutils.h"
15 
SkyObjItem(SkyObject * so)16 SkyObjItem::SkyObjItem(SkyObject *so)
17     : m_Name(so->name()), m_LongName(so->longname()), m_TypeName(so->typeName()), m_So(so)
18 {
19     switch (so->type())
20     {
21         case SkyObject::PLANET:
22         case SkyObject::MOON:
23             m_Type = Planet;
24             break;
25         case SkyObject::STAR:
26         case SkyObject::CATALOG_STAR:
27         case SkyObject::MULT_STAR:
28             m_Type = Star;
29             break;
30         case SkyObject::CONSTELLATION:
31         case SkyObject::ASTERISM:
32             m_Type = Constellation;
33             break;
34         case SkyObject::GALAXY:
35             m_Type = Galaxy;
36             break;
37         case SkyObject::OPEN_CLUSTER:
38         case SkyObject::GLOBULAR_CLUSTER:
39         case SkyObject::GALAXY_CLUSTER:
40             m_Type = Cluster;
41             break;
42         case SkyObject::PLANETARY_NEBULA:
43         case SkyObject::SUPERNOVA_REMNANT:
44         case SkyObject::GASEOUS_NEBULA:
45         case SkyObject::DARK_NEBULA:
46             m_Type = Nebula;
47             break;
48         case SkyObject::SUPERNOVA:
49             m_Type = Supernova;
50     }
51 
52     setPosition(m_So);
53 }
54 
data(int role)55 QVariant SkyObjItem::data(int role)
56 {
57     switch (role)
58     {
59         case DispNameRole:
60             return getDescName();
61         case DispImageRole:
62             return getImageURL(true);
63         case DispSummaryRole:
64             return getSummary(true);
65         case CategoryRole:
66             return getType();
67         case CategoryNameRole:
68             return getTypeName();
69         default:
70             return QVariant();
71     }
72 }
73 
74 ///Moved to skyobjlistmodel.cpp
75 /*
76 QHash<int, QByteArray> SkyObjItem::roleNames() const
77 {
78     QHash<int, QByteArray> roles;
79     roles[DispNameRole] = "dispName";
80     roles[CategoryRole] = "type";
81     roles[CategoryNameRole] = "typeName";
82     return roles;
83 }
84 */
85 
setPosition(SkyObject * so)86 void SkyObjItem::setPosition(SkyObject *so)
87 {
88     double altitude;
89     dms azimuth;
90     if (so->type() == SkyObject::SATELLITE)
91     {
92         altitude = so->alt().Degrees();
93         azimuth  = so->az();
94     }
95     else
96     {
97         KStarsData *data  = KStarsData::Instance();
98         KStarsDateTime ut = data->geo()->LTtoUT(
99             KStarsDateTime(QDateTime::currentDateTime().toLocalTime()));
100         SkyPoint sp = so->recomputeCoords(ut, data->geo());
101 
102         //check altitude of object at this time.
103         sp.EquatorialToHorizontal(data->lst(), data->geo()->lat());
104         altitude = sp.alt().Degrees();
105         azimuth  = sp.az();
106     }
107 
108     double rounded_altitude = (int)(altitude / 5.0) * 5.0;
109 
110     if (rounded_altitude <= 0)
111         m_Position = "<span style='color:red'>" +
112                      xi18n("NOT VISIBLE: About %1 degrees below the %2 horizon",
113                            -rounded_altitude, KSUtils::toDirectionString(azimuth)) +
114                      "</span>";
115     else
116         m_Position = "<span style='color:yellow'>" +
117                      xi18n("Now visible: About %1 degrees above the %2 horizon",
118                            rounded_altitude, KSUtils::toDirectionString(azimuth)) +
119                      "</span>";
120 }
121 
findImage(const QString & prefix,const SkyObject & obj,const QString & suffix)122 QString findImage(const QString &prefix, const SkyObject &obj, const QString &suffix)
123 {
124     static const auto base =
125         KSPaths::writableLocation(QStandardPaths::AppDataLocation);
126     QDirIterator search(
127         base,
128         QStringList() << prefix + obj.name().toLower().remove(' ').remove('/') + suffix,
129         QDir::Files, QDirIterator::Subdirectories);
130 
131     return search.hasNext() ? QUrl::fromLocalFile(search.next()).url() : "";
132 }
getImageURL(bool preferThumb) const133 QString SkyObjItem::getImageURL(bool preferThumb) const
134 {
135     const auto &thumbURL = findImage("thumb-", *m_So, ".png");
136 
137     const auto &fullSizeURL = findImage("image-", *m_So, ".png");
138     const auto &wikiImageURL =
139         QUrl::fromLocalFile(KSPaths::locate(QStandardPaths::AppDataLocation,
140                                             "descriptions/wikiImage-" +
141                                                 m_So->name().toLower().remove(' ') +
142                                                 ".png"))
143             .url();
144     QString XPlanetURL =
145         QUrl::fromLocalFile(KSPaths::locate(QStandardPaths::AppDataLocation,
146                                             "xplanet/" + m_So->name() + ".png"))
147             .url();
148 
149     //First try to return the preferred file
150     if (!thumbURL.isEmpty() && preferThumb)
151         return thumbURL;
152     if (!fullSizeURL.isEmpty() && (!preferThumb))
153         return fullSizeURL;
154 
155     //If that fails, try to return the large image first, then the thumb, and then if it is a planet, the xplanet image. Finally if all else fails, the wiki image.
156     QString fname = fullSizeURL;
157 
158     if (fname.isEmpty())
159     {
160         fname = thumbURL;
161     }
162     if (fname.isEmpty() && m_Type == Planet)
163     {
164         fname = XPlanetURL;
165     }
166     if (fname.isEmpty())
167     {
168         fname = wikiImageURL;
169     }
170     return fname;
171 }
172 
getSummary(bool includeDescription) const173 QString SkyObjItem::getSummary(bool includeDescription) const
174 {
175     if (includeDescription)
176     {
177         QString description = loadObjectDescription();
178         if(description.indexOf(".") > 0) //This will shorten the description in the list to just a sentence, whereas in the larger space of the Object Information Summary, it is a full paragraph.
179            return m_So->typeName() + "<BR>" + getRADE() + "<BR>" + getAltAz() + "<BR><BR>" + description.left(description.indexOf(".") + 1);
180         else
181             return m_So->typeName() + "<BR>" + getRADE() + "<BR>" + getAltAz() + "<BR><BR>" + description;
182     }
183     else
184         return m_So->typeName() + "<BR>" + getRADE() + "<BR>" + getAltAz();
185 }
186 
getSurfaceBrightness() const187 QString SkyObjItem::getSurfaceBrightness() const
188 {
189     /** Surface Brightness is applicable only for extended light sources like
190       * Deep-Sky Objects. Here we use the formula SB = m + log10(a*b/4)
191       * where m is the magnitude of the sky-object. a and b are the major and minor
192       * axis lengths of the objects respectively in arcminutes. SB is the surface
193       * brightness obtained in mag * arcminutes^-2
194       */
195 
196     auto *dso = dynamic_cast<CatalogObject*>(m_So);
197     float SB           = m_So->mag();
198 
199     if (dso != nullptr)
200         SB += 2.5 * log10(dso->a() * dso->b() / 4);
201 
202     switch (getType())
203     {
204         case Galaxy:
205         case Nebula:
206             return QLocale().toString(SB, 'f', 2) + "<BR>   (mag/arcmin^2)";
207         default:
208             return QString(" --"); // Not applicable for other sky-objects
209     }
210 }
211 
getSize() const212 QString SkyObjItem::getSize() const
213 {
214     switch (getType())
215     {
216         case Galaxy:
217         case Cluster:
218         case Nebula:
219             return QLocale().toString(((CatalogObject *)m_So)->a(), 'f', 2) + "\"";
220         case Planet:
221             return QLocale().toString(((KSPlanetBase *)m_So)->angSize(), 'f', 2) + "\"";
222         default:
223             return QString(" --");
224     }
225 }
226 
loadObjectDescription() const227 inline QString SkyObjItem::loadObjectDescription() const
228 {
229     QFile file;
230     QString fname = "description-" + getName().toLower().remove(' ') + ".html";
231     //determine filename in local user KDE directory tree.
232     file.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("descriptions/" + fname));
233 
234     if (file.exists())
235     {
236         if (file.open(QIODevice::ReadOnly))
237         {
238             QTextStream in(&file);
239             QString line;
240             line = in.readLine(); //This should only read the description since the source is on the next line
241             file.close();
242             return line;
243         }
244     }
245     return getTypeName();
246 }
247 
getRADE() const248 QString SkyObjItem::getRADE() const
249 {
250     return "RA: " + m_So->ra().toHMSString() + "<BR>DE: " + m_So->dec().toDMSString();
251 }
252 
getAltAz() const253 QString SkyObjItem::getAltAz() const
254 {
255     return "Alt: " + QString::number(m_So->alt().Degrees(), 'f', 2) +
256            ", Az: " + QString::number(m_So->az().Degrees(), 'f', 2);
257 }
258 
getMagnitude() const259 float SkyObjItem::getMagnitude() const
260 {
261     return m_So->mag();
262 }
263