• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Stellarium / stellarium / 5926409083

21 Aug 2023 12:45PM UTC coverage: 11.905% (+0.01%) from 11.891%
5926409083

Pull #3373

github

gzotti
Reduce verbosity a bit.
Pull Request #3373: Fix: unambiguous comet names

264 of 264 new or added lines in 9 files covered. (100.0%)

14845 of 124700 relevant lines covered (11.9%)

23289.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

7.65
/plugins/SolarSystemEditor/src/SolarSystemEditor.cpp
1
/*
2
 * Solar System editor plug-in for Stellarium
3
 * 
4
 * Copyright (C) 2010 Bogdan Marinov
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 2
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
19
 */
20

21
#include "SolarSystemEditor.hpp"
22
#include "SolarSystemManagerWindow.hpp"
23

24
#include "StelUtils.hpp"
25
#include "StelApp.hpp"
26
#include "StelFileMgr.hpp"
27
#include "StelIniParser.hpp"
28
#include "StelModuleMgr.hpp"
29
#include "StelObjectMgr.hpp"
30
#include "SolarSystem.hpp"
31

32
#include <QDate>
33
#include <QDebug>
34
#include <QDir>
35
#include <QFile>
36
#include <QSettings>
37
#include <QString>
38
#include <QBuffer>
39
#include <QRegularExpression>
40
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
41
#include <QTextCodec>
42
#endif
43

44
#include <cmath>
45
#include <stdexcept>
46

47

48
StelModule* SolarSystemEditorStelPluginInterface::getStelModule() const
×
49
{
50
        return new SolarSystemEditor();
×
51
}
52

53
StelPluginInfo SolarSystemEditorStelPluginInterface::getPluginInfo() const
×
54
{
55
        // Allow to load the resources when used as a static plugin
56
        Q_INIT_RESOURCE(SolarSystemEditor);
×
57
        
58
        StelPluginInfo info;
×
59
        info.id = "SolarSystemEditor";
×
60
        info.displayedName = N_("Solar System Editor");
×
61
        info.authors = "Bogdan Marinov";
×
62
        info.contact = STELLARIUM_URL;
×
63
        info.description = N_("An interface for adding asteroids and comets to Stellarium. It can download object lists from the Minor Planet Center's website and perform searches in its online database.");
×
64
        info.version = SOLARSYSTEMEDITOR_PLUGIN_VERSION;
×
65
        info.license = SOLARSYSTEMEDITOR_PLUGIN_LICENSE;
×
66
        return info;
×
67
}
×
68

69
SolarSystemEditor::SolarSystemEditor():
×
70
        isInitialized(false),
×
71
        mainWindow(nullptr),
×
72
        solarSystemConfigurationFile(nullptr)
×
73
{
74
        setObjectName("SolarSystemEditor");
×
75
        solarSystem = GETSTELMODULE(SolarSystem);
×
76

77
        //I really hope that the file manager is instantiated before this
78
        // GZ new: Not sure whether this should be major or minor here.
79
        defaultSolarSystemFilePath        = QFileInfo(StelFileMgr::getInstallationDir() + "/data/ssystem_minor.ini").absoluteFilePath();
×
80
        majorSolarSystemFilePath        = QFileInfo(StelFileMgr::getInstallationDir() + "/data/ssystem_major.ini").absoluteFilePath();
×
81
        customSolarSystemFilePath        = QFileInfo(StelFileMgr::getUserDir() + "/data/ssystem_minor.ini").absoluteFilePath();
×
82
}
×
83

84
SolarSystemEditor::~SolarSystemEditor()
×
85
{
86
        if (solarSystemConfigurationFile != nullptr)
×
87
        {
88
                delete solarSystemConfigurationFile;
×
89
        }
90
}
×
91

92
void SolarSystemEditor::init()
×
93
{
94
        //Get a list of the "default" minor Solar System objects' names:
95
        //TODO: Use it as validation for the loading of the plug-in
96
        // GZ TBD: Maybe only load the names from ssystem_major.ini, so that even default minor bodies can be reduced by the user.
97
        // The current solution loads the default major objects and thus prevents Pluto from deletion.
98
//        if (QFile::exists(defaultSolarSystemFilePath))
99
//        {
100
//                // GZ We no longer prevent users from deleting minor bodies even when they came in the default file!
101
//                //defaultSsoIdentifiers = listAllLoadedObjectsInFile(defaultSolarSystemFilePath);
102
//        }
103
//        else
104
//        {
105
//                //TODO: Better error message
106
//                qDebug() << "SolarSystemEditor: Something is horribly wrong with installdir (minor)" << QDir::toNativeSeparators(StelFileMgr::getInstallationDir());
107
//                return;
108
//        }
109
        if (QFile::exists(majorSolarSystemFilePath))
×
110
        {
111
                //defaultSsoIdentifiers.unite(listAllLoadedObjectsInFile(majorSolarSystemFilePath));
112
                defaultSsoIdentifiers = listAllLoadedObjectsInFile(majorSolarSystemFilePath);
×
113
        }
114
        else
115
        {
116
                qDebug() << "SolarSystemEditor: Something is horribly wrong with installdir (major)" << QDir::toNativeSeparators(StelFileMgr::getInstallationDir());
×
117
                return;
×
118
        }
119

120
        try
121
        {
122
                //Make sure that a user ssystem_minor.ini actually exists
123
                if (!cloneSolarSystemConfigurationFile())
×
124
                {
125
                        qWarning() << "SolarSystemEditor: Cannot copy ssystem_minor.ini to user data directory. Plugin will not work.";
×
126
                        return;
×
127
                }
128

129
                mainWindow = new SolarSystemManagerWindow();
×
130
        }
131
        catch (std::runtime_error &e)
×
132
        {
133
                qWarning() << "init() error: " << e.what();
×
134
                return;
×
135
        }
×
136

137
        // { Asteroid Number, "Periodic Comet Number"}
138
        cometLikeAsteroids = {
139
                {   2060,  "95P" }, {   4015, "107P" }, {   7968, "133P" }, {  60558, "174P" },
×
140
                { 118401, "176P" }, { 248370, "433P" }, { 300163, "288P" }, { 323137, "282P" },
×
141
                { 457175, "362P" }
×
142
        };
×
143

144
        initMinorPlanetData();
×
145
        initCometCrossref();
×
146

147
        connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(updateI18n()));
×
148
        isInitialized = true;
×
149

150
        // key bindings and other actions
151
        addAction("actionShow_MPC_Import", N_("Solar System Editor"), N_("Import orbital elements in MPC format..."), mainWindow, "newImportMPC()", "Ctrl+Alt+S");
×
152
}
153

154
double SolarSystemEditor::getCallOrder(StelModuleActionName) const// actionName
×
155
{
156
        return 0.;
×
157
}
158

159
bool SolarSystemEditor::configureGui(bool show)
×
160
{
161
        //If the plug-in has failed to initialize, disable the button
162
        //TODO: Display a message in the window instead.
163
        if (!isInitialized)
×
164
                return false;
×
165

166
        if(show)
×
167
        {
168
                mainWindow->setVisible(true);
×
169

170
                //Debugging block
171
                //if (cloneSolarSystemConfigurationFile())
172
                {
173
                        //Import Encke for a start
174
                        /*SsoElements SSO = readMpcOneLineCometElements("0002P         2010 08  6.5102  0.336152  0.848265  186.5242  334.5718   11.7843  20100104  11.5  6.0  2P/Encke                                                 MPC 59600");
175
                        if (!appendToSolarSystemConfigurationFile(SSO))
176
                                return true;
177
                        */
178

179
                        //Import a list of comets on the desktop. The file is from
180
                        //http://www.minorplanetcenter.org/iau/Ephemerides/Comets/Soft00Cmt.txt
181
                        //It seems that some entries in the list don't match the described format
182
                        /*QList<SsoElements> objectList = readMpcOneLineCometElementsFromFile(StelFileMgr::getDesktopDir() + "/Soft00Cmt.txt");
183
                        if (!appendToSolarSystemConfigurationFile(objectList))
184
                                return true;
185
                        */
186

187
                        //Import Cruithne
188
                        /*SsoElements asteroid = readMpcOneLineMinorPlanetElements("03753   15.6   0.15 K107N 205.95453   43.77037  126.27658   19.80793  0.5149179  0.98898552   0.9977217  3 MPO183459   488  28 1973-2010 0.58 M-h 3Eh MPC        0000           (3753) Cruithne   20100822");
189
                        if (!appendToSolarSystemConfigurationFile(asteroid))
190
                                return true;
191
                        */
192

193
                        //Import a list of asteroids. The file is from
194
                        //http://www.minorplanetcenter.org/iau/Ephemerides/Bright/2010/Soft00Bright.txt
195
                        /*QList<SsoElements> objectList = readMpcOneLineMinorPlanetElementsFromFile(StelFileMgr::getDesktopDir() + "/Soft00Bright.txt");
196
                        if (!appendToSolarSystemConfigurationFile(objectList))
197
                                return true;*/
198

199
                        //Destroy and re-create the Solal System
200
                        //solarSystemManager->reloadPlanets();
201
                }
202
        }
203
        return true;
×
204
}
205

206
void SolarSystemEditor::updateI18n()
×
207
{
208
        //The Solar System MUST be translated before updating the window
209
        //NOTE: Remove this if/when you merge this module in the Solar System module
210
        solarSystem->updateI18n();
×
211
}
×
212

213
bool SolarSystemEditor::cloneSolarSystemConfigurationFile() const
×
214
{
215
        QDir userDataDirectory(StelFileMgr::getUserDir());
×
216
        if (!userDataDirectory.exists())
×
217
        {
218
                qCritical() << "Unable to find user data directory:" << QDir::toNativeSeparators(userDataDirectory.absolutePath());
×
219
                return false;
×
220
        }
221
        if (!userDataDirectory.exists("data") && !userDataDirectory.mkdir("data"))
×
222
        {
223
                qDebug() << "Unable to create a \"data\" subdirectory in" << QDir::toNativeSeparators(userDataDirectory.absolutePath());
×
224
                return false;
×
225
        }
226

227
        if (QFile::exists(customSolarSystemFilePath))
×
228
        {
229
                qDebug() << "Using the ssystem_minor.ini file that already exists in the user directory...";
×
230
                return true;
×
231
        }
232

233
        if (QFile::exists(defaultSolarSystemFilePath))
×
234
        {
235
                qDebug() << "Trying to copy ssystem_minor.ini to" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
236
                return QFile::copy(defaultSolarSystemFilePath, customSolarSystemFilePath);
×
237
        }
238
        else
239
        {
240
                qCritical() << "SolarSystemEditor: Cannot find default ssystem_minor.ini. This branch should be unreachable.";
×
241
                Q_ASSERT(0);
×
242
                return false;
243
        }
244
}
×
245

