1 /*
2 SPDX-FileCopyrightText: 2005 Jason Harris <kstars@30doradus.org>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "cometscomponent.h"
8
9 #ifndef KSTARS_LITE
10 #include "kstars.h"
11 #endif
12 #include "ksfilereader.h"
13 #include "kspaths.h"
14 #include "kstarsdata.h"
15 #include "ksutils.h"
16 #include "ksnotification.h"
17 #include "kstars_debug.h"
18 #ifndef KSTARS_LITE
19 #include "skymap.h"
20 #else
21 #include "kstarslite.h"
22 #endif
23 #include "Options.h"
24 #include "skylabeler.h"
25 #include "skypainter.h"
26 #include "solarsystemcomposite.h"
27 #include "auxiliary/filedownloader.h"
28 #include "auxiliary/kspaths.h"
29 #include "projections/projector.h"
30 #include "skyobjects/kscomet.h"
31
32 #include <QFile>
33 #include <QHttpMultiPart>
34 #include <QPen>
35 #include <QStandardPaths>
36
37 #include <cmath>
38
CometsComponent(SolarSystemComposite * parent)39 CometsComponent::CometsComponent(SolarSystemComposite *parent)
40 : SolarSystemListComponent(parent)
41 {
42 loadData();
43 }
44
selected()45 bool CometsComponent::selected()
46 {
47 return Options::showComets();
48 }
49
50 /*
51 * @short Initialize the comets list.
52 * Reads in the comets data from the comets.dat file.
53 *
54 * Populate the list of Comets from the data file.
55 * The data file is a CSV file with the following columns :
56 * @li 1 full name [string]
57 * @li 2 modified julian day of orbital elements [int]
58 * @li 3 perihelion distance in AU [double]
59 * @li 4 eccentricity of orbit [double]
60 * @li 5 inclination angle of orbit in degrees [double]
61 * @li 6 argument of perihelion in degrees [double]
62 * @li 7 longitude of the ascending node in degrees [double]
63 * @li 8 time of perihelion passage (YYYYMMDD.DDD) [double]
64 * @li 9 orbit solution ID [string]
65 * @li 10 Near-Earth Object (NEO) flag [bool]
66 * @li 11 comet total magnitude parameter [float]
67 * @li 12 comet nuclear magnitude parameter [float]
68 * @li 13 object diameter (from equivalent sphere) [float]
69 * @li 14 object bi/tri-axial ellipsoid dimensions [string]
70 * @li 15 geometric albedo [float]
71 * @li 16 rotation period [float]
72 * @li 17 orbital period [float]
73 * @li 18 earth minimum orbit intersection distance [double]
74 * @li 19 orbit classification [string]
75 * @li 20 comet total magnitude slope parameter
76 * @li 21 comet nuclear magnitude slope parameter
77 * @note See KSComet constructor for more details.
78 */
loadData()79 void CometsComponent::loadData()
80 {
81 QString name, orbit_class;
82
83 emitProgressText(i18n("Loading comets"));
84 qCInfo(KSTARS) << "Loading comets";
85
86 qDeleteAll(m_ObjectList);
87 m_ObjectList.clear();
88
89 objectNames(SkyObject::COMET).clear();
90 objectLists(SkyObject::COMET).clear();
91
92 QString file_name = KSPaths::locate(QStandardPaths::AppDataLocation, QString("cometels.json.gz"));
93
94 try
95 {
96 KSUtils::MPCParser com_parser(file_name);
97 com_parser.for_each(
98 [&](const auto & get)
99 {
100 KSComet *com = nullptr;
101 name = get("Designation_and_name").toString();
102
103 int perihelion_year, perihelion_month, perihelion_day, perihelion_hour, perihelion_minute, perihelion_second;
104
105 // Perihelion Distance in AU
106 double perihelion_distance = get("Perihelion_dist").toDouble();
107 // Orbital Eccentricity
108 double eccentricity = get("e").toDouble();
109 // Argument of perihelion, J2000.0 (degrees)
110 double perihelion_argument = get("Peri").toDouble();
111 // Longitude of the ascending node, J2000.0 (degrees)
112 double ascending_node = get("Node").toDouble();
113 // Inclination in degrees, J2000.0 (degrees)
114 double inclination = get("i").toDouble();
115
116 // Perihelion Date
117 perihelion_year = get("Year_of_perihelion").toInt();
118 perihelion_month = get("Month_of_perihelion").toInt();
119 // Stored as double in MPC
120 double peri_day = get("Day_of_perihelion").toDouble();
121 perihelion_day = static_cast<int>(peri_day);
122 double peri_hour = (peri_day - perihelion_day) * 24;
123 perihelion_hour = static_cast<int>(peri_hour);
124 perihelion_minute = static_cast<int>((peri_hour - perihelion_hour) * 60);
125 perihelion_second = ( (( peri_hour - perihelion_hour) * 60) - perihelion_minute) * 60;
126
127 long double Tp = KStarsDateTime(QDate(perihelion_year, perihelion_month, perihelion_day),
128 QTime(perihelion_hour, perihelion_minute, perihelion_second)).djd();
129
130 // Orbit type
131 orbit_class = get("Orbit_type").toString();
132 double absolute_magnitude = get("H").toDouble();
133 double slope_parameter = get("G").toDouble();
134
135 com = new KSComet(name,
136 QString(),
137 perihelion_distance,
138 eccentricity,
139 dms(inclination),
140 dms(perihelion_argument),
141 dms(ascending_node),
142 Tp,
143 absolute_magnitude,
144 101.0,
145 slope_parameter,
146 101.0);
147
148 com->setOrbitClass(orbit_class);
149 com->setAngularSize(0.005);
150 appendListObject(com);
151
152 // Add *short* name to the list of object names
153 objectNames(SkyObject::COMET).append(com->name());
154 objectLists(SkyObject::COMET).append(QPair<QString, const SkyObject *>(com->name(), com));
155 });
156 }
157 catch (const std::runtime_error)
158 {
159 qCInfo(KSTARS) << "Loading comets failed.";
160 qCInfo(KSTARS) << " -> was trying to read " + file_name;
161 return;
162 }
163 }
164
165 // Used for JPL Data
166 // DO NOT REMOVE, we can revert to JPL at any time.
167 //void CometsComponent::loadData()
168 //{
169 // QString name, orbit_id, orbit_class, dimensions;
170
171 // emitProgressText(i18n("Loading comets"));
172 // qCInfo(KSTARS) << "Loading comets";
173
174 // qDeleteAll(m_ObjectList);
175 // m_ObjectList.clear();
176
177 // objectNames(SkyObject::COMET).clear();
178 // objectLists(SkyObject::COMET).clear();
179
180 // QString file_name =
181 // KSPaths::locate(QStandardPaths::AppDataLocation, QString("comets.dat"));
182
183 // try
184 // {
185 // KSUtils::JPLParser com_parser(file_name);
186 // com_parser.for_each(
187 // [&](const auto &get)
188 // {
189 // KSComet *com = nullptr;
190 // name = get("full_name").toString();
191 // name = name.trimmed();
192 // bool neo;
193 // double q, e, dble_i, dble_w, dble_N, Tp, earth_moid;
194 // float M1, M2, K1, K2, diameter, albedo, rot_period, period;
195 // q = get("q").toString().toDouble();
196 // e = get("e").toString().toDouble();
197 // dble_i = get("i").toString().toDouble();
198 // dble_w = get("w").toString().toDouble();
199 // dble_N = get("om").toString().toDouble();
200 // Tp = get("tp").toString().toDouble();
201 // orbit_id = get("orbit_id").toString();
202 // neo = get("neo").toString() == "Y";
203
204 // if (get("M1").toString().toFloat() == 0.0)
205 // M1 = 101.0;
206 // else
207 // M1 = get("M1").toString().toFloat();
208
209 // if (get("M2").toString().toFloat() == 0.0)
210 // M2 = 101.0;
211 // else
212 // M2 = get("M2").toString().toFloat();
213
214 // diameter = get("diameter").toString().toFloat();
215 // dimensions = get("extent").toString();
216 // albedo = get("albedo").toString().toFloat();
217 // rot_period = get("rot_per").toString().toFloat();
218 // period = get("per.y").toDouble();
219 // earth_moid = get("moid").toString().toDouble();
220 // orbit_class = get("class").toString();
221 // K1 = get("H").toString().toFloat();
222 // K2 = get("G").toString().toFloat();
223
224 // com = new KSComet(name, QString(), q, e, dms(dble_i), dms(dble_w),
225 // dms(dble_N), Tp, M1, M2, K1, K2);
226 // com->setOrbitID(orbit_id);
227 // com->setNEO(neo);
228 // com->setDiameter(diameter);
229 // com->setDimensions(dimensions);
230 // com->setAlbedo(albedo);
231 // com->setRotationPeriod(rot_period);
232 // com->setPeriod(period);
233 // com->setEarthMOID(earth_moid);
234 // com->setOrbitClass(orbit_class);
235 // com->setAngularSize(0.005);
236 // appendListObject(com);
237
238 // // Add *short* name to the list of object names
239 // objectNames(SkyObject::COMET).append(com->name());
240 // objectLists(SkyObject::COMET)
241 // .append(QPair<QString, const SkyObject *>(com->name(), com));
242 // });
243 // }
244 // catch (const std::runtime_error &e)
245 // {
246 // qCInfo(KSTARS) << "Loading comets failed.";
247 // qCInfo(KSTARS) << " -> was trying to read " + file_name;
248 // return;
249 // }
250 //}
251
draw(SkyPainter * skyp)252 void CometsComponent::draw(SkyPainter *skyp)
253 {
254 Q_UNUSED(skyp)
255 #ifndef KSTARS_LITE
256 if (!selected() || Options::zoomFactor() < 10 * MINZOOM)
257 return;
258
259 bool hideLabels = !Options::showCometNames() || (SkyMap::Instance()->isSlewing() && Options::hideLabels());
260 double rsunLabelLimit = Options::maxRadCometName();
261
262 //FIXME: Should these be config'able?
263 skyp->setPen(QPen(QColor("transparent")));
264 skyp->setBrush(QBrush(QColor("white")));
265
266 for (auto so : m_ObjectList)
267 {
268 KSComet *com = dynamic_cast<KSComet *>(so);
269 double mag = com->mag();
270 if (std::isnan(mag) == 0)
271 {
272 bool drawn = skyp->drawComet(com);
273 if (drawn && !(hideLabels || com->rsun() >= rsunLabelLimit))
274 SkyLabeler::AddLabel(com, SkyLabeler::COMET_LABEL);
275 }
276 }
277 #endif
278 }
279
280 // DO NOT REMOVE
281 //void CometsComponent::updateDataFile(bool isAutoUpdate)
282 //{
283 // delete (downloadJob);
284 // downloadJob = new FileDownloader();
285
286 // if (isAutoUpdate == false)
287 // downloadJob->setProgressDialogEnabled(true, i18n("Comets Update"),
288 // i18n("Downloading comets updates..."));
289 // downloadJob->registerDataVerification([&](const QByteArray &data)
290 // { return data.startsWith("{\"signature\""); });
291
292 // connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady()));
293
294 // // For auto-update, we ignore errors
295 // if (isAutoUpdate == false)
296 // connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
297
298 // QUrl url = QUrl("https://ssd-api.jpl.nasa.gov/sbdb_query.api");
299 // QByteArray post_data = KSUtils::getJPLQueryString(
300 // "c",
301 // "full_name,epoch.mjd,q,e,i,w,om,tp,orbit_id,neo,"
302 // "M1,M2,diameter,extent,albedo,rot_per,per.y,moid,H,G,class",
303 // QVector<KSUtils::JPLFilter>{});
304 // // FIXME: find out what { "Af", "!=", "D" } used to mean
305
306 // downloadJob->post(url, post_data);
307 //}
308
updateDataFile(bool isAutoUpdate)309 void CometsComponent::updateDataFile(bool isAutoUpdate)
310 {
311 delete (downloadJob);
312 downloadJob = new FileDownloader();
313
314 if (isAutoUpdate == false)
315 downloadJob->setProgressDialogEnabled(true, i18n("Comets Update"),
316 i18n("Downloading comets updates..."));
317
318 connect(downloadJob, SIGNAL(downloaded()), this, SLOT(downloadReady()));
319
320 // For auto-update, we ignore errors
321 if (isAutoUpdate == false)
322 connect(downloadJob, SIGNAL(error(QString)), this, SLOT(downloadError(QString)));
323
324 QUrl url = QUrl("https://www.minorplanetcenter.net/Extended_Files/cometels.json.gz");
325 downloadJob->get(url);
326 }
327
downloadReady()328 void CometsComponent::downloadReady()
329 {
330 // Comment the first line
331 QByteArray data = downloadJob->downloadedData();
332
333 // Write data to cometels.json.gz
334 QFile file(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation))
335 .filePath("cometels.json.gz"));
336 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
337 {
338 file.write(data);
339 file.close();
340 }
341 else
342 qCWarning(KSTARS) << "Failed writing comet data to" << file.fileName();
343
344 QString focusedComet;
345
346 #ifdef KSTARS_LITE
347 SkyObject *foc = KStarsLite::Instance()->map()->focusObject();
348 if (foc && foc->type() == SkyObject::COMET)
349 {
350 focusedComet = foc->name();
351 KStarsLite::Instance()->map()->setFocusObject(nullptr);
352 }
353 #else
354 SkyObject *foc = KStars::Instance()->map()->focusObject();
355 if (foc && foc->type() == SkyObject::COMET)
356 {
357 focusedComet = foc->name();
358 KStars::Instance()->map()->setFocusObject(nullptr);
359 }
360 #endif
361
362 // Reload comets
363 loadData();
364
365 #ifdef KSTARS_LITE
366 KStarsLite::Instance()->data()->setFullTimeUpdate();
367 if (!focusedComet.isEmpty())
368 KStarsLite::Instance()->map()->setFocusObject(
369 KStarsLite::Instance()->data()->objectNamed(focusedComet));
370 #else
371 if (!focusedComet.isEmpty())
372 KStars::Instance()->map()->setFocusObject(
373 KStars::Instance()->data()->objectNamed(focusedComet));
374 KStars::Instance()->data()->setFullTimeUpdate();
375 #endif
376
377 downloadJob->deleteLater();
378 }
379
380 // DO NOT REMOVE
381 //void CometsComponent::downloadReady()
382 //{
383 // // Comment the first line
384 // QByteArray data = downloadJob->downloadedData();
385
386 // // Write data to comets.dat
387 // QFile file(QDir(KSPaths::writableLocation(QStandardPaths::AppDataLocation))
388 // .filePath("comets.dat"));
389 // if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text))
390 // {
391 // file.write(data);
392 // file.close();
393 // }
394 // else
395 // qCWarning(KSTARS) << "Failed writing comet data to" << file.fileName();
396
397 // QString focusedComet;
398
399 //#ifdef KSTARS_LITE
400 // SkyObject *foc = KStarsLite::Instance()->map()->focusObject();
401 // if (foc && foc->type() == SkyObject::COMET)
402 // {
403 // focusedComet = foc->name();
404 // KStarsLite::Instance()->map()->setFocusObject(nullptr);
405 // }
406 //#else
407 // SkyObject *foc = KStars::Instance()->map()->focusObject();
408 // if (foc && foc->type() == SkyObject::COMET)
409 // {
410 // focusedComet = foc->name();
411 // KStars::Instance()->map()->setFocusObject(nullptr);
412 // }
413 //#endif
414
415 // // Reload comets
416 // loadData();
417
418 //#ifdef KSTARS_LITE
419 // KStarsLite::Instance()->data()->setFullTimeUpdate();
420 // if (!focusedComet.isEmpty())
421 // KStarsLite::Instance()->map()->setFocusObject(
422 // KStarsLite::Instance()->data()->objectNamed(focusedComet));
423 //#else
424 // if (!focusedComet.isEmpty())
425 // KStars::Instance()->map()->setFocusObject(
426 // KStars::Instance()->data()->objectNamed(focusedComet));
427 // KStars::Instance()->data()->setFullTimeUpdate();
428 //#endif
429
430 // downloadJob->deleteLater();
431 //}
432
downloadError(const QString & errorString)433 void CometsComponent::downloadError(const QString &errorString)
434 {
435 KSNotification::error(i18n("Error downloading asteroids data: %1", errorString));
436 qCCritical(KSTARS) << errorString;
437 downloadJob->deleteLater();
438 }
439