246
bool SolarSystemEditor::resetSolarSystemConfigurationFile() const
×
247
{
248
        if (QFile::exists(customSolarSystemFilePath))
×
249
        {
250
                if (!QFile::remove((customSolarSystemFilePath)))
×
251
                {
252
                        qWarning() << "Unable to delete" << QDir::toNativeSeparators(customSolarSystemFilePath)
×
253
                                 << StelUtils::getEndLineChar() << "Please remove the file manually.";
×
254
                        return false;
×
255
                }
256
        }
257

258
        return cloneSolarSystemConfigurationFile();
×
259
}
260

261
void SolarSystemEditor::resetSolarSystemToDefault()
×
262
{
263
        if (isInitialized)
×
264
        {
265
                if (resetSolarSystemConfigurationFile())
×
266
                {
267
                        //Deselect all currently selected objects
268
                        StelObjectMgr * objectManager = GETSTELMODULE(StelObjectMgr);
×
269
                        //TODO
270
                        objectManager->unSelect();
×
271

272
                        solarSystem->reloadPlanets();
×
273
                        emit solarSystemChanged();
×
274
                }
275
        }
276
}
×
277

278
bool SolarSystemEditor::copySolarSystemConfigurationFileTo(QString filePath)
×
279
{
280
        if (QFile::exists(customSolarSystemFilePath))
×
281
        {
282
                QFileInfo targetFile(filePath);
×
283
                return QFile::copy(customSolarSystemFilePath, targetFile.absoluteFilePath());
×
284
        }
×
285
        else
286
        {
287
                return false;
×
288
        }
289
}
290

291
bool SolarSystemEditor::isFileEncodingValid(QString filePath) const
×
292
{
293
        QFile checkFile(filePath);
×
294
        if (!checkFile.open(QIODevice::ReadOnly))
×
295
        {
296
                qWarning() << "[Solar System Editor] Cannot open " << QDir::toNativeSeparators(filePath);
×
297
                return false;
×
298
        }
299
        else
300
        {
301
#if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
302
                QTextStream in(&checkFile);
303
                in.setEncoding(QStringConverter::Utf8);
304
                in.readAll();
305
                checkFile.close();
306

307
                if (in.status()!=QTextStream::Ok)
308
#else
309
                QByteArray byteArray = checkFile.readAll();
×
310
                checkFile.close();
×
311

312
                QTextCodec::ConverterState state;
×
313
                QTextCodec *codec = QTextCodec::codecForName("UTF-8");
×
314
                const QString text = codec->toUnicode(byteArray.constData(), byteArray.size(), &state);
×
315
                Q_UNUSED(text)
316
                if (state.invalidChars > 0)
×
317
#endif
318
                {
319
                        qDebug() << "[Solar System Editor] Not a valid UTF-8 sequence in file " << filePath;
×
320
                        return false;
×
321
                }
322
                else
323
                        return true;
×
324
        }
×
325
}
×
326

327
bool SolarSystemEditor::replaceSolarSystemConfigurationFileWith(QString filePath)
×
328
{
329
        if (!QFile::exists(filePath))
×
330
        {
331
                qCritical() << "SolarSystemEditor: Cannot replace, file not found: " << filePath;
×
332
                return false;
×
333
        }
334

335
        if (!isFileEncodingValid(filePath))
×
336
                return false;
×
337

338
        //Is it a valid configuration file?
339
        QSettings settings(filePath, StelIniFormat);
×
340
        if (settings.status() != QSettings::NoError)
×
341
        {
342
                qCritical() << "SolarSystemEditor: " << QDir::toNativeSeparators(filePath) << "is not a valid configuration file.";
×
343
                return false;
×
344
        }
345

346
        //Remove the existingfile
347
        if (QFile::exists(customSolarSystemFilePath))
×
348
        {
349
                if(!QFile::remove(customSolarSystemFilePath))
×
350
                {
351
                        qCritical() << "SolarSystemEditor: Cannot remove old file.";
×
352
                        return false;
×
353
                }
354
        }
355

356
        //Copy the new file
357
        //If the copy fails, reset to the default configuration
358
        if (QFile::copy(filePath, customSolarSystemFilePath))
×
359
        {
360
                solarSystem->reloadPlanets();
×
361
                emit solarSystemChanged();
×
362
                return true;
×
363
        }
364
        else
365
        {
366
                qWarning() << "SolarSystemEditor: Could not replace file. Restoring default.";
×
367
                if (cloneSolarSystemConfigurationFile())
×
368
                {
369
                        solarSystem->reloadPlanets();
×
370
                        emit solarSystemChanged();
×
371
                        return true;
×
372
                }
373
                else
374
                {
375
                        qCritical() << "SolarSystemEditor: Could neither replace nor then restore default file. This comes unexpected.";
×
376
                        return false;
×
377
                }
378
        }
379
}
×
380

381
bool SolarSystemEditor::addFromSolarSystemConfigurationFile(QString filePath)
×
382
{
383
        if (!QFile::exists(filePath))
×
384
        {
385
                qCritical() << "SolarSystemEditor: Cannot add data from file, file" << filePath << "not found.";
×
386
                return false;
×
387
        }
388

389
        if (!isFileEncodingValid(filePath))
×
390
                return false;
×
391

392
        //Is it a valid configuration file?
393
        QSettings newData(filePath, StelIniFormat);
×
394
        if (newData.status() != QSettings::NoError)
×
395
        {
396
                qCritical() << "SolarSystemEditor: Cannot add data from file," << QDir::toNativeSeparators(filePath) << "is not a valid configuration file.";
×
397
                return false;
×
398
        }
399

400
        //Process the existing and new files:
401
        if (QFile::exists(customSolarSystemFilePath))
×
402
        {
403
                QSettings minorBodies(customSolarSystemFilePath, StelIniFormat);
×
404

405
                // add and overwrite existing data in the user's ssystem_minor.ini by the data in the new file.
406
                qDebug() << "ADD OBJECTS: Data for " << newData.childGroups().count() << "objects to minor file with " << minorBodies.childGroups().count() << "entries";
×
407
                for (auto group : newData.childGroups())
×
408
                {
409
                        QString fixedGroupName = fixGroupName(group);
×
410
                        newData.beginGroup(group);
×
411
                        qDebug() << "  Group: " << group << "for object " << newData.value("name");
×
412
                        QStringList minorChildGroups = minorBodies.childGroups();
×
413
                        if (minorChildGroups.contains(fixedGroupName))
×
414
                        {
415
                                qDebug() << "   This group " << fixedGroupName << "already exists. Updating values";
×
416
                        }
417
                        else
418
                                qDebug() << "   This group " << fixedGroupName << "does not yet exist. Adding values";
×
419

420
                        minorBodies.beginGroup(fixedGroupName);
×
421
                        QStringList newKeys = newData.allKeys(); // limited to the group!
×
422
                        for (const auto &key : qAsConst(newKeys))
×
423
                        {
424
                                minorBodies.setValue(key, newData.value(key));
×
425
                        }
426
                        minorBodies.endGroup();
×
427
                        newData.endGroup();
×
428
                }
×
429
                minorBodies.sync();
×
430
                qDebug() << "Minor groups now: " << minorBodies.childGroups();
×
431
                // There may be a generic group "General" in the updated file, created from comments. We must remove it.
432
                if (minorBodies.childGroups().contains("General"))
×
433
                {
434
                        qDebug() << "Removing unwanted General group.";
×
435
                        minorBodies.remove("General");
×
436
                        qDebug() << "Minor groups after fix now: " << minorBodies.childGroups();
×
437
                }
438
                minorBodies.sync();
×
439

440
                solarSystem->reloadPlanets();
×
441
                emit solarSystemChanged();
×
442
                return true;
×
443
        }
×
444
        else
445
        {
446
                qCritical() << "SolarSystemEditor: Cannot add data to use file," << QDir::toNativeSeparators(customSolarSystemFilePath) << "not found.";
×
447
                return false;
×
448
        }
449
}
×
450

451
QHash<QString,QString> SolarSystemEditor::listAllLoadedObjectsInFile(QString filePath) const
×
452
{
453
        if (!QFile::exists(filePath))
×
454
                return QHash<QString,QString>();
×
455

456
        QSettings solarSystemIni(filePath, StelIniFormat);
×
457
        if (solarSystemIni.status() != QSettings::NoError)
×
458
                return QHash<QString,QString>();
×
459

460
        QStringList groups = solarSystemIni.childGroups();
×
461
        QStringList minorBodies = solarSystem->getAllMinorPlanetCommonEnglishNames();
×
462
        QHash<QString,QString> loadedObjects;
×
463
        for (const auto &group : qAsConst(groups))
×
464
        {
465
                QString name = solarSystemIni.value(group + "/name").toString();
×
466
                if (minorBodies.contains(name))
×
467
                {
468
                        loadedObjects.insert(name, group);
×
469
                }
470
        }
×
471
        return loadedObjects;
×
472
}
×
473

474
void SolarSystemEditor::initCometCrossref()
×
475
{
476
        Q_ASSERT(cometCrossref.isEmpty());
×
477

478
        QFile cdata(":/SolarSystemEditor/comet_discovery.fab");
×
479
        if(cdata.open(QFile::ReadOnly | QFile::Text))
×
480
        {
481
                // regular expression to find the comments and empty lines
482
                static const QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
×
483
                static const QRegularExpression periodicCometNumberRx("^(\\d+)([PD])$");
×
484

485
                while(!cdata.atEnd())
×
486
                {
487
                        QString line = QString::fromUtf8(cdata.readLine());
×
488

489
                        // Skip comments
490
                        if (commentRx.match(line).hasMatch() || line.startsWith("//"))
×
491
                                continue;
×
492

493
                        // Find data
494
                        if (!line.isEmpty())
×
495
                        {
496
                                CometDiscoveryData comet;
×
497
                                QStringList columns = line.split("|");
×
498
                                // New IAU Designation ("1994 V1", "1995 Q3", etc.)
499
                                //    A year followed by an upper-case letter indicating the half-month of discovery,
500
                                //    followed by a number indicating the order of discovery. This may, in turn, be
501
                                //    followed by a dash and another capital letter indicating one part of a fragmented comet.
502
                                // Old IAU Permanent Designation ("1378","1759 I", "1993 XIII", ...)
503
                                //    A year usually followed by a Roman numeral (which must be in upper-case) indicating
504
                                //    the order of perihelion passage of the comet in that year. When there was only one
505
                                //    comet passing perihelion in a year, then just the year number is used for this designation.
506
                                // Old IAU Provisional Designation ("1982i", "1887a", "1991a1" ...)
507
                                //    A year followed by a single lower-case letter, optionally followed by a single digit.
508
                                // Periodic Comet Number ("1P", "2P", ... )
509
                                //    1-3 digits followed by an upper-case "P", indicating one of the multiple-apparition
510
                                //    objects in the file. This is part of the new designation system.
511
                                //
512
                                // Source: https://pds-smallbodies.astro.umd.edu/data_sb/resources/designation_formats.shtml
513
                                //
514
                                // Equivalences in ssystem_minor.ini:
515
                                // New (1994) IAU Designation      is equal to date_code, or a number like 33P or 3D for periodic or defunct comets, resp.
516
                                // Old IAU Permanent Designation   is equal to perihelion_code
517
                                // Old IAU Provisional Designation is equal to discovery_code
518
                                // number                          is equal to comet_number
519

520
                                // [1] IAU designation or standard designation (Periodic Comet Number) for periodic comets
521
                                QString designation = columns.at(0).trimmed();
×
522
                                // [2] IAU designation or date code for comets
523
                                comet.date_code       = columns.at(1).simplified(); // remove extra internal spaces
×
524
                                // [3] perihelion code for comets
525
                                comet.perihelion_code = columns.at(2).simplified(); // remove extra internal spaces
×
526
                                // [4] discovery code for comets
527
                                comet.discovery_code  = columns.at(3).trimmed();
×
528
                                // [5] discovery date for comets
529
                                comet.discovery_date  = columns.at(4).trimmed();
×
530
                                // [6] discoverer of comets
531
                                comet.discoverer      = columns.at(5).trimmed();
×
532

533
                                if (cometCrossref.contains(designation))
×
534
                                        qWarning() << "SSE: Comet cross-index data: Overwriting entry for" << designation;
×
535
                                cometCrossref.insert(designation, comet);
×
536
                        }
×
537
                }
×
538
                cdata.close();
×
539
        }
540
}
×
541

542
void SolarSystemEditor::initMinorPlanetData()
×
543
{
544
        Q_ASSERT(numberedMinorPlanets.isEmpty());
×
545

546
        QFile dcdata(":/SolarSystemEditor/discovery_circumstances.dat");
×
547
        // Open file
548
        if (dcdata.open(QIODevice::ReadOnly))
×
549
        {
550
                QByteArray data = StelUtils::uncompress(dcdata);
×
551
                dcdata.close();
×
552

553
                //check if decompressing was successful
554
                if(data.isEmpty())
×
555
                        return;
×
556

557
                //create and open a QBuffer for reading
558
                QBuffer buf(&data);
×
559
                buf.open(QIODevice::ReadOnly);
×
560

561
                // regular expression to find the comments and empty lines
562
                static const QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
×
563

564
                while (!buf.atEnd())
×
565
                {
566
                        QString line = QString::fromUtf8(buf.readLine());
×
567

568
                        // Skip comments
569
                        if (commentRx.match(line).hasMatch() || line.startsWith("//"))
×
570
                                continue;
×
571

572
                        QStringList list=line.split("\t");
×
573
                        int number = list.at(0).trimmed().toInt();
×
574
                        // The first entry is the date of discovery, the second is the name of discoverer
575
                        DiscoveryCircumstances discovery(list.at(1).trimmed(), list.at(2).trimmed());
×
576

577
                        numberedMinorPlanets.insert(number, discovery);
×
578
                }
×
579
                buf.close();
×
580
        }
×
581
}
×
582

583
QHash<QString,QString> SolarSystemEditor::listAllLoadedSsoIdentifiers() const
×
584
{
585
        if (QFile::exists(customSolarSystemFilePath))
×
586
        {
587
                return listAllLoadedObjectsInFile(customSolarSystemFilePath);
×
588
        }
589
        else
590
        {
591
                //TODO: Error message
592
                return QHash<QString,QString>();
×
593
        }
594
}
595

596
bool SolarSystemEditor::removeSsoWithName(QString name)
×
597
{
598
        if (name.isEmpty())
×
599
                return false;
×
600

601
        //qDebug() << name;
602
//        if (defaultSsoIdentifiers.keys().contains(name))
603
//        {
604
//                qWarning() << "You can't delete the default Solar System objects like" << name << "for the moment.";
605
//                qCritical() << "As of 0.16, this line should be impossible to reach!";
606
//                return false;
607
//        }
608

609
        //Make sure that the file exists
610
        if (!QFile::exists(customSolarSystemFilePath))
×
611
        {
612
                qDebug() << "Can't remove" << name << "from ssystem_minor.ini: Unable to find" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
613
                return false;
×
614
        }
615

616
        //Open the file
617
        QSettings settings(customSolarSystemFilePath, StelIniFormat);
×
618
        if (settings.status() != QSettings::NoError)
×
619
        {
620
                qDebug() << "Error opening ssystem_minor.ini:" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
621
                return false;
×
622
        }
623

624
        //Remove the section
625
        for (const auto &group : settings.childGroups())
×
626
        {
627
                if (settings.value(group + "/name").toString() == name)
×
628
                {
629
                        settings.remove(group);
×
630
                        settings.sync();
×
631
                        break;
×
632
                }
633
        }
×
634

635
        //Deselect all currently selected objects
636
        //TODO: I bet that someone will complain, so: unselect only the removed one
637
        GETSTELMODULE(StelObjectMgr)->unSelect();
×
638

639
        //Reload the Solar System
640
        //solarSystem->reloadPlanets();
641
        // Better: just remove this one object!
642
        solarSystem->removeMinorPlanet(name);
×
643
        emit solarSystemChanged();
×
644

645
        return true;
×
646
}
×
647

648
//TODO: Strings that have failed to be parsed. The usual source of discrepancies is
649
//http://www.minorplanetcenter.org/iau/Ephemerides/Comets/Soft00Cmt.txt
650
//It seems that some entries in the list don't match the described format.
651
/*
652
  "    CJ95O010  1997 03 31.4141  0.906507  0.994945  130.5321  282.6820   89.3193  20100723  -2.0  4.0  C/1995 O1 (Hale-Bopp)                                    MPC 61436" -> minus sign, fixed
653
  "    CK09K030  2011 01  9.266   3.90156   1.00000   251.413     0.032   146.680              8.5  4.0  C/2009 K3 (Beshore)                                      MPC 66205" -> lower precision than the spec, fixed
654
  "    CK10F040  2010 04  6.109   0.61383   1.00000   120.718   237.294    89.143             13.5  4.0  C/2010 F4 (Machholz)                                     MPC 69906" -> lower precision than the spec, fixed
655
  "    CK10M010  2012 02  7.840   2.29869   1.00000   265.318    82.150    78.373              9.0  4.0  C/2010 M1 (Gibbs)                                        MPC 70817" -> lower precision than the spec, fixed
656
  "    CK10R010  2011 11 28.457   6.66247   1.00000    96.009   345.949   157.437              6.0  4.0  C/2010 R1 (LINEAR)                                       MPEC 2010-R99" -> lower precision than the spec, fixed
657
  "0128P      b  2007 06 13.8064  3.062504  0.320891  210.3319  214.3583    4.3606  20100723   8.5  4.0  128P/Shoemaker-Holt                                      MPC 51822" -> fragment, fixed
658
  "0141P      d  2010 05 29.7106  0.757809  0.749215  149.3298  246.0849   12.8032  20100723  12.0 12.0  141P/Machholz                                            MPC 59599" -> fragment, fixed
659
*/
660
SsoElements SolarSystemEditor::readMpcOneLineCometElements(QString oneLineElements) const
×
661
{
662
        static const QRegularExpression leadingZerosExpr("^0*");
×
663
        SsoElements result;
×
664
        // New and exact definition, directly from https://minorplanetcenter.net/iau/info/CometOrbitFormat.html
665
        // We parse by documented columns and process extracted strings later.
666
        /*
667
         * Wrong regexp; GH: #2281
668
        QRegularExpression mpcParser("^([[:print:]]{4})"      //    1 -   4  i4     1. Periodic comet number
669
                                    "([CPDIA])"               //    5        a1     2. Orbit type (generally `C', `P' or `D') -- A=reclassified as Asteroid? I=Interstellar.
670
                                    "([[:print:]]{7}).{2}"    //    6 -  12  a7     3. IAU designation (in packed form)
671
                                    "([[:print:]]{4})."       //   15 -  18  i4     4. Year of perihelion passage
672
                                    "([[:print:]]{2})."       //   20 -  21  i2     5. Month of perihelion passage
673
                                    "([[:print:]]{7})."       //   23 -  29  f7.4   6. Day of perihelion passage (TT)
674
                                    "([[:print:]]{9}).{2}"    //   31 -  39  f9.6   7. Perihelion distance (AU)
675
                                    "([[:print:]]{8}).{2}"    //   42 -  49  f8.6   8. Orbital eccentricity
676
                                    "([[:print:]]{8}).{2}"    //   52 -  59  f8.4   9. Argument of perihelion, J2000.0 (degrees)
677
                                    "([[:print:]]{8}).{2}"    //   62 -  69  f8.4  10. Longitude of the ascending node, J2000.0 (degrees)
678
                                    "([[:print:]]{8}).{2}"    //   72 -  79  f8.4  11. Inclination in degrees, J2000.0 (degrees)
679
                                    "([[:print:]]{4})"        //   82 -  85  i4    12. Year of epoch for perturbed solutions
680
                                    "([[:print:]]{2})"        //   86 -  87  i2    13. Month of epoch for perturbed solutions
681
                                    "([[:print:]]{2})\\s{2}"  //   88 -  89  i2    14. Day of epoch for perturbed solutions
682
                                    "([[:print:]]{4})\\s"     //   92 -  95  f4.1  15. Absolute magnitude
683
                                    "([[:print:]]{4})\\s{2}"  //   97 - 100  f4.0  16. Slope parameter
684
                                    "([[:print:]]{56})\\s"    //  103 - 158  a56   17. Designation and Name
685
                                    "([[:print:]]{1,9}).*$"   //  160 - 168  a9    18. Reference
686
            );
687
        */
688
        static const QRegularExpression mpcParser("^\\s*(\\d{4})?"                        //    1 -   4  i4     1. Periodic comet number
689
                                                  "([CPDIA])"                             //    5        a1     2. Orbit type (generally `C', `P' or `D') -- A=reclassified as Asteroid? I=Interstellar.
690
                                                  "((?:\\w{6}|\\s{6})?[0a-zA-Z])?\\s+"    //    6 -  12  a7     3. IAU designation (in packed form)
691
                                                  "(\\d{4})\\s+"                          //   15 -  18  i4     4. Year of perihelion passage
692
                                                  "(\\d{2})\\s+"                          //   20 -  21  i2     5. Month of perihelion passage
693
                                                  "(\\d{1,2}\\.\\d{3,4})\\s+"             //   23 -  29  f7.4   6. Day of perihelion passage (TT)
694
                                                  "(\\d{1,2}\\.\\d{5,6})\\s+"             //   31 -  39  f9.6   7. Perihelion distance (AU)
695
                                                  "(\\d\\.\\d{5,6})\\s+"                  //   42 -  49  f8.6   8. Orbital eccentricity
696
                                                  "(\\d{1,3}\\.\\d{3,4})\\s+"             //   52 -  59  f8.4   9. Argument of perihelion, J2000.0 (degrees)
697
                                                  "(\\d{1,3}\\.\\d{3,4})\\s+"             //   62 -  69  f8.4  10. Longitude of the ascending node, J2000.0 (degrees)
698
                                                  "(\\d{1,3}\\.\\d{3,4})\\s+"             //   72 -  79  f8.4  11. Inclination in degrees, J2000.0 (degrees)
699
                                                  "(?:(\\d{4})"                           //   82 -  85  i4    12. Year of epoch for perturbed solutions
700
                                                  "(\\d\\d)"                              //   86 -  87  i2    13. Month of epoch for perturbed solutions
701
                                                  "(\\d\\d))?\\s+"                        //   88 -  89  i2    14. Day of epoch for perturbed solutions
702
                                                  "(\\-?\\d{1,2}\\.\\d)\\s+"              //   92 -  95  f4.1  15. Absolute magnitude
703
                                                  "(\\d{1,2}\\.\\d)\\s+"                  //   97 - 100  f4.0  16. Slope parameter
704
                                                  "(\\S.{1,54}\\S)"                       //  103 - 158  a56   17. Designation and Name
705
                                                  "(?:\\s+(\\S.*))?$");                   //  160 - 168  a9    18. Reference
×
706
        if (!mpcParser.isValid())
×
707
        {
708
                qWarning() << "Bad Regular Expression:" << mpcParser.errorString();
×
709
                qWarning() << "Error offset:" << mpcParser.patternErrorOffset();
×
710
        }
711
        QRegularExpressionMatch mpcMatch=mpcParser.match(oneLineElements);
×
712
        //qDebug() << "RegExp captured:" << mpcMatch.capturedTexts();
713
        if (!mpcMatch.hasMatch())
×
714
        {
715
                qWarning() << "No match for" << oneLineElements;
×
716
                return result;
×
717
        }
718

719
        QString periodicNumberString = mpcMatch.captured(1).trimmed();
×
720
        periodicNumberString.remove(leadingZerosExpr);
×
721
        QChar orbitType = mpcMatch.captured(2).at(0);
×
722
        QString packedIauDesignation = mpcMatch.captured(3).trimmed(); // packed IAU designation, or a fragment code
×
723
        QString iauDesignation; // This either remains empty (for periodical comets) or receives something like P/2023 T2-B
×
724
        QString fragment; // periodic comets may have fragments with several letters!
×
725
        if (packedIauDesignation.length()==7)
×
726
        {
727
                iauDesignation=QString("%1/%2").arg(orbitType, unpackCometIAUDesignation(packedIauDesignation)); // like "P/2023 T2-B"
×
728
        }
729
        else
730
        {
731
                Q_ASSERT(packedIauDesignation.isLower());
×
732
                fragment=packedIauDesignation.toUpper();
×
733
        }
734

735
        const QString mpcName = mpcMatch.captured(17).trimmed(); // From MPC file, name format is either "332P-C/Ikeya-Murakami" or "C/2023 P1 (Nishimura)".
×
736
        // Now, split this and separate out the parts!
737

738
        if (periodicNumberString.isEmpty() && packedIauDesignation.isEmpty())
×
739
        {
740
                qWarning() << "Comet" << mpcName << "is missing both comet number AND IAU designation. Skipping.";
×
741
                return result;
×
742
        }
743
        if (mpcName.isEmpty())
×
744
        {
745
                qWarning() << "Comet element line cannot be decoded. Skipping " << oneLineElements;
×
746
                return result;
×
747
        }
748

749
        // Sanity check: Make sure a numbered periodic name has the same number.
750
        if ((periodicNumberString.length()) && (!mpcName.startsWith(periodicNumberString)))
×
751
                        qCritical() << "Parsed number '" << periodicNumberString << "' does not fit number in name! Line " << oneLineElements;
×
752

753

754
        // Derive name: Just use MPC name as-is for unnumbered comets, or add perihel year in brackets to numbered ones.
755
        QString name;
×
756
        if (mpcName.contains("(")) // "C/2023 P1 (Nishimura)" style
×
757
        {
758
                // Sanity check: Just make sure it matches!
759
                if ((iauDesignation.length()>0) && (!mpcName.startsWith(iauDesignation)))
×
760
                        qCritical() << "Parsed designation '" << iauDesignation <<  "' does not fit number in name! Line " << oneLineElements;
×
761
                name=mpcName;
×
762
        }
763
        else // "332P-C/Ikeya-Murakami" style
764
        {
765
                QStringList nameList=mpcName.split('/');
×
766
                QString fragmentPostfix;
×
767
                if (fragment.length()>0)
×
768
                        fragmentPostfix=QString("-%1").arg(fragment);
×
769
                if (nameList.count()!=2)
×
770
                {
771
                        qInfo() << "Odd name (no error, but no proper name for periodic comet) given in line " << oneLineElements;
×
772
                        name=nameList.at(0);
×
773
                }
774
                else
775
                        name=QString("%1%2%3/%4").arg(periodicNumberString, orbitType, fragmentPostfix, nameList.at(1).trimmed()); // "332P-C/Ikeya-Murakami"
×
776

777
                // Sanity check: Make sure this is identical to the mpcName...
778
                if (name!=mpcName)
×
779
                        qWarning() << "Some mismatch in names: constructed " << name << "vs. directly read" << mpcName;
×
780
                name.append(QString(" (%1)").arg(mpcMatch.captured(4).toInt())); // "332P-C/Ikeya-Murakami (2021)"
×
781
        }
×
782

783
        result.insert("name", name);
×
784
        if (!iauDesignation.isEmpty())
×
785
                result.insert("iau_designation", iauDesignation);
×
786

787
        // Now fill in missing data which we have in our file database (cometsData)
788

789
        QString key;
×
790
        static const QRegularExpression periodicRe("^([1-9][0-9]*[PD](-\\w+)?)"); // No "/" at end, there are nameless numbered comets! (e.g. 362, 396)
×
791
        QRegularExpressionMatch periodMatch=periodicRe.match(name);
×
792
        if (periodMatch.hasMatch())
×
793
                key=periodMatch.captured(1);
×
794
        else
795
                key = name.split("(").at(0).trimmed(); // IAU 1994 date style
×
796

797
        if (cometCrossref.contains(key))
×
798
        {
799
                qDebug() << "Filling extra data for " << name << "with key" << key;
×
800
                const CometDiscoveryData comet = cometCrossref.value(key);
×
801
                // It is still possible that we match one nnP entry with data from a different apparition. The additional IDs should be limited to the right perihelion date.
802
                // add IAU designation in addition to the old-style designation
803
                if (!comet.date_code.isEmpty() && !result.contains("iau_designation"))
×
804
                        result.insert("iau_designation", comet.date_code);
×
805

806
                // The data parsed from MPC may contain a makeshift perihelion code that only has a year. Only overwrite if we are in the same year!
807
                if (!comet.perihelion_code.isEmpty() &&
×
808
                                (!result.contains("perihelion_code") ||
×
809
                                 (result.value("perihelion_code").toString().length()==4) && comet.perihelion_code.contains(result.value("perihelion_code").toString())))
×
810
                {
811
                        result.insert("perihelion_code", comet.perihelion_code);
×
812
                        if (!comet.discovery_code.isEmpty() && result.contains("discovery_code") && (comet.discovery_code!=result.value("discovery_code")))
×
813
                                qWarning() << "DIFFERENT COMET. Won't update " << comet.discovery_code << "for " << name;
×
814
                        else if (!comet.discovery_code.isEmpty() && !result.contains("discovery_code"))
×
815
                                result.insert("discovery_code", comet.discovery_code);
×
816
                }
817
                if (!comet.discovery_date.isEmpty())
×
818
                        result.insert("discovery", comet.discovery_date);
×
819
                if (!comet.discoverer.isEmpty())
×
820
                        result.insert("discoverer", comet.discoverer);
×
821
        }
×
822

823
        QString sectionName = convertToGroupName(name);
×
824
        if (sectionName.isEmpty())
×
825
        {
826
                qWarning() << "Cannot derive a comet section name from" << name << ". Skipping.";
×
827
                return SsoElements();
×
828
        }
829
        result.insert("section_name", sectionName);
×
830

831
        //After a name has been determined, insert the essential keys
832
        //result.insert("parent", "Sun"); // 0.16: omit obvious default.
833
        result.insert("type", "comet");
×
834
        //result.insert("type", orbitType == 'A' ? "asteroid" : "comet"); // GZ: One could expect A=reclassified as asteroid, but eccentricity can be >1, so what is that?
835
        //"kepler_orbit" could be used for all cases:
836
        //"ell_orbit" interprets distances as kilometers, not AUs
837
        // result.insert("coord_func", "kepler_orbit"); // 0.20: omit default
838
        //result.insert("coord_func", "comet_orbit"); // 0.20: add this default for compatibility with earlier versions! TBD: remove for 1.0
839
        //result.insert("color", "1.0, 1.0, 1.0");  // 0.16: omit obvious default.
840
        //result.insert("tex_map", "nomap.png");    // 0.16: omit obvious default.
841

842
        bool ok1=false, ok2=false, ok3=false, ok4=false, ok5=false;
×
843

844
        int year        = mpcMatch.captured(4).toInt(&ok1);
×
845
        int month        = mpcMatch.captured(5).toInt(&ok2);
×
846
        double dayFraction        = mpcMatch.captured(6).toDouble(&ok3);
×
847

848
        if (ok1 && ok2 && ok3)
×
849
        {
850
                double jde;
851
                StelUtils::getJDFromDate(&jde, year, month, 0, 0, 0, 0.f);
×
852
                jde+=dayFraction;
×
853
                result.insert("orbit_TimeAtPericenter", jde);
×
854
        }
×
855
        else
856
        {
857
                qWarning() << "Cannot read perihelion date for comet " << name;
×
858
                return SsoElements();
×
859
        }
860

861
        double perihelionDistance = mpcMatch.captured(7).toDouble(&ok1); //AU
×
862
        double eccentricity = mpcMatch.captured(8).toDouble(&ok2); //NOT degrees, but without dimension.
×
863
        double argumentOfPerihelion = mpcMatch.captured(9).toDouble(&ok3);//J2000.0, degrees
×
864
        double longitudeOfAscendingNode = mpcMatch.captured(10).toDouble(&ok4);//J2000.0, degrees
×
865
        double inclination = mpcMatch.captured(11).toDouble(&ok5);
×
866
        if (ok1 && ok2 && ok3 && ok4 && ok5)
×
867
        {
868
                result.insert("orbit_PericenterDistance", perihelionDistance);
×
869
                result.insert("orbit_Eccentricity", eccentricity);
×
870
                result.insert("orbit_ArgOfPericenter", argumentOfPerihelion);
×
871
                result.insert("orbit_AscendingNode", longitudeOfAscendingNode);
×
872
                result.insert("orbit_Inclination", inclination);
×
873
        }
874
        else
875
        {
876
                qWarning() << "Cannot read main elements for comet " << name;
×
877
                return SsoElements();
×
878
        }
879

880
        // We should reduce orbit_good for elliptical orbits to one half period before/after epoch!
881
        // TBD: Can be removed in 1.0, relegating to handling by the loader.
882
        if (eccentricity < 1.0)
×
883
        {
884
                // Heafner, Fundamental Ephemeris Computations, p.71
885
                const double mu=(0.01720209895*0.01720209895); // GAUSS_GRAV_CONST^2
×
886
                const double a=perihelionDistance/(1.-eccentricity); // semimajor axis.
×
887
                const double meanMotion=std::sqrt(mu/(a*a*a)); // radians/day
×
888
                double period=M_PI*2.0 / meanMotion; // period, days
×
889
                result.insert("orbit_good", qMin(1000, static_cast<int>(floor(0.5*period)))); // validity for elliptical osculating elements, days. Goes from aphel to next aphel or max 1000 days.                
×
890
        }
891
        else
892
                result.insert("orbit_good", 1000); // default validity for osculating elements, days
×
893

894
        // Epoch
895
        year    = mpcMatch.captured(12).toInt(&ok1);
×
896
        month   = mpcMatch.captured(13).toInt(&ok2);
×
897
        int day = mpcMatch.captured(14).toInt(&ok3);
×
898
        if (ok1 && ok2 && ok3)
×
899
        {
900
                double jde;
901
                StelUtils::getJDFromDate(&jde, year, month, day, 0, 0, 0.f);
×
902
                result.insert("orbit_Epoch", jde);
×
903
        }
×
904
        else
905
        {
906
                qWarning() << "Warning: Cannot read element epoch date for comet " << name << ". Will use T_perihel at load time.";
×
907
        }
908

909
        double absoluteMagnitude = mpcMatch.captured(15).toDouble(&ok1);
×
910
        if (ok1)
×
911
                result.insert("absolute_magnitude", absoluteMagnitude);
×
912
        double slopeParameter = mpcMatch.captured(16).toDouble(&ok2);
×
913
        if (ok2)
×
914
                result.insert("slope_parameter", slopeParameter);
×
915
        if (!(ok1 && ok2))
×
916
        {
917
                qWarning() << "Warning: Problem reading magnitude parameters for comet " << name;
×
918
        }
919

920
        result.insert("ref", mpcMatch.captured(18).simplified());
×
921
        //if (orbitType!='A')
922
        //{
923
                //result.insert("radius", 5); //Fictitious default assumption
924
                //result.insert("albedo", 0.1); // GZ 2014-01-10: Comets are very dark, should even be 0.03!
925
                //result.insert("dust_lengthfactor", 0.4); // dust tail length w.r.t. gas tail length
926
                //result.insert("dust_brightnessfactor", 1.5); // dust tail brightness w.r.t. gas tail.
927
                //result.insert("dust_widthfactor", 1.5); // opening w.r.t. gas tail opening width.
928
        //}
929
        //qDebug() << "readMpcOneLineCometElements done\n";
930
        return result;
×
931
}
×
932

933
SsoElements SolarSystemEditor::readMpcOneLineMinorPlanetElements(QString oneLineElements) const
×
934
{
935
        SsoElements result;
×
936

937
        //This time I'll try splitting the line to columns, instead of
938
        //using a regular expression.
939
        //Using QString::mid() allows parsing it in a random sequence.
940

941
        //Length validation
942
        if (oneLineElements.isEmpty() ||
×
943
            oneLineElements.length() > 202 ||
×
944
            oneLineElements.length() < 152) //The column ends at 160, but is left-aligned
×
945
        {
946
                return result;
×
947
        }
948

949
        QString column;
×
950
        QString objectType = "asteroid";
×
951
        bool ok = false;
×
952
        //bool isLongForm = (oneLineElements.length() > 160) ? true : false;
953

954
        //Minor planet number or IAU designation
955
        column = oneLineElements.mid(0, 7).trimmed();
×
956
        if (column.isEmpty())
×
957
        {
958
                return result;
×
959
        }
960
        int minorPlanetNumber = 0;
×
961
        QString iauDesignation;
×
962
        QString name;
×
963
        if (column.toInt(&ok) || ok)
×
964
        {
965
                minorPlanetNumber = column.toInt();
×
966
        }
967
        else
968
        {
969
                //See if it is a number, but packed
970
                //I hope the format is right (I've seen prefixes only between A and P)
971
                QRegularExpression packedMinorPlanetNumber("^([A-Za-z])(\\d+)$");
×
972
                QRegularExpressionMatch mpMatch;
×
973
                if (column.indexOf(packedMinorPlanetNumber, 0, &mpMatch) == 0)
×
974
                {
975
                        minorPlanetNumber = mpMatch.captured(2).toInt(&ok);
×
976
                        //TODO: Validation
977
                        QChar prefix = mpMatch.captured(1).at(0);
×
978
                        if (prefix.isUpper())
×
979
                        {
980
                                minorPlanetNumber += ((10 + prefix.toLatin1() - 'A') * 10000);
×
981
                        }
982
                        else
983
                        {
984
                                minorPlanetNumber += ((10 + prefix.toLatin1() - 'a' + 26) * 10000);
×
985
                        }
986
                }
987
                else
988
                {
989
                        iauDesignation = unpackMinorPlanetIAUDesignation(column);
×
990
                }
991
        }
×
992

993
        if (minorPlanetNumber)
×
994
        {
995
                name = QString::number(minorPlanetNumber);
×
996
        }
997
        else if(iauDesignation.isEmpty())
×
998
        {
999
                qDebug() << "readMpcOneLineMinorPlanetElements():"
×
1000
                         << column
×
1001
                         << "is not a valid number or packed IAU designation";
×
1002
                return SsoElements();
×
1003
        }
1004
        else
1005
        {
1006
                name = iauDesignation;
×
1007
        }
1008

1009
        //In case the longer format is used, extract the human-readable name
1010
        column = oneLineElements.mid(166, 28).trimmed();
×
1011
        if (!column.isEmpty())
×
1012
        {
1013
                if (minorPlanetNumber)
×
1014
                {
1015
                        QRegularExpression asteroidName("^\\((\\d+)\\)\\s+(\\S.+)$");
×
1016
                        QRegularExpressionMatch astMatch;
×
1017
                        if (column.indexOf(asteroidName, 0, &astMatch) == 0)
×
1018
                        {
1019
                                name = astMatch.captured(2);
×
1020
                                result.insert("minor_planet_number", minorPlanetNumber);
×
1021
                        }
1022
                        else
1023
                        {
1024
                                //Use the whole string, just in case
1025
                                name = column;
×
1026
                        }
1027
                        // Add Periodic Comet Number for comet-like asteroids
1028
                        QString dateCode = cometLikeAsteroids.value(minorPlanetNumber, "");
×
1029
                        if (!dateCode.isEmpty())
×
1030
                        {
1031
                                if (name==QString("(%1)").arg(QString::number(minorPlanetNumber)))
×
1032
                                        result.insert("date_code", QString("%1/%2").arg(dateCode, name));
×
1033
                                else
1034
                                        result.insert("date_code", QString("%1/(%2) %3").arg(dateCode, QString::number(minorPlanetNumber), name));
×
1035
                        }
1036
                }
×
1037
                //In the other case, the name is already the IAU designation
1038
        }
1039
        if (name.isEmpty())
×
1040
                return SsoElements();
×
1041

1042
        result.insert("name", name);
×
1043

1044
        //Section name
1045
        QString sectionName = convertToGroupName(name, minorPlanetNumber);
×
1046
        if (sectionName.isEmpty())
×
1047
        {
1048
                return SsoElements();
×
1049
        }
1050
        result.insert("section_name", sectionName);
×
1051

1052
        //After a name has been determined, insert the essential keys
1053
        //result.insert("parent", "Sun");         // 0.16: omit obvious default.
1054
        //"kepler_orbit" is used for all cases:
1055
        //"ell_orbit" interprets distances as kilometers, not AUs
1056
        //result.insert("coord_func","kepler_orbit"); // 0.20: omit default
1057
        result.insert("coord_func", "comet_orbit"); // 0.20: add this default for compatibility with earlier versions!
×
1058

1059
        //result.insert("color", "1.0, 1.0, 1.0"); // 0.16: omit obvious default.
1060
        //result.insert("tex_map", "nomap.png");   // 0.16: omit obvious default.
1061

1062
        //Magnitude and slope parameter
1063
        column = oneLineElements.mid(8,5).trimmed();
×
1064
        double absoluteMagnitude = column.toDouble(&ok);
×
1065
        if (!ok)
×
1066
                return SsoElements();
×
1067
        column = oneLineElements.mid(14,5).trimmed();
×
1068
        double slopeParameter = column.toDouble(&ok);
×
1069
        if (!ok)
×
1070
                return SsoElements();
×
1071
        result.insert("absolute_magnitude", absoluteMagnitude);
×
1072
        result.insert("slope_parameter", slopeParameter);
×
1073

1074
        //Orbital parameters
1075
        column = oneLineElements.mid(37, 9).trimmed();
×
1076
        double argumentOfPerihelion = column.toDouble(&ok);//J2000.0, degrees
×
1077
        if (!ok)
×
1078
                return SsoElements();
×
1079
        result.insert("orbit_ArgOfPericenter", argumentOfPerihelion);
×
1080

1081
        column = oneLineElements.mid(48, 9).trimmed();
×
1082
        double longitudeOfTheAscendingNode = column.toDouble(&ok);//J2000.0, degrees
×
1083
        if (!ok)
×
1084
                return SsoElements();
×
1085
        result.insert("orbit_AscendingNode", longitudeOfTheAscendingNode);
×
1086

1087
        column = oneLineElements.mid(59, 9).trimmed();
×
1088
        double inclination = column.toDouble(&ok);//J2000.0, degrees
×
1089
        if (!ok)
×
1090
                return SsoElements();
×
1091
        result.insert("orbit_Inclination", inclination);
×
1092

1093
        column = oneLineElements.mid(70, 9).trimmed();
×
1094
        double eccentricity = column.toDouble(&ok);//degrees
×
1095
        if (!ok)
×
1096
                return SsoElements();
×
1097
        result.insert("orbit_Eccentricity", eccentricity);
×
1098

1099
        column = oneLineElements.mid(80, 11).trimmed();
×
1100
        double meanDailyMotion = column.toDouble(&ok);//degrees per day
×
1101
        if (!ok)
×
1102
                return SsoElements();
×
1103
        result.insert("orbit_MeanMotion", meanDailyMotion);
×
1104

1105
        column = oneLineElements.mid(92, 11).trimmed();
×
1106
        double semiMajorAxis = column.toDouble(&ok);
×
1107
        if (!ok)
×
1108
                return SsoElements();
×
1109
        result.insert("orbit_SemiMajorAxis", semiMajorAxis);
×
1110

1111
        column = oneLineElements.mid(20, 5).trimmed();//Epoch, in packed form
×
1112
        QRegularExpression packedDateFormat("^([IJK])(\\d\\d)([1-9A-C])([1-9A-V])$");
×
1113
        QRegularExpressionMatch dateMatch;
×
1114
        if (column.indexOf(packedDateFormat, 0, &dateMatch) != 0)
×
1115
        {
1116
                qWarning() << "readMpcOneLineMinorPlanetElements():"
×
1117
                         << column << "is not a date in packed format";
×
1118
                return SsoElements();
×
1119
        }
1120
        int year  = unpackYearNumber(dateMatch.captured(1).at(0).toLatin1(), dateMatch.captured(2).toInt());
×
1121
        int month = unpackDayOrMonthNumber(dateMatch.captured(3).at(0));
×
1122
        int day   = unpackDayOrMonthNumber(dateMatch.captured(4).at(0));
×
1123
        //qDebug() << column << year << month << day;
1124
        QDate epochDate(year, month, day);
×
1125
        if (!epochDate.isValid())
×
1126
        {
1127
                qWarning() << "readMpcOneLineMinorPlanetElements():"
×
1128
                         << column << "unpacks to"
×
1129
                         << QString("%1-%2-%3").arg(year).arg(month).arg(day)
×
1130
                                 << "This is not a valid date for an Epoch.";
×
1131
                return SsoElements();
×
1132
        }
1133
        //Epoch is at .0 TT, i.e. midnight
1134
        double epochJDE;
1135
        StelUtils::getJDFromDate(&epochJDE, year, month, day, 0, 0, 0);
×
1136
        result.insert("orbit_Epoch", epochJDE);
×
1137

1138
        column = oneLineElements.mid(26, 9).trimmed();
×
1139
        double meanAnomalyAtEpoch = column.toDouble(&ok);//degrees
×
1140
        if (!ok)
×
1141
                return SsoElements();
×
1142
        result.insert("orbit_MeanAnomaly", meanAnomalyAtEpoch);
×
1143
        // We assume from now on that orbit_good is 1/2 orbital duration for elliptical orbits if not given explicitly.
1144
        // This allows following full ellipses. However, elsewhere we should signal to users when
1145
        // it should be assumed that elements are outdated and thus positions wrong. (Let's take "1000 earth days" for that.)
1146
        if (eccentricity >=1.)
×
1147
        {
1148
            // This should actually never happen for minor planets!
1149
            qWarning() << "Strange eccentricity for " << name << ":" << eccentricity;
×
1150
            result.insert("orbit_good", 1000); // default validity for osculating elements for parabolic/hyperbolic comets, days
×
1151
        }
1152

1153
        // 2:3 resonance to Neptune [https://en.wikipedia.org/wiki/Plutino]
1154
        if (static_cast<int>(semiMajorAxis) == 39)
×
1155
                objectType = "plutino";
×
1156

1157
        // Classical Kuiper belt objects [https://en.wikipedia.org/wiki/Classical_Kuiper_belt_object]
1158
        if (semiMajorAxis>=40 && semiMajorAxis<=50)
×
1159
                objectType = "cubewano";
×
1160

1161
        // Calculate perihelion
1162
        const double q = (1 - eccentricity)*semiMajorAxis;
×
1163

1164
        // Scattered disc objects
1165
        if (q > 35)
×
1166
                objectType = "scattered disc object";
×
1167

1168
        // Sednoids [https://en.wikipedia.org/wiki/Planet_Nine]
1169
        if (q > 30 && semiMajorAxis > 250)
×
1170
                objectType = "sednoid";
×
1171

1172
        //Radius and albedo
1173
        //Assume albedo of 0.15 and calculate a radius based on the absolute magnitude
1174
        //as described here: http://www.physics.sfasu.edu/astro/asteroids/sizemagnitude.html
1175
        double albedo = 0.15; //Assumed
×
1176
        double radius = std::ceil(0.5*(1329 / std::sqrt(albedo)) * std::pow(10, -0.2 * absoluteMagnitude)); // Original formula is for diameter!
×
1177
        result.insert("albedo", albedo);
×
1178
        result.insert("radius", radius);
×
1179

1180
        DiscoveryCircumstances dc = numberedMinorPlanets.value(minorPlanetNumber, DiscoveryCircumstances("",""));
×
1181
        if (!dc.first.isEmpty())
×
1182
        {
1183
                result.insert("discovery", dc.first);
×
1184
                result.insert("discoverer", dc.second);
×
1185
        }
1186

1187
        result.insert("type", objectType);
×
1188

1189
        return result;
×
1190
}
×
1191

1192
QList<SsoElements> SolarSystemEditor::readMpcOneLineCometElementsFromFile(QString filePath) const
×
1193
{
1194
        QList<SsoElements> objectList;
×
1195

1196
        if (!QFile::exists(filePath))
×
1197
        {
1198
                qDebug() << "Can't find" << QDir::toNativeSeparators(filePath);
×
1199
                return objectList;
×
1200
        }
1201

1202
        QFile mpcElementsFile(filePath);
×
1203
        if (mpcElementsFile.open(QFile::ReadOnly | QFile::Text ))//| QFile::Unbuffered
×
1204
        {
1205
                int candidatesCount = 0;
×
1206
                int lineCount = 0;
×
1207

1208
                while(!mpcElementsFile.atEnd())
×
1209
                {
1210
                        QString oneLineElements = QString(mpcElementsFile.readLine(200));
×
1211
                        if(oneLineElements.endsWith('\n'))
×
1212
                        {
1213
                                oneLineElements.chop(1);
×
1214
                        }
1215
                        if (oneLineElements.isEmpty())
×
1216
                        {
1217
                                qDebug() << "Empty line skipped.";
×
1218
                                continue;
×
1219
                        }
1220
                        lineCount++;
×
1221

1222
                        SsoElements ssObject = readMpcOneLineCometElements(oneLineElements);
×
1223
                        if(!ssObject.isEmpty() && !ssObject.value("section_name").toString().isEmpty())
×
1224
                        {
1225
                                objectList << ssObject;
×
1226
                                candidatesCount++;
×
1227
                        }
1228
                }
×
1229
                mpcElementsFile.close();
×
1230
                qDebug() << "Done reading comet orbital elements."
×
1231
                         << "Recognized" << candidatesCount << "candidate objects"
×
1232
                         << "out of" << lineCount << "lines.";
×
1233

1234
                return objectList;
×
1235
        }
1236
        else
1237
        {
1238
                qDebug() << "Unable to open for reading" << QDir::toNativeSeparators(filePath);
×
1239
                qDebug() << "File error:" << mpcElementsFile.errorString();
×
1240
                return objectList;
×
1241
        }
1242
}
×
1243

1244
QList<SsoElements> SolarSystemEditor::readMpcOneLineMinorPlanetElementsFromFile(QString filePath) const
×
1245
{
1246
        QList<SsoElements> objectList;
×
1247

1248
        if (!QFile::exists(filePath))
×
1249
        {
1250
                qDebug() << "Can't find" << QDir::toNativeSeparators(filePath);
×
1251
                return objectList;
×
1252
        }
1253

1254
        QFile mpcElementsFile(filePath);
×
1255
        if (mpcElementsFile.open(QFile::ReadOnly | QFile::Text ))//| QFile::Unbuffered
×
1256
        {
1257
                int candidatesCount = 0;
×
1258
                int lineCount = 0;
×
1259

1260
                while(!mpcElementsFile.atEnd())
×
1261
                {
1262
                        QString oneLineElements = QString(mpcElementsFile.readLine(202 + 2));//Allow for end-of-line characters
×
1263
                        if(oneLineElements.endsWith('\n'))
×
1264
                        {
1265
                                oneLineElements.chop(1);
×
1266
                        }
1267
                        if (oneLineElements.isEmpty())
×
1268
                        {
1269
                                qDebug() << "Empty line skipped.";
×
1270
                                continue;
×
1271
                        }
1272
                        lineCount++;
×
1273

1274
                        SsoElements ssObject = readMpcOneLineMinorPlanetElements(oneLineElements);
×
1275
                        if(!ssObject.isEmpty() && !ssObject.value("section_name").toString().isEmpty())
×
1276
                        {
1277
                                objectList << ssObject;
×
1278
                                candidatesCount++;
×
1279
                        }
1280
                }
×
1281
                mpcElementsFile.close();
×
1282
                qDebug() << "Done reading minor planet orbital elements."
×
1283
                         << "Recognized" << candidatesCount << "candidate objects"
×
1284
                         << "out of" << lineCount << "lines.";
×
1285

1286
                return objectList;
×
1287
        }
1288
        else
1289
        {
1290
                qDebug() << "Unable to open for reading" << QDir::toNativeSeparators(filePath);
×
1291
                qDebug() << "File error:" << mpcElementsFile.errorString();
×
1292
                return objectList;
×
1293
        }
1294
}
×
1295

1296
bool SolarSystemEditor::appendToSolarSystemConfigurationFile(QList<SsoElements> objectList)
×
1297
{
1298
        qDebug() << "appendToSolarSystemConfigurationFile begin ... ";
×
1299
        if (objectList.isEmpty())
×
1300
        {
1301
                return false;
×
1302
        }
1303

1304
        //Check if the configuration file exists
1305
        if (!QFile::exists(customSolarSystemFilePath))
×
1306
        {
1307
                qDebug() << "Can't append object data to ssystem_minor.ini: Unable to find" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
1308
                return false;
×
1309
        }
1310

1311
        // load all existing minor bodies
1312
        QHash<QString,QString> loadedObjects = listAllLoadedSsoIdentifiers();
×
1313

1314
        QSettings * solarSystemSettings = new QSettings(customSolarSystemFilePath, StelIniFormat);
×
1315
        if (solarSystemSettings->status() != QSettings::NoError)
×
1316
        {
1317
                qDebug() << "Error opening ssystem_minor.ini:" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
1318
                return false;
×
1319
        }
1320

1321
        //Remove loaded objects (identified by name, not by section name) that are also in the proposed objectList
1322
        for (const auto &object : objectList)
×
1323
        {
1324
                QString name = object.value("name").toString();
×
1325
                if (name.isEmpty())
×
1326
                        continue;
×
1327

1328
                QString group = object.value("section_name").toString();
×
1329
                if (group.isEmpty())
×
1330
                        continue;
×
1331

1332
                if (loadedObjects.contains(name))
×
1333
                {
1334
                        solarSystemSettings->remove(loadedObjects.value(name));
×
1335
                        loadedObjects.remove(name);
×
1336
                }
1337
                else if (solarSystemSettings->childGroups().contains(group))
×
1338
                {
1339
                        loadedObjects.remove(solarSystemSettings->value(group + "/name").toString());
×
1340
                        solarSystemSettings->remove(group);
×
1341
                }
1342
        }
×
1343
        solarSystemSettings->sync();
×
1344
        delete solarSystemSettings;
×
1345
        solarSystemSettings = nullptr;
×
1346

1347
        const int width = -30;
×
1348
        QList<DiscoveryCircumstances> extraData;
×
1349

1350
        //Write to file. (Handle as regular text file, not QSettings.)
1351
        //TODO: The usual validation
1352
        qDebug() << "Appending to file...";
×
1353
        QFile solarSystemConfigurationFile(customSolarSystemFilePath);
×
1354
        if(solarSystemConfigurationFile.open(QFile::WriteOnly | QFile::Append | QFile::Text))
×
1355
        {
1356
                QTextStream output (&solarSystemConfigurationFile);
×
1357
                bool appendedAtLeastOne = false;
×
1358

1359
                for (auto object : objectList)
×
1360
                {
1361
                        if (!object.contains("section_name"))
×
1362
                                continue;
×
1363

1364
                        QString sectionName = object.value("section_name").toString();
×
1365
                        if (sectionName.isEmpty())
×
1366
                                continue;
×
1367
                        object.remove("section_name");
×
1368

1369
                        QString name = object.value("name").toString();
×
1370
                        if (name.isEmpty())
×
1371
                                continue;
×
1372

1373
                        output << StelUtils::getEndLineChar() << QString("[%1]").arg(sectionName); // << StelUtils::getEndLineChar();
×
1374
                        QHash<QString, QVariant>::const_iterator i=object.cbegin();
×
1375
                        while (i != object.cend())
×
1376
                        {
1377
                                // formatting strings
1378
                                output << QString("%1 = %2").arg(i.key(), width).arg(i.value().toString()); // << StelUtils::getEndLineChar();
×
1379
                                ++i;
×
1380
                        }
1381

1382
                        extraData.clear();
×
1383
                        const int mpn = object.value("minor_planet_number").toInt();
×
1384
                        if (mpn==0)
×
1385
                        {
1386
                                // this is a minor planet
1387
                                DiscoveryCircumstances dc = numberedMinorPlanets.value(mpn, DiscoveryCircumstances("",""));
×
1388
                                if (!dc.first.isEmpty())
×
1389
                                {
1390
                                        extraData.append(DiscoveryCircumstances("discovery", dc.first));
×
1391
                                        extraData.append(DiscoveryCircumstances("discoverer", dc.second));
×
1392
                                }
1393
                        }
×
1394
                        else
1395
                        {
1396
                                // this is a comet
1397
                                QString ckey = name.split("/").at(0).trimmed();
×
1398
                                CometDiscoveryData comet;
×
1399
                                if (cometCrossref.contains(ckey))
×
1400
                                {
1401
                                        // standard designation [1P]
1402
                                        comet = cometCrossref.value(ckey);
×
1403
                                        // add IAU designation in addition to the old-style designation
1404
                                        if (!comet.date_code.isEmpty())
×
1405
                                                extraData.append(DiscoveryCircumstances("iau_designation", comet.date_code));
×
1406
                                }
1407
                                else
1408
                                        comet = cometCrossref.value(name.split("(").at(0).trimmed()); // IAU designation [P/1682 Q1]
×
1409

1410
                                if (!comet.perihelion_code.isEmpty())
×
1411
                                        extraData.append(DiscoveryCircumstances("perihelion_code", comet.perihelion_code));
×
1412
                                if (!comet.discovery_code.isEmpty())
×
1413
                                        extraData.append(DiscoveryCircumstances("discovery_code", comet.discovery_code));
×
1414
                                if (!comet.discovery_date.isEmpty())
×
1415
                                        extraData.append(DiscoveryCircumstances("discovery", comet.discovery_date));
×
1416
                                if (!comet.discoverer.isEmpty())
×
1417
                                        extraData.append(DiscoveryCircumstances("discoverer", comet.discoverer));
×
1418
                        }
×
1419
                        for (const auto &ed : extraData)
×
1420
                        {
1421
                                // formatting strings
1422
                                output << QString("%1 = %2").arg(ed.first, width).arg(ed.second); // << StelUtils::getEndLineChar();
×
1423
                        }
1424

1425
                        output.flush();
×
1426
                        qDebug() << "Appended successfully" << sectionName;
×
1427
                        appendedAtLeastOne = true;
×
1428
                }
×
1429

1430
                solarSystemConfigurationFile.close();
×
1431
                qDebug() << "appendToSolarSystemConfigurationFile appended: " << appendedAtLeastOne;
×
1432

1433
                return appendedAtLeastOne;
×
1434
        }
×
1435
        else
1436
        {
1437
                qDebug() << "Unable to open for writing" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
1438
                return false;
×
1439
        }
1440
}
×
1441

1442
bool SolarSystemEditor::appendToSolarSystemConfigurationFile(SsoElements object)
×
1443
{
1444
        if (!object.contains("section_name") || object.value("section_name").toString().isEmpty())
×
1445
        {
1446
                qDebug() << "appendToSolarSystemConfigurationFile(): Invalid object:" << object;
×
1447
                return false;
×
1448
        }
1449

1450
        QList<SsoElements> list;
×
1451
        list << object;
×
1452
        return appendToSolarSystemConfigurationFile(list);
×
1453
}
×
1454

1455
bool SolarSystemEditor::updateSolarSystemConfigurationFile(QList<SsoElements> objectList, UpdateFlags flags)
×
1456
{
1457
        if (objectList.isEmpty())
×
1458
        {
1459
                //Empty lists can be added without any problem. :)
1460
                qWarning() << "updateSolarSystemConfigurationFile(): The source list is empty.";
×
1461
                return true;
×
1462
        }
1463

1464
        //Check if the configuration file exists
1465
        if (!QFile::exists(customSolarSystemFilePath))
×
1466
        {
1467
                qDebug() << "Can't update ssystem.ini: Unable to find" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
1468
                return false;
×
1469
        }
1470

1471
        QSettings solarSystem(customSolarSystemFilePath, StelIniFormat);
×
1472
        if (solarSystem.status() != QSettings::NoError)
×
1473
        {
1474
                qDebug() << "Error opening ssystem.ini:" << QDir::toNativeSeparators(customSolarSystemFilePath);
×
1475
                return false;
×
1476
        }
1477

1478
        QStringList existingSections = solarSystem.childGroups();
×
1479
        QHash<QString,QString> loadedObjects = listAllLoadedSsoIdentifiers();
×
1480
        //TODO: Move to constructor?
1481
        // This list of elements gets temporarily deleted.
1482
        // GZ: Note that the original implementation assumed that the coord_func could ever change. This is not possible at least in 0.13 and later:
1483
        // ell_orbit is used for moons (distances in km) while kepler_orbit (comet_orbit) is used for minor bodies around the sun.
1484
        static const QStringList orbitalElementsKeys = {
1485
                "coord_func",
1486
                "orbit_ArgOfPericenter",
1487
                "orbit_AscendingNode",
1488
                "orbit_Eccentricity",
1489
                "orbit_Epoch",
1490
// GZ It seems to have been an error to include these.  They might simply be updated without prior deletion.
1491
//                "orbit_good",
1492
//                "dust_lengthfactor",
1493
//                "dust_brightnessfactor",
1494
//                "dust_widthfactor",
1495
                "orbit_Inclination",
1496
                "orbit_LongOfPericenter",
1497
                "orbit_MeanAnomaly",
1498
                "orbit_MeanLongitude",
1499
                "orbit_MeanMotion",
1500
                "orbit_PericenterDistance",
1501
                "orbit_Period",
1502
                "orbit_SemiMajorAxis",
1503
                "orbit_TimeAtPericenter"};
×
1504

1505
        qDebug() << "Updating objects...";
×
1506
        for (auto object : objectList)
×
1507
        {
1508
                QString name = object.value("name").toString();
×
1509
                if (name.isEmpty())
×
1510
                        continue;
×
1511

1512
                QString sectionName = object.value("section_name").toString();
×
1513
                if (sectionName.isEmpty())
×
1514
                        continue;
×
1515
                object.remove("section_name");
×
1516

1517
                if (loadedObjects.contains(name))
×
1518
                {
1519
                        if (sectionName != loadedObjects.value(name))
×
1520
                        {
1521
                                //Is this a name conflict between an asteroid and a moon?
1522
                                QString currentParent = solarSystem.value(loadedObjects.value(name) + "/parent").toString();
×
1523
                                QString newParent = object.value("parent").toString();
×
1524
                                if (newParent != currentParent)
×
1525
                                {
1526
                                        name.append('*');
×
1527
                                        object.insert("name", name);
×
1528

1529
                                        if (!existingSections.contains(sectionName))
×
1530
                                        {
1531
                                                solarSystem.beginGroup(sectionName);
×
1532
                                                QHash<QString, QVariant>::const_iterator i=object.cbegin();
×
1533
                                                while (i != object.cend())
×
1534
                                                {
1535
                                                        solarSystem.setValue(i.key(), i.value());
×
1536
                                                        ++i;
×
1537
                                                }
1538
                                                solarSystem.endGroup();
×
1539
                                        }
1540
                                }
1541
                                else
1542
                                {
1543
                                        //If the parent is the same, update that object
1544
                                        sectionName = loadedObjects.value(name);
×
1545
                                }
1546
                        }
×
1547
                }
1548
                else
1549
                {
1550
                        qDebug() << "Skipping update of" << sectionName << ", as no object with this name exists.";
×
1551
                        continue;
×
1552
                }
1553

1554
                solarSystem.beginGroup(sectionName);
×
1555

1556
                if (flags.testFlag(UpdateNameAndNumber))
×
1557
                {
1558
                        updateSsoProperty(solarSystem, object, "name");
×
1559
                        updateSsoProperty(solarSystem, object, "minor_planet_number");
×
1560
                        updateSsoProperty(solarSystem, object, "iau_designation");
×
1561
                        // Comet codes
1562
                        updateSsoProperty(solarSystem, object, "date_code");
×
1563
                        updateSsoProperty(solarSystem, object, "perihelion_code");
×
1564
                        updateSsoProperty(solarSystem, object, "discovery_code");
×
1565
                        // Discovery Circumstances
1566
                        updateSsoProperty(solarSystem, object, "discovery");
×
1567
                        updateSsoProperty(solarSystem, object, "discoverer");
×
1568
                }
1569

1570
                if (flags.testFlag(UpdateType))
×
1571
                {
1572
                        updateSsoProperty(solarSystem, object, "type");
×
1573
                }
1574

1575
                if (flags.testFlag(UpdateOrbitalElements))
×
1576
                {
1577
                        //Remove all orbital elements first, in case
1578
                        //the new ones use another coordinate function
1579
                        // GZ This seems completely useless now. Type of orbit will not change as it is always kepler_orbit.
1580
                        //for (const auto &key : orbitalElementsKeys)
1581
                        //{
1582
                        //        solarSystem.remove(key);
1583
                        //}
1584

1585
                        for (const auto &key : orbitalElementsKeys)
×
1586
                        {
1587
                                updateSsoProperty(solarSystem, object, key);
×
1588
                        }
1589
                }
1590

1591
                if (flags.testFlag(UpdateMagnitudeParameters))
×
1592
                {
1593
                        if (object.contains("absolute_magnitude") && object.contains("slope_parameter"))
×
1594
                        {
1595
                                QString type = solarSystem.value("type").toString();
×
1596
                                if (type == "asteroid" || type == "plutino" || type == "cubewano" || type == "scattered disc object" || type == "sednoid" || type == "comet" )
×
1597
                                {
1598
                                        updateSsoProperty(solarSystem, object, "absolute_magnitude");
×
1599
                                        updateSsoProperty(solarSystem, object, "slope_parameter");
×
1600
                                }
1601
                                else
1602
                                {
1603
                                        qWarning() << "cannot update magnitude for type " << type;
×
1604
                                }
1605
                        }
×
1606
                }
1607

1608
                solarSystem.endGroup();
×
1609
                qDebug() << "Updated successfully" << sectionName;
×
1610
        }
×
1611

1612
        return true;
×
1613
}
×
1614

1615
void SolarSystemEditor::updateSsoProperty(QSettings & settings, SsoElements & properties, QString key)
×
1616
{
1617
        if (properties.contains(key))
×
1618
        {
1619
                settings.setValue(key, properties.value(key));
×
1620
        }
1621
}
×
1622

1623
QString SolarSystemEditor::convertToGroupName(QString &name, int minorPlanetNumber)
×
1624
{
1625
        //TODO: Should I remove all non-alphanumeric, or only the obviously problematic?
1626
        QString groupName(name);
×
1627
        groupName.remove('\\');
×
1628
        groupName.remove('/');
×
1629
        groupName.remove('#');
×
1630
        groupName.remove(' ');
×
1631
        groupName.remove('-');
×
1632
        groupName = groupName.toLower();
×
1633

1634
        //To prevent mix-up between asteroids and satellites:
1635
        //insert the minor planet number in the section name
1636
        //(if an asteroid is named, it must be numbered)
1637
        if (minorPlanetNumber)
×
1638
        {
1639
                groupName.prepend(QString::number(minorPlanetNumber));
×
1640
        }
1641

1642
        return groupName;
×
1643
}
×
1644

1645
QString SolarSystemEditor::fixGroupName(QString &name)
×
1646
{
1647
        QString groupName(name);
×
1648
        groupName.replace("%25", "%");
×
1649
        groupName.replace("%28", "(");
×
1650
        groupName.replace("%29", ")");
×
1651
        return groupName;
×
1652
}
×
1653

1654
int SolarSystemEditor::unpackDayOrMonthNumber(QChar digit)
×
1655
{
1656
        //0-9, 0 is an invalid value in the designed use of this function.
1657
        if (digit.isDigit())
×
1658
        {
1659
                return digit.digitValue();
×
1660
        }
1661

1662
        if (digit.isUpper())
×
1663
        {
1664
                char letter = digit.toLatin1();
×
1665
                if ((letter < 'A') || (letter > 'V'))
×
1666
                        return 0;
×
1667
                return (10 + (letter - 'A'));
×
1668
        }
1669
        else
1670
        {
1671
                return -1;
×
1672
        }
1673
}
1674

1675
int SolarSystemEditor::unpackYearNumber (QChar prefix, int lastTwoDigits)
22✔
1676
{
1677
        int year = lastTwoDigits;
22✔
1678
        switch (prefix.toLatin1())
22✔
1679
        {
1680
                case 'I':
1✔
1681
                        year += 1800;
1✔
1682
                        break;
1✔
1683
                case 'J':
12✔
1684
                        year += 1900;
12✔
1685
                        break;
12✔
1686
                case 'K':
9✔
1687
                default:
1688
                        year += 2000;
9✔
1689
        }
1690
        return year;
22✔
1691
}
1692

1693
//Can be used both for minor planets and comets with no additional modification,
1694
//as the regular expression for comets will match only capital letters.
1695
int SolarSystemEditor::unpackAlphanumericNumber (QChar prefix, int lastDigit)
26✔
1696
{
1697
        int cycleCount = lastDigit;
26✔
1698
        if (prefix.isDigit())
26✔
1699
                cycleCount += prefix.digitValue() * 10;
16✔
1700
        else if (prefix.isLetter() && prefix.isUpper())
10✔
1701
                cycleCount += (10 + prefix.toLatin1() - QChar('A').toLatin1()) * 10;
8✔
1702
        else if (prefix.isLetter() && prefix.isLower())
2✔
1703
                cycleCount += (10 + prefix.toLatin1() - QChar('a').toLatin1()) * 10 + 26*10;
2✔
1704
        else
1705
                cycleCount = 0; //Error
×
1706

1707
        return cycleCount;
26✔
1708
}
1709

1710
QString SolarSystemEditor::unpackMinorPlanetIAUDesignation (const QString &packedDesignation)
17✔
1711
{
1712
        static const QRegularExpression packedFormat("^([IJK])(\\d\\d)([A-Z])([\\dA-Za-z])(\\d)([A-Z])$");
17✔
1713
        QRegularExpressionMatch pfMatch;
17✔
1714
        if (packedDesignation.indexOf(packedFormat, 0, &pfMatch) != 0)
17✔
1715
        {
1716
                static const QRegularExpression packedSurveyDesignation("^(PL|T1|T2|T3)S(\\d+)$");
4✔
1717
                QRegularExpressionMatch psMatch = packedSurveyDesignation.match(packedDesignation);
4✔
1718
                if (psMatch.hasMatch())
4✔
1719
                {
1720
                        int number = psMatch.captured(2).toInt();
4✔
1721
                        if (psMatch.captured(1) == "PL")
4✔
1722
                                return QString("%1 P-L").arg(number);
1✔
1723
                        else if (psMatch.captured(1) == "T1")
3✔
1724
                                return QString("%1 T-1").arg(number);
1✔
1725
                        else if (psMatch.captured(1) == "T2")
2✔
1726
                                return QString("%1 T-2").arg(number);
1✔
1727
                        else
1728
                                return QString("%1 T-3").arg(number);
1✔
1729
                        //TODO: Are there any other surveys?
1730
                }
1731
                else
1732
                        return QString();
×
1733
        }
4✔
1734

1735
        //Year
1736
        QChar yearPrefix = pfMatch.captured(1).at(0);
13✔
1737
        int yearLastTwoDigits = pfMatch.captured(2).toInt();
13✔
1738
        int year = unpackYearNumber(yearPrefix, yearLastTwoDigits);
13✔
1739

1740
        //Letters
1741
        QString halfMonthLetter = pfMatch.captured(3);
13✔
1742
        QString secondLetter = pfMatch.captured(6);
13✔
1743

1744
        //Second letter cycle count
1745
        QChar cycleCountPrefix = pfMatch.captured(4).at(0);
13✔
1746
        int cycleCountLastDigit = pfMatch.captured(5).toInt();
13✔
1747
        int cycleCount = unpackAlphanumericNumber(cycleCountPrefix, cycleCountLastDigit);
13✔
1748

1749
        //Assemble the unpacked IAU designation
1750
        QString result = QString("%1 %2%3").arg(year).arg(halfMonthLetter, secondLetter);
26✔
1751
        if (cycleCount != 0)
13✔
1752
        {
1753
                result.append(QString::number(cycleCount));
11✔
1754
        }
1755

1756
        return result;
13✔
1757
}
17✔
1758

1759
// Should be complete as of 08/2023.
1760
QString SolarSystemEditor::unpackCometIAUDesignation (const QString &packedDesignation)
9✔
1761
{
1762
        Q_ASSERT(packedDesignation.length()==7);
9✔
1763
        static const QRegularExpression packedFormat("^([IJK])(\\d\\d)([A-Z])([\\dA-Za-z])(\\d)([A-Za-z0])$");
9✔
1764
        QRegularExpressionMatch pfMatch = packedFormat.match(packedDesignation);
9✔
1765

1766
        //Year
1767
        const QChar yearPrefix = pfMatch.captured(1).at(0);
9✔
1768
        const int yearLastTwoDigits = pfMatch.captured(2).toInt();
9✔
1769
        const int year = unpackYearNumber(yearPrefix, yearLastTwoDigits);
9✔
1770

1771
        //Letters
1772
        QString halfMonthLetter = pfMatch.captured(3);
9✔
1773
        QString fragmentLetter = pfMatch.captured(6); // this is either lowercase for a fragment, or a second character for the week code.
9✔
1774
        if (fragmentLetter != "0" && fragmentLetter.isUpper())
9✔
1775
                halfMonthLetter.append(fragmentLetter); // in this case there are two letters. Presumably a renamed preliminary asteroid code?
3✔
1776

1777
        int cycleCount = unpackAlphanumericNumber(pfMatch.captured(4).at(0), pfMatch.captured(5).toInt());
9✔
1778

1779
        //Assemble the unpacked IAU designation
1780
        QString result = QString("%1 %2%3").arg(QString::number(year), halfMonthLetter, cycleCount>0 ? QString::number(cycleCount) : "");
18✔
1781
        if (fragmentLetter != "0" && fragmentLetter.isLower())
9✔
1782
        {
1783
                result.append(QString("-%1").arg(fragmentLetter.toUpper()));
2✔
1784
        }
1785

1786
        return result;
18✔
1787
}
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc