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

Stellarium / stellarium / 17068063291

19 Aug 2025 11:22AM UTC coverage: 11.766%. Remained the same
17068063291

push

github

alex-w
Reformatting

14706 of 124990 relevant lines covered (11.77%)

18303.49 hits per line

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

0.0
/src/core/modules/StarMgr.cpp
1
/*
2
 * Stellarium
3
 * Copyright (C) 2002 Fabien Chereau
4
 *
5
 * The big star catalogue extension to Stellarium:
6
 * Author and Copyright: Johannes Gajdosik, 2006
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * as published by the Free Software Foundation; either version 2
11
 * of the License, or (at your option) any later version.
12
 *
13
 * This program is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with this program; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
21
 */
22

23

24
#include "StelProjector.hpp"
25
#include "StarMgr.hpp"
26
#include "StelObject.hpp"
27
#include "StelTexture.hpp"
28

29
#include "StelTranslator.hpp"
30
#include "StelGeodesicGrid.hpp"
31
#include "StelApp.hpp"
32
#include "StelTextureMgr.hpp"
33
#include "StelObjectMgr.hpp"
34
#include "StelLocaleMgr.hpp"
35
#include "StelSkyCultureMgr.hpp"
36
#include "StelFileMgr.hpp"
37
#include "StelModuleMgr.hpp"
38
#include "StelCore.hpp"
39
#include "StelPainter.hpp"
40
#include "StelJsonParser.hpp"
41
#include "ZoneArray.hpp"
42
#include "StelSkyDrawer.hpp"
43
#include "StelModuleMgr.hpp"
44
#include "ConstellationMgr.hpp"
45
#include "Planet.hpp"
46
#include "StelUtils.hpp"
47
#include "StelHealpix.hpp"
48
#include "StelObject.hpp"
49

50
#include <QGlobalStatic>
51
#include <QTextStream>
52
#include <QFile>
53
#include <QBuffer>
54
#include <QSettings>
55
#include <QString>
56
#include <QRegularExpression>
57
#include <QDebug>
58
#include <QFileInfo>
59
#include <QDir>
60
#include <QCryptographicHash>
61

62
#include <cstdlib>
63

64
Q_GLOBAL_STATIC(QStringList, spectral_array);
×
65
Q_GLOBAL_STATIC(QStringList, component_array);
×
66
Q_GLOBAL_STATIC(QStringList, objtype_array);
×
67

68
// This number must be incremented each time the content or file format of the stars catalogs change
69
// It can also be incremented when the defaultStarsConfig.json file change.
70
// It should always match the version field of the defaultStarsConfig.json file
71
static const int StarCatalogFormatVersion = 25;
72

73
// Initialise statics
74
bool StarMgr::flagSciNames = true;
75
bool StarMgr::flagAdditionalStarNames = true;
76
bool StarMgr::flagDesignations = false;
77
bool StarMgr::flagDblStarsDesignation = false;
78
bool StarMgr::flagVarStarsDesignation = false;
79
bool StarMgr::flagHIPDesignation = false;
80

81
QHash<StarId,QString> StarMgr::commonNamesMap;
82
QHash<StarId,QString> StarMgr::commonNamesI18nMap;
83
QHash<QString,StarId> StarMgr::commonNamesI18nUppercaseIndex;
84
QHash<QString,StarId> StarMgr::commonNamesUppercaseIndex;
85

86
// Cultural names: We must store all data here, and have one common index to native names&spelling, pronunciation, english and user-language spelling
87
QMultiHash<StarId, StelObject::CulturalName> StarMgr::culturalNamesMap; // cultural names
88
QMultiMap<QString, StarId> StarMgr::culturalNamesUppercaseIndex; // reverse mappings of uppercased names. For names, unfortunately multiple results are possible!
89

90
QHash<StarId,QString> StarMgr::sciDesignationsMap;      // Bayer/Flamsteed. TODO: Convert map target to QStringList?
91
QHash<QString,StarId> StarMgr::sciDesignationsIndex;
92
QHash<StarId,QString> StarMgr::sciExtraDesignationsMap; // Other sci designations. TODO: Convert map target to QStringList?
93
QHash<QString,StarId> StarMgr::sciExtraDesignationsIndex;
94
QHash<StarId, varstar> StarMgr::varStarsMap;
95
QHash<QString, StarId> StarMgr::varStarsIndex;
96
QHash<StarId, wds> StarMgr::wdsStarsMap;
97
QHash<QString, StarId> StarMgr::wdsStarsIndex;
98
QMap<QString, crossid> StarMgr::crossIdMap;
99
QHash<int, StarId> StarMgr::saoStarsIndex;
100
QHash<int, StarId> StarMgr::hdStarsIndex;
101
QHash<int, StarId> StarMgr::hrStarsIndex;
102
QHash<StarId, QString> StarMgr::referenceMap;
103
QHash<StarId, binaryorbitstar> StarMgr::binaryOrbitStarMap;
104

105
QStringList initStringListFromFile(const QString& file_name)
×
106
{
107
        QStringList list;
×
108
        QFile f(file_name);
×
109
        if (f.open(QIODevice::ReadOnly | QIODevice::Text))
×
110
        {
111
                while (!f.atEnd())
×
112
                {
113
                        QString s = QString::fromUtf8(f.readLine());
×
114
                        s.chop(1);
×
115
                        list << s;
×
116
                }
×
117
                f.close();
×
118
        }
119
        return list;
×
120
}
×
121

122
QString StarMgr::convertToSpectralType(int index)
×
123
{
124
        if (index < 0 || index >= spectral_array->size())
×
125
        {
126
                qDebug() << "convertToSpectralType: bad index: " << index << ", max: " << spectral_array->size();
×
127
                return "";
×
128
        }
129
        return spectral_array->at(index);
×
130
}
131

132
QString StarMgr::convertToComponentIds(int index)
×
133
{
134
        if (index < 0 || index >= component_array->size())
×
135
        {
136
                qDebug() << "convertToComponentIds: bad index: " << index << ", max: " << component_array->size();
×
137
                return "";
×
138
        }
139
        return component_array->at(index);
×
140
}
141

142
QString StarMgr::convertToOjectTypes(int index)
×
143
{
144
        if (index < 0 || index >= objtype_array->size())
×
145
        {
146
                qDebug() << "convertToObjTypeIds: bad index: " << index << ", max: " << objtype_array->size();
×
147
                return "";
×
148
        }
149
        return objtype_array->at(index).trimmed();
×
150
}
151

152

153
void StarMgr::initTriangle(int lev,int index, const Vec3f &c0, const Vec3f &c1, const Vec3f &c2)
×
154
{
155
        gridLevels[lev]->initTriangle(index,c0,c1,c2);
×
156
}
×
157

158

159
StarMgr::StarMgr(void)
×
160
        : StelObjectModule()
161
        , flagStarName(false)
×
162
        , labelsAmount(0.)
×
163
        , gravityLabel(false)
×
164
        , maxGeodesicGridLevel(-1)
×
165
        , lastMaxSearchLevel(-1)
×
166
        , hipIndex(new HipIndexStruct[NR_OF_HIP+1])
×
167
{
168
        setObjectName("StarMgr");
×
169
        objectMgr = GETSTELMODULE(StelObjectMgr);
×
170
        Q_ASSERT(objectMgr);
×
171
}
×
172

173
/*************************************************************************
174
 Reimplementation of the getCallOrder method
175
*************************************************************************/
176
double StarMgr::getCallOrder(StelModuleActionName actionName) const
×
177
{
178
        if (actionName==StelModule::ActionDraw)
×
179
                return StelApp::getInstance().getModuleMgr().getModule("ConstellationMgr")->getCallOrder(actionName)+10;
×
180
        return 0;
×
181
}
182

183

184
StarMgr::~StarMgr(void)
×
185
{
186
        for (auto* z : std::as_const(gridLevels))
×
187
                delete z;
×
188
        gridLevels.clear();
×
189
        if (hipIndex)
×
190
                delete[] hipIndex;
×
191
}
×
192

193
// Allow untranslated name here if set in constellationMgr!
194
QString StarMgr::getCommonNameI18n(StarId hip)
×
195
{
196
        return commonNamesI18nMap.value(hip, QString());
×
197
}
198

199
// Get the cultural names for a star with a specified
200
// Hipparcos or Gaia catalogue number.
201
// @param hip The Hipparcos/Gaia number of star
202
// @return cultural names of star
203
QList<StelObject::CulturalName> StarMgr::getCulturalNames(StarId hip)
×
204
{
205
        // As returned, the latest added comes first!
206
        // Our SC JSONs are typically human generated, with most common first, so reverse the results.
207

208
        QList<StelObject::CulturalName>cNames=culturalNamesMap.values(hip);
×
209
        std::reverse(cNames.begin(), cNames.end());
×
210
        return cNames;
×
211
}
×
212

213
QString StarMgr::getCommonEnglishName(StarId hip)
×
214
{
215
        return commonNamesMap.value(hip, QString());
×
216
}
217

218
QString StarMgr::getSciDesignation(StarId hip)
×
219
{
220
        return sciDesignationsMap.value(hip, QString());
×
221
}
222

223
QString StarMgr::getSciExtraDesignation(StarId hip)
×
224
{
225
        return sciExtraDesignationsMap.value(hip, QString());
×
226
}
227

228
QString StarMgr::getCrossIdentificationDesignations(const QString &hip)
×
229
{
230
        Q_ASSERT(hip==hip.trimmed());
×
231
        QStringList designations;
×
232
        //auto cr = crossIdMap.find(hip);
233
        //if (cr==crossIdMap.end() && hip.right(1).toUInt()==0) // What was the purpose of this? check for 0 (bug) or space (just trim!)?
234
        //        cr = crossIdMap.find(hip.left(hip.size()-1));
235

236
        if (crossIdMap.contains(hip))
×
237
        {
238
                crossid crossIdData = crossIdMap.value(hip);
×
239
                if (crossIdData.hr>0)
×
240
                        designations << QString("HR %1").arg(crossIdData.hr);
×
241

242
                if (crossIdData.hd>0)
×
243
                        designations << QString("HD %1").arg(crossIdData.hd);
×
244

245
                if (crossIdData.sao>0)
×
246
                        designations << QString("SAO %1").arg(crossIdData.sao);
×
247
        }
248

249
        return designations.join(" - ");
×
250
}
×
251

252
QString StarMgr::getWdsDesignation(StarId hip)
×
253
{
254
        return (wdsStarsMap.contains(hip) ? QString("WDS J%1").arg(wdsStarsMap.value(hip).designation) : QString());
×
255
}
256

257
int StarMgr::getWdsLastObservation(StarId hip)
×
258
{
259
        return (wdsStarsMap.contains(hip) ? wdsStarsMap.value(hip).observation : 0);
×
260
}
261

262
float StarMgr::getWdsLastPositionAngle(StarId hip)
×
263
{
264
        return (wdsStarsMap.contains(hip) ? wdsStarsMap.value(hip).positionAngle : 0);
×
265
}
266

267
float StarMgr::getWdsLastSeparation(StarId hip)
×
268
{
269
        return (wdsStarsMap.contains(hip) ? wdsStarsMap.value(hip).separation : 0);
×
270
}
271

272
QString StarMgr::getGcvsDesignation(StarId hip)
×
273
{
274
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).designation : QString());
×
275
}
276

277
QString StarMgr::getGcvsVariabilityType(StarId hip)
×
278
{
279
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).vtype : QString());
×
280
}
281

282
float StarMgr::getGcvsMaxMagnitude(StarId hip)
×
283
{
284
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).maxmag : -99.f);
×
285
}
286

287
int StarMgr::getGcvsMagnitudeFlag(StarId hip)
×
288
{
289
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).mflag : 0);
×
290
}
291

292

293
float StarMgr::getGcvsMinMagnitude(StarId hip, bool firstMinimumFlag)
×
294
{
295
        if (varStarsMap.contains(hip))
×
296
        {
297
                varstar var=varStarsMap.value(hip);
×
298
                return (firstMinimumFlag ? var.min1mag : var.min2mag);
×
299
        }
×
300
        return -99.f;
×
301
}
302

303
QString StarMgr::getGcvsPhotometricSystem(StarId hip)
×
304
{
305
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).photosys : QString());
×
306
}
307

308
double StarMgr::getGcvsEpoch(StarId hip)
×
309
{
310
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).epoch : -99.);
×
311
}
312

313
double StarMgr::getGcvsPeriod(StarId hip)
×
314
{
315
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).period : -99.);
×
316
}
317

318
int StarMgr::getGcvsMM(StarId hip)
×
319
{
320
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).Mm : -99);
×
321
}
322

323
QString StarMgr::getGcvsSpectralType(StarId hip)
×
324
{
325
        return (varStarsMap.contains(hip) ? varStarsMap.value(hip).stype : QString());
×
326
}
327

328
binaryorbitstar StarMgr::getBinaryOrbitData(StarId hip)
×
329
{
330
        return binaryOrbitStarMap.value(hip, binaryorbitstar());
×
331
}
332

333
void StarMgr::copyDefaultConfigFile()
×
334
{
335
        try
336
        {
337
                StelFileMgr::makeSureDirExistsAndIsWritable(StelFileMgr::getUserDir()+"/stars/hip_gaia3");
×
338
                starConfigFileFullPath = StelFileMgr::getUserDir()+"/stars/hip_gaia3/starsConfig.json";
×
339
                qDebug() << "Creates file " << QDir::toNativeSeparators(starConfigFileFullPath);
×
340
                QFile::copy(StelFileMgr::getInstallationDir()+"/stars/hip_gaia3/defaultStarsConfig.json", starConfigFileFullPath);
×
341
                QFile::setPermissions(starConfigFileFullPath, QFile::permissions(starConfigFileFullPath) | QFileDevice::WriteOwner);
×
342
        }
343
        catch (std::runtime_error& e)
×
344
        {
345
                qWarning() << e.what();
×
346
                qFatal("Could not create configuration file stars/hip_gaia3/starsConfig.json");
×
347
        }
×
348
}
×
349

350
void StarMgr::init()
×
351
{
352
        QSettings* conf = StelApp::getInstance().getSettings();
×
353
        Q_ASSERT(conf);
×
354

355
        starConfigFileFullPath = StelFileMgr::findFile("stars/hip_gaia3/starsConfig.json", StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::File));
×
356
        if (starConfigFileFullPath.isEmpty())
×
357
        {
358
                qWarning() << "Could not find the starsConfig.json file: will copy the default one.";
×
359
                copyDefaultConfigFile();
×
360
        }
361

362
        QFile fic(starConfigFileFullPath);
×
363
        if(fic.open(QIODevice::ReadOnly))
×
364
        {
365
                starSettings = StelJsonParser::parse(&fic).toMap();
×
366
                fic.close();
×
367
        }
368

369
        // Increment the 1 each time any star catalog file change
370
        if (starSettings.value("version").toInt()!=StarCatalogFormatVersion)
×
371
        {
372
                qWarning() << "Found an old starsConfig.json file, upgrade..";
×
373
                fic.remove();
×
374
                copyDefaultConfigFile();
×
375
                QFile fic2(starConfigFileFullPath);
×
376
                if(fic2.open(QIODevice::ReadOnly))
×
377
                {
378
                        starSettings = StelJsonParser::parse(&fic2).toMap();
×
379
                        fic2.close();
×
380
                }
381
        }
×
382

383
        loadData(starSettings);
×
384

385
        populateStarsDesignations();
×
386
        populateHipparcosLists();
×
387

388
        setFontSize(StelApp::getInstance().getScreenFontSize());
×
389
        connect(&StelApp::getInstance(), SIGNAL(screenFontSizeChanged(int)), this, SLOT(setFontSize(int)));
×
390

391
        setFlagStars(conf->value("astro/flag_stars", true).toBool());
×
392
        setFlagLabels(conf->value("astro/flag_star_name",true).toBool());
×
393
        setLabelsAmount(conf->value("stars/labels_amount",3.).toDouble());
×
394
        setFlagAdditionalNames(conf->value("astro/flag_star_additional_names",true).toBool());
×
395
        setDesignationUsage(conf->value("astro/flag_star_designation_usage", false).toBool());
×
396
        setFlagDblStarsDesignation(conf->value("astro/flag_star_designation_dbl", false).toBool());
×
397
        setFlagVarStarsDesignation(conf->value("astro/flag_star_designation_var", false).toBool());
×
398
        setFlagHIPDesignation(conf->value("astro/flag_star_designation_hip", false).toBool());
×
399

400
        objectMgr->registerStelObjectMgr(this);
×
401
        texPointer = StelApp::getInstance().getTextureManager().createTexture(StelFileMgr::getInstallationDir()+"/textures/pointeur2.png");   // Load pointer texture
×
402

403
        StelApp::getInstance().getCore()->getGeodesicGrid(maxGeodesicGridLevel)->visitTriangles(maxGeodesicGridLevel,initTriangleFunc,this);
×
404
        StelApp *app = &StelApp::getInstance();
×
405
        connect(app, SIGNAL(languageChanged()), this, SLOT(updateI18n()));
×
406
        connect(&app->getSkyCultureMgr(), &StelSkyCultureMgr::currentSkyCultureChanged, this, &StarMgr::updateSkyCulture);
×
407

408
        QString displayGroup = N_("Display Options");
×
409
        addAction("actionShow_Stars", displayGroup, N_("Stars"), "flagStarsDisplayed", "S");
×
410
        addAction("actionShow_Stars_Labels", displayGroup, N_("Stars labels"), "flagLabelsDisplayed", "Alt+S");
×
411
        // Details: https://github.com/Stellarium/stellarium/issues/174
412
        addAction("actionShow_Stars_MagnitudeLimitIncrease", displayGroup, N_("Increase the magnitude limit for stars"), "increaseStarsMagnitudeLimit()");
×
413
        addAction("actionShow_Stars_MagnitudeLimitReduce", displayGroup, N_("Reduce the magnitude limit for stars"), "reduceStarsMagnitudeLimit()");
×
414
}
×
415

416

417
void StarMgr::drawPointer(StelPainter& sPainter, const StelCore* core)
×
418
{
419
        const QList<StelObjectP> newSelected = objectMgr->getSelectedObject("Star");
×
420
        if (!newSelected.empty())
×
421
        {
422
                const StelObjectP obj = newSelected[0];
×
423
                Vec3d pos=obj->getJ2000EquatorialPos(core);
×
424

425
                Vec3f screenpos;
×
426
                // Compute 2D pos and return if outside screen
427
                if (!sPainter.getProjector()->project(pos, screenpos))
×
428
                        return;
×
429

430
                sPainter.setColor(obj->getInfoColor());
×
431
                texPointer->bind();
×
432
                sPainter.setBlending(true);
×
433
                sPainter.drawSprite2dMode(screenpos[0], screenpos[1], 13.f, static_cast<float>(StelApp::getInstance().getAnimationTime())*40.f);
×
434
        }
×
435
}
×
436

437
bool StarMgr::checkAndLoadCatalog(const QVariantMap& catDesc, const bool load)
×
438
{
439
        const bool checked = catDesc.value("checked").toBool();
×
440
        QString catalogFileName = catDesc.value("fileName").toString();
×
441

442
        // See if it is an absolute path, else prepend default path
443
        if (!(StelFileMgr::isAbsolute(catalogFileName)))
×
444
                catalogFileName = "stars/hip_gaia3/"+catalogFileName;
×
445

446
        QString catalogFilePath = StelFileMgr::findFile(catalogFileName);
×
447
        if (catalogFilePath.isEmpty())
×
448
        {
449
                // The file is supposed to be checked, but we can't find it
450
                if (checked)
×
451
                {
452
                        qWarning().noquote() << "Could not find star catalog" << QDir::toNativeSeparators(catalogFileName);
×
453
                        setCheckFlag(catDesc.value("id").toString(), false);
×
454
                }
455
                return false;
×
456
        }
457
        // Possibly fixes crash on Vista
458
        if (!StelFileMgr::isReadable(catalogFilePath))
×
459
        {
460
                qWarning().noquote() << "User does not have permissions to read catalog" << QDir::toNativeSeparators(catalogFilePath);
×
461
                return false;
×
462
        }
463

464
        if (!checked)
×
465
        {
466
                // The file is not checked but we found it, maybe from a previous download/version
467
                qInfo().noquote() << "Found file" << QDir::toNativeSeparators(catalogFilePath) << ", checking md5sum...";
×
468

469
                QFile file(catalogFilePath);
×
470
                if(file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
×
471
                {
472
                        // Compute the MD5 sum
473
                        QCryptographicHash md5Hash(QCryptographicHash::Md5);
×
474
                        const qint64 cat_sz = file.size();
×
475
                        qint64 maxStarBufMd5 = qMin(cat_sz, 9223372036854775807LL);
×
476
                        uchar *cat = maxStarBufMd5 ? file.map(0, maxStarBufMd5) : Q_NULLPTR;
×
477
                        if (!cat)
×
478
                        {
479
                                // The OS was not able to map the file, revert to slower not mmap based method
480
                                static const qint64 maxStarBufMd5 = 1024*1024*8;
481
                                char* mmd5buf = static_cast<char*>(malloc(maxStarBufMd5));
×
482
                                while (!file.atEnd())
×
483
                                {
484
                                        qint64 sz = file.read(mmd5buf, maxStarBufMd5);
×
485
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
486
                                        md5Hash.addData(QByteArrayView(mmd5buf, static_cast<int>(sz)));
×
487
#else
488
                                        md5Hash.addData(mmd5buf, static_cast<int>(sz));
489
#endif
490
                                }
491
                                free(mmd5buf);
×
492
                        }
493
                        else
494
                        {
495
#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
496
                                md5Hash.addData(QByteArrayView(reinterpret_cast<const char*>(cat), static_cast<int>(cat_sz)));
×
497
#else
498
                                md5Hash.addData(reinterpret_cast<const char*>(cat), static_cast<int>(cat_sz));
499
#endif
500
                                file.unmap(cat);
×
501
                        }
502
                        file.close();
×
503
                        if (md5Hash.result().toHex()!=catDesc.value("checksum").toByteArray())
×
504
                        {
505
                                qWarning().noquote() << "Error: File" << QDir::toNativeSeparators(catalogFileName) << "is corrupt, MD5 mismatch! Found" << md5Hash.result().toHex() << "expected" << catDesc.value("checksum").toByteArray();
×
506
                                file.remove();
×
507
                                return false;
×
508
                        }
509
                        qInfo() << "MD5 sum correct!";
×
510
                        setCheckFlag(catDesc.value("id").toString(), true);
×
511
                }
×
512
        }
×
513

514
        if (load)
×
515
        {
516
                ZoneArray* z = ZoneArray::create(catalogFilePath, true);
×
517
                if (z)
×
518
                {
519
                        if (z->level<gridLevels.size())
×
520
                        {
521
                                qWarning().noquote() << QDir::toNativeSeparators(catalogFileName) << ", " << z->level << ": duplicate level";
×
522
                                delete z;
×
523
                                return true;
×
524
                        }
525
                        Q_ASSERT(z->level==maxGeodesicGridLevel+1);
×
526
                        Q_ASSERT(z->level==gridLevels.size());
×
527
                        ++maxGeodesicGridLevel;
×
528
                        gridLevels.append(z);
×
529
                }
530
                return true;
×
531
        }
532
        else
533
        {
534
                qWarning().noquote() << "Star catalog: " << QDir::toNativeSeparators(catalogFileName) << "is found but not loaded because at least one of the lower levels is missing!";
×
535
                return false;
×
536
        }
537
}
×
538

539
void StarMgr::setCheckFlag(const QString& catId, bool b)
×
540
{
541
        // Update the starConfigFileFullPath file to take into account that we now have a new catalog
542
        int idx=0;
×
543
        for (const auto& catV : std::as_const(catalogsDescription))
×
544
        {
545
                ++idx;
×
546
                QVariantMap m = catV.toMap();
×
547
                if (m.value("id").toString()!=catId)
×
548
                        continue;
×
549
                const bool checked = m.value("checked").toBool();
×
550
                if (checked==b)
×
551
                        return;
×
552
                m["checked"]=b;
×
553
                catalogsDescription[idx-1]=m;                
×
554
        }
×
555
        starSettings["catalogs"]=catalogsDescription;
×
556
        QFile tmp(starConfigFileFullPath);
×
557
        if(tmp.open(QIODevice::WriteOnly))
×
558
        {
559
                StelJsonParser::write(starSettings, &tmp);
×
560
                tmp.close();
×
561
        }
562
}
×
563

564
void StarMgr::loadData(const QVariantMap &starsConfig)
×
565
{
566
        // Please do not init twice:
567
        Q_ASSERT(maxGeodesicGridLevel < 0);
×
568

569
        qInfo() << "Loading star data ...";
×
570

571
        catalogsDescription = starsConfig.value("catalogs").toList();
×
572
        bool isSuccessing = true;
×
573
        foreach (const QVariant& catV, catalogsDescription)
×
574
        {
575
                QVariantMap m = catV.toMap();
×
576
                isSuccessing = checkAndLoadCatalog(m, isSuccessing);
×
577
        }
×
578

579
        for (int i=0; i<=NR_OF_HIP; i++)
×
580
        {
581
                hipIndex[i].a = Q_NULLPTR;
×
582
                hipIndex[i].z = Q_NULLPTR;
×
583
                hipIndex[i].s = Q_NULLPTR;
×
584
        }
585
        for (auto* z : std::as_const(gridLevels))
×
586
                z->updateHipIndex(hipIndex);
×
587

588
        const QString cat_hip_sp_file_name = starsConfig.value("hipSpectralFile").toString();
×
589
        if (cat_hip_sp_file_name.isEmpty())
×
590
        {
591
                qWarning() << "ERROR: stars:cat_hip_sp_file_name not found";
×
592
        }
593
        else
594
        {
595
                QString tmpFic = StelFileMgr::findFile("stars/hip_gaia3/" + cat_hip_sp_file_name);
×
596
                if (tmpFic.isEmpty())
×
597
                        qWarning() << "ERROR while loading data from" << QDir::toNativeSeparators(("stars/hip_gaia3/" + cat_hip_sp_file_name));
×
598
                else
599
                        *spectral_array = initStringListFromFile(tmpFic);
×
600
        }
×
601

602
        const QString cat_objtype_file_name = starsConfig.value("objecttypesFile").toString();
×
603
        if (cat_objtype_file_name.isEmpty())
×
604
        {
605
                qWarning() << "ERROR: stars:cat_objtype_file_name not found";
×
606
        }
607
        else
608
        {
609
                QString tmpFic = StelFileMgr::findFile("stars/hip_gaia3/" + cat_objtype_file_name);
×
610
                if (tmpFic.isEmpty())
×
611
                        qWarning() << "ERROR while loading data from" << QDir::toNativeSeparators(("stars/hip_gaia3/" + cat_objtype_file_name));
×
612
                else
613
                        *objtype_array = initStringListFromFile(tmpFic);
×
614
        }
×
615

616
        // create an array with the first element being an empty string, and then contain strings A,B,C,...,Z
617
        component_array->append("");
×
618
        for (int i=0; i<26; i++)
×
619
                component_array->append(QString(QChar('A'+i)));
×
620

621
        lastMaxSearchLevel = maxGeodesicGridLevel;
×
622
        qInfo().noquote() << "Finished loading star catalogue data, max_geodesic_level:" << maxGeodesicGridLevel;
×
623
}
×
624

625
void StarMgr::populateHipparcosLists()
×
626
{
627
        hipparcosStars.clear();
×
628
        hipStarsHighPM.clear();
×
629
        doubleHipStars.clear();
×
630
        variableHipStars.clear();
×
631
        algolTypeStars.clear();
×
632
        classicalCepheidsTypeStars.clear();
×
633
        carbonStars.clear();
×
634
        bariumStars.clear();
×
635
        const int pmLimit = 1000; // arc-second per year!
×
636
        for (int hip=0; hip<=NR_OF_HIP; hip++)
×
637
        {
638
                const Star1 *const s = hipIndex[hip].s;
×
639
                if (s)
×
640
                {
641
                        const SpecialZoneArray<Star1> *const a = hipIndex[hip].a;
×
642
                        const SpecialZoneData<Star1> *const z = hipIndex[hip].z;
×
643
                        StelObjectP so = s->createStelObject(a,z);
×
644
                        hipparcosStars.push_back(so);
×
645
                        QString spectrum = convertToSpectralType(s->getSpInt());
×
646
                        // Carbon stars have spectral type, which start with C letter
647
                        if (spectrum.startsWith("C", Qt::CaseInsensitive))
×
648
                                carbonStars.push_back(so);
×
649

650
                        // Barium stars have spectral class G to K and contains "Ba" string
651
                        if ((spectrum.startsWith("G", Qt::CaseInsensitive) || spectrum.startsWith("K", Qt::CaseInsensitive)) && spectrum.contains("Ba", Qt::CaseSensitive))
×
652
                                bariumStars.push_back(so);
×
653

654
                        if (!getGcvsVariabilityType(s->getHip()).isEmpty())
×
655
                        {
656
                                QPair<StelObjectP, float> sa(so, static_cast<float>(getGcvsPeriod(s->getHip())));
×
657
                                variableHipStars.push_back(sa);
×
658
                                
659
                                QString vartype = getGcvsVariabilityType(s->getHip());
×
660
                                if (vartype.contains("EA"))
×
661
                                        algolTypeStars.push_back(QPair<StelObjectP, float>(so, sa.second));
×
662
                                else if (vartype.contains("DCEP") && !vartype.contains("DCEPS"))
×
663
                                        classicalCepheidsTypeStars.push_back(QPair<StelObjectP, float>(so, sa.second));
×
664
                        }
×
665
                        if (!getWdsDesignation(s->getHip()).isEmpty())
×
666
                        {
667
                                doubleHipStars.push_back(QPair<StelObjectP, float>(so, getWdsLastSeparation(s->getHip())));
×
668
                        }
669
                        float pm = s->getPMTotal();
×
670
                        if (qAbs(pm)>=pmLimit)
×
671
                        {
672
                                QPair<StelObjectP, float> spm(so, pm / 1000.f);  // in arc-seconds per year
×
673
                                hipStarsHighPM.push_back(spm);
×
674
                        }
×
675
                }
×
676
        }
677

678
        qInfo().nospace() << "Lists of stars: " << hipparcosStars.count() << " HIP stars, " << doubleHipStars.count() << " are binaries, " << variableHipStars.count()
×
679
                          << " variable (" << algolTypeStars.count() << " Algol-type, " << classicalCepheidsTypeStars.count() << " Cepheids), " << carbonStars.count()
×
680
                          << " Carbon stars, " << bariumStars.count() << " Barium stars, and " << hipStarsHighPM.count() << " have PM>1000mas/yr.";
×
681
}
×
682

683
// Load common names from file
684
StarMgr::CommonNames StarMgr::loadCommonNames(const QString& commonNameFile) const
×
685
{
686
        CommonNames commonNames;
×
687

688
        if (commonNameFile.isEmpty()) return commonNames;
×
689

690
        qInfo().noquote() << "Loading star names from" << QDir::toNativeSeparators(commonNameFile);
×
691
        QFile cnFile(commonNameFile);
×
692
        if (!cnFile.open(QIODevice::ReadOnly | QIODevice::Text))
×
693
        {
694
                qWarning().noquote() << "Could not open" << QDir::toNativeSeparators(commonNameFile);
×
695
                return commonNames;
×
696
        }
697

698
        int readOk=0;
×
699
        int totalRecords=0;
×
700
        int lineNumber=0;
×
701
        QString record;
×
702
        // Allow empty and comment lines where first char (after optional blanks) is #
703
        static const QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
×
704
        // record structure is delimited with a | character.  We will
705
        // use a QRegularExpression to extract the fields. with white-space padding permitted
706
        // (i.e. it will be stripped automatically) Example record strings:
707
        // "   677|_("Alpheratz")"
708
        // "113368|_("Fomalhaut")"
709
        // Note: Stellarium doesn't support sky cultures made prior to version 25.1 now!
710
        static const QRegularExpression recordRx("^\\s*(\\d+)\\s*\\|[_]*[(]\"(.*)\"[)]\\s*([\\,\\d\\s]*)");
×
711

712
        while(!cnFile.atEnd())
×
713
        {
714
                record = QString::fromUtf8(cnFile.readLine()).trimmed();
×
715
                lineNumber++;
×
716
                if (commentRx.match(record).hasMatch())
×
717
                        continue;
×
718

719
                totalRecords++;
×
720
                QRegularExpressionMatch recMatch=recordRx.match(record);
×
721
                if (!recMatch.hasMatch())
×
722
                {
723
                        qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(commonNameFile)
×
724
                                             << " - record does not match record pattern";
×
725
                        qWarning().noquote() << "Problematic record:" << record;
×
726
                        continue;
×
727
                }
728
                else
729
                {
730
                        // The record is the right format.  Extract the fields
731
                        bool ok;
732
                        StarId hip = recMatch.captured(1).toULongLong(&ok);
×
733
                        if (!ok)
×
734
                        {
735
                                qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(commonNameFile)
×
736
                                                     << " - failed to convert " << recMatch.captured(1) << "to a number";
×
737
                                continue;
×
738
                        }
739
                        QString englishCommonName = recMatch.captured(2).trimmed();
×
740
                        if (englishCommonName.isEmpty())
×
741
                        {
742
                                qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(commonNameFile)
×
743
                                                     << " - empty name field";
×
744
                                continue;
×
745
                        }
746

747
                        const QString englishNameCap = englishCommonName.toUpper();
×
748
                        commonNames.byHIP[hip] = englishCommonName;
×
749
                        commonNames.hipByName[englishNameCap] = hip;
×
750

751
                        QString reference = recMatch.captured(3).trimmed();
×
752
                        if (!reference.isEmpty())
×
753
                        {
754
                                if (referenceMap.contains(hip))
×
755
                                        referenceMap[hip] = referenceMap[hip].append("," + reference);
×
756
                                else
757
                                        referenceMap[hip] = reference;
×
758
                        }
759

760
                        readOk++;
×
761
                }
×
762
        }
×
763
        cnFile.close();
×
764

765
        qInfo().noquote() << "Loaded" << readOk << "/" << totalRecords << "common star names";
×
766
        return commonNames;
×
767
}
×
768

769
// Identify a star HIP/Gaia StarId from its common name and the commonnames list.
770
// @param data the JSON array assigned to the "NAME commonName" entry of the current skyculture's index.json
771
// @param commonName the name given in the common_names entry in the current skyculture's index.json
772
// @param commonNamesIndexToSearchWhileLoading the part of CommonNames that provides HIPO for a given name.
773
void StarMgr::loadCultureSpecificNameForNamedObject(const QJsonArray& data, const QString& commonName,
×
774
                                                    const QMap<QString, int>& commonNamesIndexToSearchWhileLoading,
775
                                                    const QSet<int> &excludedRefs)
776
{
777
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
×
778

779
        const QString commonNameUpper=commonName.toUpper();
×
780
        if (!commonNamesIndexToSearchWhileLoading.contains(commonNameUpper))
×
781
        {
782
                // This may actually not even be a star, so we shouldn't emit any warning, just return
783
                return;
×
784
        }
785
        const int HIP = commonNamesIndexToSearchWhileLoading.value(commonNameUpper);
×
786

787
        for (const QJsonValue& entry : data)
×
788
        {
789
                // See if we have configured unwanted references. We can only attempt to exclude entries which actually have references.
790
                QVariantList refsVariants=entry["references"].toArray().toVariantList();
×
791
                if (!refsVariants.isEmpty())
×
792
                {
793
                        QSet<int> refs;
×
794
                        foreach(const QVariant &v, refsVariants) {
×
795
                            refs << v.value<int>();
×
796
                        }
×
797
                        if (refs.subtract(excludedRefs).isEmpty())
×
798
                                continue;
×
799
                }
×
800

801
                StelObject::CulturalName cName{entry["native"].toString(), entry["pronounce"].toString(), trans.qTranslateStar(entry["pronounce"].toString()),
×
802
                                        entry["transliteration"].toString(), entry["english"].toString(), trans.qTranslateStar(entry["english"].toString()), entry["IPA"].toString(), QString(), QString(), StelObject::CulturalNameSpecial::None};
×
803

804
                //if (culturalNamesMap.contains(HIP))
805
                //        qInfo() << "Adding additional cultural name for HIP" << HIP << ":" <<  cName.native << "/" << cName.pronounceI18n << "/" << cName.translated;
806
                culturalNamesMap.insert(HIP, cName); // add as possibly multiple entry to HIP.
×
807
                if (!cName.native.isEmpty())
×
808
                        culturalNamesUppercaseIndex.insert(cName.native.toUpper(), HIP);
×
809
                if (!cName.pronounceI18n.isEmpty())
×
810
                        culturalNamesUppercaseIndex.insert(cName.pronounceI18n.toUpper(), HIP);
×
811
                if (!cName.transliteration.isEmpty())
×
812
                        culturalNamesUppercaseIndex.insert(cName.transliteration.toUpper(), HIP);
×
813
                if (!cName.translatedI18n.isEmpty())
×
814
                        culturalNamesUppercaseIndex.insert(cName.translatedI18n.toUpper(), HIP);
×
815
        }
×
816
}
×
817

818
// data: the array containing one or more name dicts
819
void StarMgr::loadCultureSpecificNameForStar(const QJsonArray& data, const StarId HIP, const QSet<int> &excludedRefs)
×
820
{
821
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
×
822

823
        for (const QJsonValue& entry : data)
×
824
        {
825
                // See if we have configured unwanted references. We can only attempt to exclude entries which actually have references.
826
                QVariantList refsVariants=entry["references"].toArray().toVariantList();
×
827
                if (!refsVariants.isEmpty())
×
828
                {
829
                        QSet<int> refs;
×
830
                        foreach(const QVariant &v, refsVariants) {
×
831
                            refs << v.value<int>();
×
832
                        }
×
833
                        //qInfo() << "Star" << entry["native"].toString() << "has refs" << refs;
834
                        if (refs.subtract(excludedRefs).isEmpty())
×
835
                        {
836
                                //qInfo() << "Star name" << entry["native"].toString() << "has lost support. Skipping.";
837
                                continue;
×
838
                        }
839
                        //else
840
                        //        qInfo() << "Star" << entry["native"].toString() << "still has refs" << refs.subtract(excludedRefs);
841
                }
×
842

843
                StelObject::CulturalName cName{entry["native"].toString(), entry["pronounce"].toString(), trans.qTranslateStar(entry["pronounce"].toString()),
×
844
                                        entry["transliteration"].toString(), entry["english"].toString(), trans.qTranslateStar(entry["english"].toString()), entry["IPA"].toString()};
×
845
                //if (culturalNamesMap.contains(HIP))
846
                //        qInfo() << "Adding additional cultural name for HIP" << HIP << ":" <<  cName.native << "/" << cName.pronounceI18n << "/" << cName.translated << "/" << cName.translatedI18n;
847
                culturalNamesMap.insert(HIP, cName); // add as possibly multiple entry to HIP.
×
848
                if (!cName.native.isEmpty())
×
849
                        culturalNamesUppercaseIndex.insert(cName.native.toUpper(), HIP);
×
850
                if (!cName.pronounceI18n.isEmpty())
×
851
                        culturalNamesUppercaseIndex.insert(cName.pronounceI18n.toUpper(), HIP);
×
852
                if (!cName.transliteration.isEmpty())
×
853
                        culturalNamesUppercaseIndex.insert(cName.transliteration.toUpper(), HIP);
×
854
                if (!cName.translatedI18n.isEmpty())
×
855
                        culturalNamesUppercaseIndex.insert(cName.translatedI18n.toUpper(), HIP);
×
856
        }
×
857
        //qInfo() << "Skyculture has " << culturalNamesMap.size() << "entries, index has" << culturalNamesIndex.size();
858
}
×
859

860
void StarMgr::loadCultureSpecificNames(const QJsonObject& data, const QMap<QString, int>& commonNamesIndexToSearchWhileLoading, const QSet<int> &excludedRefs)
×
861
{
862
        for (auto it = data.begin(); it != data.end(); ++it)
×
863
        {
864
                const auto key = it.key();
×
865
                // Let's allow Hipparcos and Gaia designations only
866
                if (key.startsWith("HIP "))
×
867
                {
868
                        loadCultureSpecificNameForStar(it.value().toArray(), key.mid(4).toUInt(), excludedRefs);
×
869
                }
870
                else if (key.startsWith("Gaia DR3 "))
×
871
                {
872
                        loadCultureSpecificNameForStar(it.value().toArray(), key.mid(9).toULongLong(), excludedRefs);
×
873
                }
874
                else if (key.startsWith("NAME "))
×
875
                {
876
                        loadCultureSpecificNameForNamedObject(it.value().toArray(), key.mid(5), commonNamesIndexToSearchWhileLoading, excludedRefs);
×
877
                }
878
        }
×
879
}
×
880

881
// Load scientific designations from file
882
void StarMgr::loadSciDesignations(const QString& sciNameFile, QHash<StarId, QString>&map, QHash<QString, StarId>&index)
×
883
{
884
        map.clear();
×
885
        index.clear();
×
886
        qInfo().noquote() << "Loading scientific star designations from" << QDir::toNativeSeparators(sciNameFile);
×
887

888
        QFile snFile(sciNameFile);
×
889
        if (!snFile.open(QIODevice::ReadOnly | QIODevice::Text))
×
890
        {
891
                qWarning().noquote() << "Could not open" << QDir::toNativeSeparators(sciNameFile);
×
892
                return;
×
893
        }
894
        const QStringList& allRecords = QString::fromUtf8(snFile.readAll()).split('\n');
×
895
        snFile.close();
×
896

897
        int readOk=0;
×
898
        int totalRecords=0;
×
899
        int lineNumber=0;
×
900
        // record structure is delimited with a | character. Example record strings:
901
        // " 10819|c_And"
902
        // "113726|1_And"
903
        for (const QString& record : allRecords)
×
904
        {
905
                ++lineNumber;
×
906
                // skip comments and empty lines
907
                if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
×
908
                        continue;
×
909

910
                ++totalRecords;
×
911
                const QStringList& fields = record.split('|');
×
912
                if (fields.size()!=2)
×
913
                {
914
                        qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(sciNameFile)
×
915
                                             << " - record does not match record pattern. Skipping line.";
×
916
                        continue;
×
917
                }
918
                else
919
                {
920
                        // The record is the right format.  Extract the fields
921
                        bool ok;
922
                        StarId hip = fields.at(0).toLongLong(&ok);
×
923
                        if (!ok)
×
924
                        {
925
                                qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(sciNameFile)
×
926
                                                     << " - failed to convert " << fields.at(0) << "to a number";
×
927
                                continue;
×
928
                        }
929

930
                        QString sci_name = fields.at(1).trimmed();
×
931
                        if (sci_name.isEmpty())
×
932
                        {
933
                                qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(sciNameFile)
×
934
                                                     << " - empty name field";
×
935
                                continue;
×
936
                        }
937

938
                        sci_name.replace('_',' ');
×
939
                        // Don't set the main sci name if it's already set - it's additional sci name
940
                        if (map.contains(hip))
×
941
                        {
942
                                map[hip].append(" - " + sci_name);
×
943
                        }
944
                        else
945
                                map.insert(hip, sci_name);
×
946
                        index[sci_name] = hip;
×
947

948
                        ++readOk;
×
949
                }
×
950
        }
×
951
        qInfo().noquote() << "Loaded" << readOk << "/" << totalRecords << "scientific designations";
×
952
}
×
953

954
// Load GCVS from file
955
void StarMgr::loadGcvs(const QString& GcvsFileName)
×
956
{
957
        varStarsMap.clear();
×
958
        varStarsIndex.clear();
×
959

960
        qInfo().noquote() << "Loading variable stars data from" << QDir::toNativeSeparators(GcvsFileName);
×
961

962
        QFile vsFile(GcvsFileName);
×
963
        if (!vsFile.open(QIODevice::ReadOnly))
×
964
        {
965
                qDebug().noquote() << "Cannot open file" << QDir::toNativeSeparators(GcvsFileName);
×
966
                return;
×
967
        }
968
        QByteArray data = StelUtils::uncompress(vsFile);
×
969
        vsFile.close();
×
970
        //check if decompressing was successful
971
        if(data.isEmpty())
×
972
        {
973
                qDebug().noquote() << "Could not decompress file" << QDir::toNativeSeparators(GcvsFileName);
×
974
                return;
×
975
        }
976
        //create and open a QBuffer for reading
977
        QBuffer buf(&data);
×
978
        buf.open(QIODevice::ReadOnly);
×
979

980
        int readOk=0;
×
981
        int totalRecords=0;
×
982
        // Version of GCVS catalog
983
        static const QRegularExpression versionRx("\\s*Version:\\s*([\\d\\-\\.]+)\\s*");
×
984

985
        // record structure is delimited with a tab character.
986
        while (!buf.atEnd())
×
987
        {
988
                QString record = QString::fromUtf8(buf.readLine());
×
989

990
                // skip comments and empty lines
991
                if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
×
992
                {
993
                        QRegularExpressionMatch versionMatch=versionRx.match(record);
×
994
                        if (versionMatch.hasMatch())
×
995
                                qInfo().noquote() << "[...]" << QString("GCVS %1").arg(versionMatch.captured(1).trimmed());
×
996
                        continue;
×
997
                }
×
998

999
                totalRecords++;
×
1000
                const QStringList& fields = record.split('\t');
×
1001

1002
                bool ok;
1003
                StarId hip = fields.at(0).toLongLong(&ok);
×
1004
                if (!ok)
×
1005
                        continue;
×
1006

1007
                // Don't set the star if it's already set
1008
                if (varStarsMap.contains(hip))
×
1009
                        continue;
×
1010

1011
                varstar variableStar;
×
1012

1013
                variableStar.designation = fields.at(1).trimmed();
×
1014
                variableStar.vtype = fields.at(2).trimmed();
×
1015
                variableStar.maxmag = fields.at(3).isEmpty() ? 99.f : fields.at(3).toFloat();
×
1016
                variableStar.mflag = fields.at(4).toInt();
×
1017
                variableStar.min1mag = fields.at(5).isEmpty() ? 99.f : fields.at(5).toFloat();
×
1018
                variableStar.min2mag = fields.at(6).isEmpty() ? 99.f : fields.at(6).toFloat();
×
1019
                variableStar.photosys = fields.at(7).trimmed();
×
1020
                variableStar.epoch = fields.at(8).toDouble();
×
1021
                variableStar.period = fields.at(9).toDouble();
×
1022
                variableStar.Mm = fields.at(10).toInt();
×
1023
                variableStar.stype = fields.at(11).trimmed();
×
1024

1025
                varStarsMap[hip] = variableStar;
×
1026
                varStarsIndex[variableStar.designation.toUpper()] = hip;
×
1027
                ++readOk;
×
1028
        }
×
1029

1030
        buf.close();
×
1031
        qInfo().noquote() << "Loaded" << readOk << "/" << totalRecords << "variable stars";
×
1032
}
×
1033

1034
// Load WDS from file
1035
void StarMgr::loadWds(const QString& WdsFileName)
×
1036
{
1037
        wdsStarsMap.clear();
×
1038
        wdsStarsIndex.clear();
×
1039

1040
        qInfo().noquote() << "Loading double stars from" << QDir::toNativeSeparators(WdsFileName);
×
1041
        QFile dsFile(WdsFileName);
×
1042
        if (!dsFile.open(QIODevice::ReadOnly | QIODevice::Text))
×
1043
        {
1044
                qWarning().noquote() << "Could not open" << QDir::toNativeSeparators(WdsFileName);
×
1045
                return;
×
1046
        }
1047
        const QStringList& allRecords = QString::fromUtf8(dsFile.readAll()).split('\n');
×
1048
        dsFile.close();
×
1049

1050
        int readOk=0;
×
1051
        int totalRecords=0;
×
1052
        int lineNumber=0;
×
1053

1054
        // record structure is delimited with a tab character.
1055
        for (const auto& record : allRecords)
×
1056
        {
1057
                ++lineNumber;
×
1058
                // skip comments and empty lines
1059
                if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
×
1060
                        continue;
×
1061

1062
                ++totalRecords;
×
1063
                const QStringList& fields = record.split('\t');
×
1064

1065
                bool ok;
1066
                StarId hip = fields.at(0).toLongLong(&ok);
×
1067
                if (!ok)
×
1068
                {
1069
                        qWarning() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(WdsFileName)
×
1070
                                   << " - failed to convert " << fields.at(0) << "to a number";
×
1071
                        continue;
×
1072
                }
1073

1074
                // Don't set the star if it's already set
1075
                if (wdsStarsMap.contains(hip))
×
1076
                {
1077
                        qWarning() << "HIP" << hip << "already processed. Ignoring record:" << record;
×
1078
                        continue;
×
1079
                }
1080

1081
                wds doubleStar = {fields.at(1).trimmed(), fields.at(2).toInt(), fields.at(3).toFloat(),fields.at(4).toFloat()};
×
1082
                wdsStarsMap[hip] = doubleStar;
×
1083
                wdsStarsIndex[QString("WDS J%1").arg(doubleStar.designation.toUpper())] = hip;
×
1084
                ++readOk;
×
1085
        }
×
1086

1087
        qInfo().noquote() << "Loaded" << readOk << "/" << totalRecords << "double stars";
×
1088
}
×
1089

1090
// Load cross-identification data from file
1091
void StarMgr::loadCrossIdentificationData(const QString& crossIdFile)
×
1092
{
1093
        crossIdMap.clear();
×
1094
        saoStarsIndex.clear();        
×
1095
        hdStarsIndex.clear();        
×
1096
        hrStarsIndex.clear();
×
1097

1098
        qInfo().noquote() << "Loading cross-identification data from" << QDir::toNativeSeparators(crossIdFile);
×
1099
        QFile ciFile(crossIdFile);
×
1100
        if (!ciFile.open(QIODevice::ReadOnly))
×
1101
        {
1102
                qWarning().noquote() << "Could not open" << QDir::toNativeSeparators(crossIdFile);
×
1103
                return;
×
1104
        }
1105

1106
        QDataStream in(&ciFile);
×
1107
        in.setByteOrder(QDataStream::LittleEndian);
×
1108

1109
        crossid crossIdData;
1110

1111
        int readOk = 0;
×
1112
        int totalRecords = 0;
×
1113
        StarId hip;
1114
        unsigned char component;
1115
        int sao, hd, hr;
1116
        QString hipstar;
×
1117
        quint64 hipTemp;
1118

1119
        while (!in.atEnd())
×
1120
        {
1121
                ++totalRecords;
×
1122

1123
                in >> hipTemp >> component >> sao >> hd >> hr;
×
1124
                hip = static_cast<StarId>(hipTemp);
×
1125

1126
                if (in.status() != QDataStream::Ok)
×
1127
                {
1128
                        qWarning().noquote() << "Parse error in" << QDir::toNativeSeparators(crossIdFile)
×
1129
                                                 << " - failed to read data";
×
1130
                        break;
×
1131
                }
1132

1133
                hipstar = QString("%1%2").arg(hip).arg(component == 0 ? QString() : QString(QChar('A' + component - 1)));
×
1134
                crossIdData.sao = sao;
×
1135
                crossIdData.hd = hd;
×
1136
                crossIdData.hr = hr;
×
1137

1138
                crossIdMap[hipstar] = crossIdData;
×
1139
                if (crossIdData.sao > 0)
×
1140
                        saoStarsIndex[crossIdData.sao] = hip;
×
1141
                if (crossIdData.hd > 0)
×
1142
                        hdStarsIndex[crossIdData.hd] = hip;
×
1143
                if (crossIdData.hr > 0)
×
1144
                        hrStarsIndex[crossIdData.hr] = hip;
×
1145

1146
                ++readOk;
×
1147
        }
1148

1149
        ciFile.close();
×
1150
        qInfo().noquote() << "Loaded" << readOk << "/" << totalRecords << "cross-identification data records for stars";
×
1151
}
×
1152

1153
// load binary system orbital parameters data
1154
void StarMgr::loadBinaryOrbitalData(const QString& orbitalParamFile)
×
1155
{
1156
        qInfo().noquote() << "Loading orbital parameters data for binary system from" << QDir::toNativeSeparators(orbitalParamFile);
×
1157
        QFile opFile(orbitalParamFile);
×
1158
        if (!opFile.open(QIODevice::ReadOnly | QIODevice::Text))
×
1159
        {
1160
                qWarning().noquote() << "Could not open" << QDir::toNativeSeparators(orbitalParamFile);
×
1161
                return;
×
1162
        }
1163
        const QStringList& allRecords = QString::fromUtf8(opFile.readAll()).split('\n');
×
1164
        opFile.close();
×
1165

1166
        binaryorbitstar orbitalData;
1167

1168
        int readOk=0;
×
1169
        int totalRecords=0;
×
1170
        int lineNumber=0;
×
1171
        StarId hip;
1172
        for (const auto& record : allRecords)
×
1173
        {
1174
                ++lineNumber;
×
1175
                // skip comments and empty lines
1176
                if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
×
1177
                        continue;
×
1178

1179
                ++totalRecords;
×
1180
                const QStringList& fields = record.split('\t');
×
1181
                if (fields.size()!=18)
×
1182
                {
1183
                        qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(orbitalParamFile)
×
1184
                                             << " - record does not match record pattern";
×
1185
                        continue;
×
1186
                }
1187
                else
1188
                {
1189
                        // The record is the right format.  Extract the fields
1190
                        bool ok, allOK=true;
×
1191
                        orbitalData.binary_period    = fields.at(2).toDouble(&ok);  allOK &= ok;
×
1192
                        orbitalData.eccentricity     = fields.at(3).toFloat(&ok);   allOK &= ok;
×
1193
                        orbitalData.inclination      = fields.at(4).toFloat(&ok);   allOK &= ok;
×
1194
                        orbitalData.big_omega        = fields.at(5).toFloat(&ok);   allOK &= ok;
×
1195
                        orbitalData.small_omega      = fields.at(6).toFloat(&ok);   allOK &= ok;
×
1196
                        orbitalData.periastron_epoch = fields.at(7).toDouble(&ok);  allOK &= ok;
×
1197
                        orbitalData.semi_major       = fields.at(8).toDouble(&ok);  allOK &= ok;
×
1198
                        orbitalData.bary_distance    = fields.at(9).toDouble(&ok);  allOK &= ok;
×
1199
                        orbitalData.data_epoch       = fields.at(10).toDouble(&ok); allOK &= ok;
×
1200
                        orbitalData.bary_ra          = fields.at(11).toDouble(&ok); allOK &= ok;
×
1201
                        orbitalData.bary_dec         = fields.at(12).toDouble(&ok); allOK &= ok;
×
1202
                        orbitalData.bary_rv          = fields.at(13).toDouble(&ok); allOK &= ok;
×
1203
                        orbitalData.bary_pmra        = fields.at(14).toDouble(&ok); allOK &= ok;
×
1204
                        orbitalData.bary_pmdec       = fields.at(15).toDouble(&ok); allOK &= ok;
×
1205
                        orbitalData.primary_mass     = fields.at(16).toFloat(&ok);  allOK &= ok;
×
1206
                        orbitalData.secondary_mass   = fields.at(17).toFloat(&ok);  allOK &= ok;
×
1207
                        // do primary star
1208
                        hip = fields.at(0).toLongLong(&ok); allOK &= ok;
×
1209
                        orbitalData.hip = hip;
×
1210
                        orbitalData.primary = true;
×
1211
                        binaryOrbitStarMap[hip] = orbitalData;
×
1212
                        // do secondary star
1213
                        hip = fields.at(1).toLongLong(&ok); allOK &= ok;
×
1214
                        orbitalData.hip = hip;
×
1215
                        orbitalData.primary = false;
×
1216
                        binaryOrbitStarMap[hip] = orbitalData;
×
1217

1218
                        if (!allOK)
×
1219
                        {
1220
                                qWarning().noquote() << "Parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(orbitalParamFile)
×
1221
                                                     << " - failed to convert " << fields.at(0) << "to a number";
×
1222
                                continue;
×
1223
                        }
1224
                        ++readOk;
×
1225
                }
1226
        }
×
1227

1228
        qInfo().noquote() << "Loaded" << readOk << "/" << totalRecords << "orbital parameters data for binary system";
×
1229
}
×
1230

1231
int StarMgr::getMaxSearchLevel() const
×
1232
{
1233
        int rval = -1;
×
1234
        for (const auto* z : gridLevels)
×
1235
        {
1236
                const float mag_min = 0.001f*z->mag_min;
×
1237
                RCMag rcmag;
1238
                if (StelApp::getInstance().getCore()->getSkyDrawer()->computeRCMag(mag_min, &rcmag)==false)
×
1239
                        break;
×
1240
                rval = z->level;
×
1241
        }
1242
        return rval;
×
1243
}
1244

1245

1246
// Draw all the stars
1247
void StarMgr::draw(StelCore* core)
×
1248
{
1249
        const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000);
×
1250
        StelSkyDrawer* skyDrawer = core->getSkyDrawer();
×
1251
        // If stars are turned off don't waste time below
1252
        // projecting all stars just to draw disembodied labels
1253
        if (!static_cast<bool>(starsFader.getInterstate()))
×
1254
                return;
×
1255

1256
        static StelSkyCultureMgr* scMgr = GETSTELMODULE(StelSkyCultureMgr);
×
1257
        const bool currentSkycultureUsesCommonStarnames=scMgr->getFlagOverrideUseCommonNames() ||
×
1258
                                                        scMgr->currentSkycultureUsesCommonNames();
×
1259

1260
        int maxSearchLevel = getMaxSearchLevel();
×
1261
        double margin = 0.;  // pixel margin to be applied to viewport convex polygon
×
1262
        if (core->getSkyDrawer()->getFlagHasAtmosphere())
×
1263
        {
1264
                // Saemundsson's inversion formula for atmospheric refraction is not exact, so need some padding in terms of arcseconds
1265
                margin = 30000. * MAS2RAD * prj->getPixelPerRadAtCenter();  // 0.5 arcmin
×
1266
        }
1267
        
1268
        QVector<SphericalCap> viewportCaps = prj->getViewportConvexPolygon(margin, margin)->getBoundingSphericalCaps();
×
1269
        viewportCaps.append(core->getVisibleSkyArea());
×
1270
        const GeodesicSearchResult* geodesic_search_result = core->getGeodesicGrid(maxSearchLevel)->search(viewportCaps,maxSearchLevel);
×
1271

1272
        // Set temporary static variable for optimization
1273
        const float names_brightness = labelsFader.getInterstate() * starsFader.getInterstate();
×
1274

1275
        // prepare for aberration: Explan. Suppl. 2013, (7.38)
1276
        const bool withAberration=core->getUseAberration();
×
1277
        Vec3d vel(0.);
×
1278
        if (withAberration)
×
1279
        {
1280
                vel = core->getAberrationVec(core->getJDE());
×
1281
        }
1282

1283
        // Prepare openGL for drawing many stars
1284
        StelPainter sPainter(prj);
×
1285
        sPainter.setFont(starFont);
×
1286
        skyDrawer->preDrawPointSource(&sPainter);
×
1287

1288
        // Prepare a table for storing precomputed RCMag for all ZoneArrays
1289
        RCMag rcmag_table[RCMAG_TABLE_SIZE];
1290
        
1291
        // Draw all the stars of all the selected zones
1292
        for (const auto* z : std::as_const(gridLevels))
×
1293
        {
1294
                int limitMagIndex=RCMAG_TABLE_SIZE;
×
1295
                // overshoot by 7 mag because some stars (like HIP 16757) can get brighter than its catalogs min magnitude
1296
                const float mag_min = 0.001f*z->mag_min - 7.f;
×
1297
                for (int i=0;i<RCMAG_TABLE_SIZE;++i)
×
1298
                {
1299
                        const float mag = mag_min+0.05*i;  // 0.05 mag MagStepIncrement
×
1300
                        if (skyDrawer->computeRCMag(mag, &rcmag_table[i])==false)
×
1301
                        {
1302
                                if (i==0)
×
1303
                                        goto exit_loop;
×
1304
                                
1305
                                // The last magnitude at which the star is visible
1306
                                limitMagIndex = i-1;
×
1307
                                
1308
                                // We reached the point where stars are not visible anymore
1309
                                // Fill the rest of the table with zero and leave.
1310
                                for (;i<RCMAG_TABLE_SIZE;++i)
×
1311
                                {
1312
                                        rcmag_table[i].luminance=0;
×
1313
                                        rcmag_table[i].radius=0;
×
1314
                                }
1315
                                break;
×
1316
                        }
1317
                        rcmag_table[i].radius *= starsFader.getInterstate();
×
1318
                }
1319
                lastMaxSearchLevel = z->level;
×
1320

1321
                int maxMagStarName = 0;
×
1322
                if (labelsFader.getInterstate()>0.f)
×
1323
                {
1324
                        // Adapt magnitude limit of the stars labels according to FOV and labelsAmount
1325
                        float maxMag = (skyDrawer->getLimitMagnitude()-6.5f)*0.7f+(static_cast<float>(labelsAmount)*1.2f)-2.f;
×
1326
                        int x = static_cast<int>((maxMag-mag_min)/0.05);  // 0.05 mag MagStepIncrement
×
1327
                        if (x > 0)
×
1328
                                maxMagStarName = x;
×
1329
                }
1330
                int zone;
1331
                double withParallax = core->getUseParallax() * core->getParallaxFactor();
×
1332
                Vec3d diffPos(0., 0., 0.);
×
1333
                if (withParallax) {
×
1334
                        diffPos = core->getParallaxDiff(core->getJDE());
×
1335
                }
1336
                for (GeodesicSearchInsideIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
×
1337
                        z->draw(&sPainter, zone, true, rcmag_table, limitMagIndex, core, maxMagStarName, names_brightness, viewportCaps, withAberration, vel, withParallax, diffPos, currentSkycultureUsesCommonStarnames);
×
1338
                for (GeodesicSearchBorderIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
×
1339
                        z->draw(&sPainter, zone, false, rcmag_table, limitMagIndex, core, maxMagStarName,names_brightness, viewportCaps, withAberration, vel, withParallax, diffPos, currentSkycultureUsesCommonStarnames);
×
1340
                // always check the last zone because it is a global zone
1341
                z->draw(&sPainter, (20<<(z->level<<1)), false, rcmag_table, limitMagIndex, core, maxMagStarName, names_brightness, viewportCaps, withAberration, vel, withParallax, diffPos, currentSkycultureUsesCommonStarnames);
×
1342
        }
1343
        exit_loop:
×
1344

1345
        // Finish drawing many stars
1346
        skyDrawer->postDrawPointSource(&sPainter);
×
1347

1348
        if (objectMgr->getFlagSelectedObjectPointer())
×
1349
                drawPointer(sPainter, core);
×
1350
}
×
1351

1352

1353
// Return a QList containing the stars located
1354
// inside the limFov circle around position vv (in J2000 frame without aberration)
1355
QList<StelObjectP > StarMgr::searchAround(const Vec3d& vv, double limFov, const StelCore* core) const
×
1356
{
1357
        QList<StelObjectP > result;
×
1358
        if (!getFlagStars())
×
1359
                return result;
×
1360

1361
        Vec3d v(vv);
×
1362
        v.normalize();
×
1363

1364
        // find any vectors h0 and h1 (length 1), so that h0*v=h1*v=h0*h1=0
1365
        int i;
1366
        {
1367
                const double a0 = fabs(v[0]);
×
1368
                const double a1 = fabs(v[1]);
×
1369
                const double a2 = fabs(v[2]);
×
1370
                if (a0 <= a1)
×
1371
                {
1372
                        if (a0 <= a2) i = 0;
×
1373
                        else i = 2;
×
1374
                } else
1375
                {
1376
                        if (a1 <= a2) i = 1;
×
1377
                        else i = 2;
×
1378
                }
1379
        }
1380
        Vec3d h0(0.0,0.0,0.0);
×
1381
        h0[i] = 1.0;
×
1382
        Vec3d h1 = h0 ^ v;
×
1383
        h1.normalize();
×
1384
        h0 = h1 ^ v;
×
1385
        h0.normalize();
×
1386

1387
        // Now we have h0*v=h1*v=h0*h1=0.
1388
        // Construct a region with 4 corners e0,e1,e2,e3 inside which all desired stars must be:
1389
        double f = 1.4142136 * tan(limFov * M_PI_180);
×
1390
        h0 *= f;
×
1391
        h1 *= f;
×
1392
        Vec3d e0 = v + h0;
×
1393
        Vec3d e1 = v + h1;
×
1394
        Vec3d e2 = v - h0;
×
1395
        Vec3d e3 = v - h1;
×
1396
        f = 1.0/e0.norm();
×
1397
        e0 *= f;
×
1398
        e1 *= f;
×
1399
        e2 *= f;
×
1400
        e3 *= f;
×
1401
        // Search the triangles
1402
        SphericalConvexPolygon c(e3, e2, e2, e0);
×
1403
        const GeodesicSearchResult* geodesic_search_result = core->getGeodesicGrid(lastMaxSearchLevel)->search(c.getBoundingSphericalCaps(),lastMaxSearchLevel);
×
1404

1405
        double withParallax = core->getUseParallax() * core->getParallaxFactor();
×
1406
        Vec3d diffPos(0., 0., 0.);
×
1407
        if (withParallax) {
×
1408
                diffPos = core->getParallaxDiff(core->getJDE());
×
1409
        }
1410

1411
        // Iterate over the stars inside the triangles
1412
        f = cos(limFov * M_PI/180.);
×
1413
        for (auto* z : gridLevels)
×
1414
        {
1415
                //qDebug() << "search inside(" << it->first << "):";
1416
                int zone;
1417
                for (GeodesicSearchInsideIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
×
1418
                {
1419
                        z->searchAround(core, zone, v, withParallax, diffPos, f, result);
×
1420
                        //qDebug() << " " << zone;
1421
                }
1422
                //qDebug() << StelUtils::getEndLineChar() << "search border(" << it->first << "):";
1423
                for (GeodesicSearchBorderIterator it1(*geodesic_search_result,z->level); (zone = it1.next()) >= 0;)
×
1424
                {
1425
                        z->searchAround(core, zone, v, withParallax, diffPos, f, result);
×
1426
                        //qDebug() << " " << zone;
1427
                }
1428
                // always search the last zone because it is a global zone
1429
                z->searchAround(core, (20<<(z->level<<1)), v, withParallax, diffPos, f, result);
×
1430
        }
1431
        return result;
×
1432
}
×
1433

1434
// Return a QList containing the stars located within region (in J2000 frame without aberration)
1435
// Debugging info is available in a debug build. The reason for the minimum opening angle in StelCore::getGeodesicGrid() is still a bit unclear.
1436
QList<StelObjectP > StarMgr::searchWithin(const SphericalRegionP region, const StelCore* core, const bool hipOnly, const float maxMag) const
×
1437
{
1438
        QList<StelObjectP> result;
×
1439
        if (!getFlagStars())
×
1440
                return result;
×
1441

1442
        // For unidentified reasons, the geodesic search result is empty when the cap used in search (below) has d>0.83.
1443
        QVector<SphericalCap> caps=region->getBoundingSphericalCaps();
×
1444
        QVector<SphericalCap> largerCaps;
×
1445
        foreach (auto &cap, caps)
×
1446
        {
1447
#ifndef NDEBUG
1448
                qDebug() << "Cap: " << cap.n << cap.d;
×
1449
#endif
1450
                largerCaps.append(SphericalCap(cap.n, qMin(cap.d, 0.75))); // 0.83 seemed still too small, unclear why. 0.75 seems to work.
×
1451
        }
×
1452
        const GeodesicSearchResult* geodesic_search_result = core->getGeodesicGrid(maxGeodesicGridLevel)->search(largerCaps,maxGeodesicGridLevel);
×
1453

1454
#ifndef NDEBUG
1455
        // Just some temporary debug output.
1456
        geodesic_search_result->print();
×
1457
#endif
1458
        // prepare for aberration: Explan. Suppl. 2013, (7.38)
1459
        const bool withAberration=core->getUseAberration();
×
1460
        Vec3d vel(0.);
×
1461
        if (withAberration)
×
1462
        {
1463
                vel = core->getAberrationVec(core->getJDE());
×
1464
        }
1465

1466
#ifndef NDEBUG
1467
        qDebug() << "We have" << gridLevels.count() << " ZoneArrays in gridLevels at maxGeodesicGridLevel:" << maxGeodesicGridLevel;
×
1468
#endif
1469
        // Draw all the stars of all the selected zones
1470
        for (const  auto* z : std::as_const(gridLevels))
×
1471
        {
1472
                if (hipOnly && z->level>3) // There are no hip numbers after level 3.
×
1473
                {
1474
#ifndef NDEBUG
1475
                        qDebug() << "StarMgr::searchWithin(): Skip ZoneArray with level" << z->level << "(" << z->fname << ")";
×
1476
#endif
1477
                        continue;
×
1478
                }
1479
#ifndef NDEBUG
1480
                qDebug() << "Z level=" << z->level << "mag_min=" << z->mag_min;
×
1481
#endif
1482
                int zone;
1483
                double withParallax = core->getUseParallax() * core->getParallaxFactor();
×
1484
                Vec3d diffPos(0., 0., 0.);
×
1485
                if (withParallax) {
×
1486
                        diffPos = core->getParallaxDiff(core->getJDE());
×
1487
                }
1488

1489
                for (GeodesicSearchInsideIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
×
1490
                {
1491
#ifndef NDEBUG
1492
                        qDebug() << "Inside: Zone z->fname:" << z->fname << "Level z=" << z->level << "zone=" << zone;
×
1493
#endif
1494
                        z->searchWithin(core, zone, region, withParallax, diffPos, hipOnly, maxMag, result);
×
1495
                }
1496
                for (GeodesicSearchBorderIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
×
1497
                {
1498
#ifndef NDEBUG
1499
                        qDebug() << "Border: Zone z->fname:" << z->fname << "Level z=" << z->level << "zone=" << zone;
×
1500
#endif
1501
                        z->searchWithin(core, zone, region, withParallax, diffPos, hipOnly, maxMag, result);
×
1502
                }
1503
                // always check the last zone because it is a global zone
1504
#ifndef NDEBUG
1505
                qDebug() << "Global 20<<(z->level<<1)=" << (20<<(z->level<<1));
×
1506
#endif
1507
                z->searchWithin(core, (20<<(z->level<<1)), region, withParallax, diffPos, hipOnly, maxMag, result);
×
1508
        }
1509

1510
#ifndef NDEBUG
1511
        qInfo() << "Region contains" << result.length() << "entries";
×
1512
#endif
1513
        return result;
×
1514
}
×
1515

1516

1517

1518
//! Update i18 names from english names according to passed translator.
1519
//! The translation is done using gettext with translated strings defined in translations.h
1520
void StarMgr::updateI18n()
×
1521
{
1522
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
×
1523
        commonNamesI18nMap.clear();
×
1524
        commonNamesI18nUppercaseIndex.clear();
×
1525
        for (QHash<StarId,QString>::ConstIterator it(commonNamesMap.constBegin());it!=commonNamesMap.constEnd();it++)
×
1526
        {
1527
                const StarId i = it.key();
×
1528
                const QString t(trans.qTranslateStar(it.value()));
×
1529
                commonNamesI18nMap[i] = t;
×
1530
                commonNamesI18nUppercaseIndex[t.toUpper()] = i;
×
1531
        }
×
1532
        culturalNamesUppercaseIndex.clear();
×
1533
        for (QMultiHash<StarId,StelObject::CulturalName>::iterator it(culturalNamesMap.begin());it!=culturalNamesMap.end();++it)
×
1534
        {
1535
                StarId HIP=it.key();
×
1536
                StelObject::CulturalName &cName=it.value();
×
1537
                cName.pronounceI18n=trans.qTranslateStar(cName.pronounce);
×
1538
                cName.translatedI18n=trans.qTranslateStar(cName.translated);
×
1539
                it.value() = cName;
×
1540

1541
                // rebuild index
1542
                if (!cName.native.isEmpty())
×
1543
                        culturalNamesUppercaseIndex.insert(cName.native.toUpper(), HIP);
×
1544
                if (!cName.pronounceI18n.isEmpty())
×
1545
                        culturalNamesUppercaseIndex.insert(cName.pronounceI18n.toUpper(), HIP);
×
1546
                if (!cName.transliteration.isEmpty())
×
1547
                        culturalNamesUppercaseIndex.insert(cName.transliteration.toUpper(), HIP);
×
1548
                if (!cName.translatedI18n.isEmpty())
×
1549
                        culturalNamesUppercaseIndex.insert(cName.translatedI18n.toUpper(), HIP);
×
1550
        }
1551
}
×
1552

1553
// Search the star by HP number
1554
StelObjectP StarMgr::searchHP(int hp) const
×
1555
{
1556
        if (0 < hp && hp <= NR_OF_HIP)
×
1557
        {
1558
                const Star1 *const s = hipIndex[hp].s;
×
1559
                if (s)
×
1560
                {
1561
                        const SpecialZoneArray<Star1> *const a = hipIndex[hp].a;
×
1562
                        const SpecialZoneData<Star1> *const z = hipIndex[hp].z;
×
1563
                        return s->createStelObject(a,z);
×
1564
                }
1565
        }
1566
        return StelObjectP();
×
1567
}
1568

1569
// Search the star by Gaia source_id
1570
StelObjectP StarMgr::searchGaia(StarId source_id) const
×
1571
{
1572
        int maxSearchLevel = getMaxSearchLevel();
×
1573
        int matched = 0;
×
1574
        int index = 0;
×
1575
        // get the level 12 HEALPix index of the source
1576
        int lv12_pix = source_id / 34359738368;
×
1577
        Vec3d v;
×
1578
        StelObjectP so;
×
1579
        healpix_pix2vec(pow(2, 12), lv12_pix, v.v);  // search which pixel the source is in and turn to coordinates
×
1580
        Vec3f vf = v.toVec3f();
×
1581

1582
        for (const auto* z : gridLevels)
×
1583
        {
1584
                // search the zone where the source is in
1585
                index = StelApp::getInstance().getCore()->getGeodesicGrid(maxSearchLevel)->getZoneNumberForPoint(vf, z->level);
×
1586
                so = z->searchGaiaID(index, source_id, matched);
×
1587
                if (matched)
×
1588
                        return so;
×
1589
                
1590
                // then search the global zone 
1591
                so = z->searchGaiaID((20<<(z->level<<1)), source_id, matched);
×
1592
                if (matched)
×
1593
                        return so;
×
1594
        }
1595
        return StelObjectP();
×
1596
}
×
1597

1598
StelObjectP StarMgr::searchByNameI18n(const QString& nameI18n) const
×
1599
{
1600
        QString nameI18nUpper = nameI18n.toUpper();
×
1601

1602
        // Search by I18n common name
1603
        if (commonNamesI18nUppercaseIndex.contains(nameI18nUpper))
×
1604
                return searchHP(commonNamesI18nUppercaseIndex.value(nameI18nUpper));
×
1605

1606
        // Search by cultural names? (Any names: native/pronounceI18n/translatedI18n/transliteration
1607
        if (getFlagAdditionalNames() && culturalNamesUppercaseIndex.contains(nameI18nUpper))
×
1608
        {
1609
                // This only returns the first-found number.
1610
                StarId starId=culturalNamesUppercaseIndex.value(nameI18nUpper);
×
1611
                return (starId <= NR_OF_HIP ? searchHP(starId) : searchGaia(starId));
×
1612
        }
1613

1614
        return searchByName(nameI18n);
×
1615
}
×
1616

1617
StelObjectP StarMgr::searchByName(const QString& name) const
×
1618
{
1619
        QString nameUpper = name.toUpper();
×
1620
        StarId sid;
1621

1622
        // Search by HP number if it's an HP formatted number. The final part (A/B/...) is ignored
1623
        static const QRegularExpression rx("^\\s*(HP|HIP)\\s*(\\d+)\\s*.*$", QRegularExpression::CaseInsensitiveOption);
×
1624
        QRegularExpressionMatch match=rx.match(nameUpper);
×
1625
        if (match.hasMatch())
×
1626
                return searchHP(match.captured(2).toInt());
×
1627

1628
        // Search by SAO number if it's an SAO formatted number
1629
        static const QRegularExpression rx2("^\\s*(SAO)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
1630
        match=rx2.match(nameUpper);
×
1631
        if (match.hasMatch())
×
1632
        {
1633
                const int saoIndex=match.captured(2).toInt();
×
1634
                if (saoStarsIndex.contains(saoIndex))
×
1635
                {
1636
                        sid = saoStarsIndex.value(saoIndex);
×
1637
                        return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1638
                }
1639
        }
1640

1641
        // Search by HD number if it's an HD formatted number
1642
        static const QRegularExpression rx3("^\\s*(HD)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
1643
        match=rx3.match(nameUpper);
×
1644
        if (match.hasMatch())
×
1645
        {
1646
                const int hdIndex=match.captured(2).toInt();
×
1647
                if (hdStarsIndex.contains(hdIndex))
×
1648
                {
1649
                        sid = hdStarsIndex.value(hdIndex);
×
1650
                        return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1651
                }
1652
        }
1653

1654
        // Search by HR number if it's an HR formatted number
1655
        static const QRegularExpression rx4("^\\s*(HR)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
1656
        match=rx4.match(nameUpper);
×
1657
        if (match.hasMatch())
×
1658
        {
1659
                const int hrIndex=match.captured(2).toInt();
×
1660
                if (hrStarsIndex.contains(hrIndex))
×
1661
                {
1662
                        sid = hrStarsIndex.value(hrIndex);
×
1663
                        return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1664
                }
1665
        }
1666

1667
        // Search by Gaia number if it's an Gaia formatted number.
1668
        static const QRegularExpression rx5("^\\s*(Gaia DR3)\\s*(\\d+)\\s*.*$", QRegularExpression::CaseInsensitiveOption);
×
1669
        match=rx5.match(nameUpper);
×
1670
        if (match.hasMatch())
×
1671
                return searchGaia(match.captured(2).toLongLong());
×
1672

1673
        // Search by English common name
1674
        if (commonNamesUppercaseIndex.contains(nameUpper))
×
1675
        {
1676
                sid = commonNamesUppercaseIndex.value(nameUpper);
×
1677
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1678
        }
1679

1680
        if (getFlagAdditionalNames())
×
1681
        {
1682
                if (culturalNamesUppercaseIndex.contains(nameUpper))
×
1683
                {
1684
                        // This only returns the first-found number.
1685
                        sid=culturalNamesUppercaseIndex.value(nameUpper);
×
1686
                        return (sid <= NR_OF_HIP ? searchHP(sid) : searchGaia(sid));
×
1687
                }
1688
        }
1689

1690
        // Search by scientific name
1691
        if (sciDesignationsIndex.contains(name)) // case sensitive!
×
1692
        {
1693
                sid = sciDesignationsIndex.value(name);
×
1694
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1695
        }
1696
        if (sciDesignationsIndex.contains(nameUpper)) // case insensitive!
×
1697
        {
1698
                sid = sciDesignationsIndex.value(nameUpper);
×
1699
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1700
        }
1701

1702
        // Search by scientific name
1703
        if (sciExtraDesignationsIndex.contains(name)) // case sensitive!
×
1704
        {
1705
                sid = sciExtraDesignationsIndex.value(name);
×
1706
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1707
        }
1708
        if (sciExtraDesignationsIndex.contains(nameUpper)) // case insensitive!
×
1709
        {
1710
                sid = sciExtraDesignationsIndex.value(nameUpper);
×
1711
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1712
        }
1713

1714
        // Search by GCVS name
1715
        if (varStarsIndex.contains(nameUpper))
×
1716
        {
1717
                sid = varStarsIndex.value(nameUpper);
×
1718
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1719
        }
1720

1721
        // Search by WDS name
1722
        if (wdsStarsIndex.contains(nameUpper))
×
1723
        {
1724
                sid = wdsStarsIndex.value(nameUpper);
×
1725
                return (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
1726
        }
1727

1728
        return StelObjectP();
×
1729
}
×
1730

1731
StelObjectP StarMgr::searchByID(const QString &id) const
×
1732
{
1733
        return searchByName(id);
×
1734
}
1735

1736
//! Find and return the list of at most maxNbItem objects auto-completing the passed object name.
1737
QStringList StarMgr::listMatchingObjects(const QString& objPrefix, int maxNbItem, bool useStartOfWords) const
×
1738
{
1739
        QStringList result;
×
1740
        if (maxNbItem <= 0 || !getFlagStars())
×
1741
                return result;
×
1742

1743
        const QString objPrefixUpper = objPrefix.toUpper();
×
1744
        bool found;
1745

1746
        // Search for common names
1747
        QHashIterator<QString, StarId> i(commonNamesI18nUppercaseIndex);
×
1748
        while (i.hasNext())
×
1749
        {
1750
                i.next();
×
1751
                if (useStartOfWords && i.key().startsWith(objPrefixUpper))
×
1752
                        found = true;
×
1753
                else found = (!useStartOfWords && i.key().contains(objPrefixUpper));
×
1754

1755
                if (found)
×
1756
                {
1757
                        if (maxNbItem<=0)
×
1758
                                break;
×
1759
                        result.append(getCommonNameI18n(i.value()));
×
1760
                        --maxNbItem;
×
1761
                }
1762
        }
1763

1764
        QHashIterator<QString, StarId> j(commonNamesUppercaseIndex);
×
1765
        while (j.hasNext())
×
1766
        {
1767
                j.next();
×
1768
                if (useStartOfWords && j.key().startsWith(objPrefixUpper))
×
1769
                        found = true;
×
1770
                else found = (!useStartOfWords && j.key().contains(objPrefixUpper));
×
1771

1772
                if (found)
×
1773
                {
1774
                        if (maxNbItem<=0)
×
1775
                                break;
×
1776
                        result.append(getCommonEnglishName(j.value()));
×
1777
                        --maxNbItem;
×
1778
                }
1779
        }
1780

1781
        if (getFlagAdditionalNames())
×
1782
        {
1783
#if  (QT_VERSION<QT_VERSION_CHECK(6,0,0))
1784
                QMapIterator<QString, StarId>it(culturalNamesUppercaseIndex);
1785
#else
1786
                QMultiMapIterator<QString, StarId>it(culturalNamesUppercaseIndex);
×
1787
#endif
1788
                while (it.hasNext())
×
1789
                {
1790
                        it.next();
×
1791
                        QString name=it.key();
×
1792

1793
                        if (useStartOfWords && name.startsWith(objPrefixUpper, Qt::CaseInsensitive))
×
1794
                                found = true;
×
1795
                        else found = (!useStartOfWords && name.contains(objPrefixUpper, Qt::CaseInsensitive));
×
1796

1797
                        if (found)
×
1798
                        {
1799
                                if (maxNbItem<=0)
×
1800
                                        break;
×
1801

1802
                                // We must retrieve the original mixed-case spelling
1803
                                const QList<StelObject::CulturalName> cNames=getCulturalNames(it.value());
×
1804
                                QString finalName;
×
1805
                                for (const StelObject::CulturalName &cName: cNames)
×
1806
                                {
1807
                                        if (!cName.native.compare(name, Qt::CaseInsensitive))
×
1808
                                                finalName=cName.native;
×
1809
                                        else if (!cName.pronounceI18n.compare(name, Qt::CaseInsensitive))
×
1810
                                                finalName=cName.pronounceI18n;
×
1811
                                        else if (!cName.transliteration.compare(name, Qt::CaseInsensitive))
×
1812
                                                finalName=cName.transliteration;
×
1813
                                        else if (!cName.translatedI18n.compare(name, Qt::CaseInsensitive))
×
1814
                                                finalName=cName.translatedI18n;
×
1815
                                }
1816
                                if (finalName.isEmpty())
×
1817
                                {
1818
                                        qWarning() << "No original string found for " << name
×
1819
                                                   << "(This should not be possible...)";
×
1820
                                        finalName=name;
×
1821
                                }
1822

1823
                                result.append(finalName);
×
1824
                                --maxNbItem;
×
1825
                        }
×
1826
                }
×
1827
        }
×
1828

1829
        // Search for sci names
1830
        // need special character escape because many stars have name starting with "*"
1831
        QString bayerPattern = objPrefix;
×
1832
        QRegularExpression bayerRegEx(QRegularExpression::escape(bayerPattern));
×
1833
        QString bayerPatternCI = objPrefixUpper;
×
1834
        QRegularExpression bayerRegExCI(QRegularExpression::escape(bayerPatternCI));
×
1835

1836
        // if the first character is a Greek letter, check if there's an index
1837
        // after it, such as "alpha1 Cen".
1838
        if (objPrefix.at(0).unicode() >= 0x0391 && objPrefix.at(0).unicode() <= 0x03A9)
×
1839
                bayerRegEx.setPattern(bayerPattern.insert(1,"\\d?"));        
×
1840
        if (objPrefixUpper.at(0).unicode() >= 0x0391 && objPrefixUpper.at(0).unicode() <= 0x03A9)
×
1841
                bayerRegExCI.setPattern(bayerPatternCI.insert(1,"\\d?"));
×
1842

1843
        QHashIterator<QString,StarId>it(sciDesignationsIndex);
×
1844
        while (it.hasNext())
×
1845
        {
1846
                it.next();
×
1847
                if (it.key().indexOf(bayerRegEx)==0 || it.key().indexOf(objPrefix)==0)
×
1848
                {
1849
                        if (maxNbItem<=0)
×
1850
                                break;
×
1851
                        QStringList names = getSciDesignation(it.value()).split(" - ");
×
1852
                        for (const auto &name : std::as_const(names))
×
1853
                        {
1854
                                if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
×
1855
                                        found = true;
×
1856
                                else found = (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive));
×
1857

1858
                                if (found)
×
1859
                                {
1860
                                        if (maxNbItem<=0)
×
1861
                                                break;
×
1862
                                        result.append(name);
×
1863
                                        --maxNbItem;
×
1864
                                }
1865
                        }
1866
                }
×
1867
                else if (it.key().at(0) != objPrefix.at(0))
×
1868
                        break;
×
1869
        }
1870

1871
        it.toFront(); // reset
×
1872
        while (it.hasNext())
×
1873
        {
1874
                it.next();
×
1875
                if (it.key().indexOf(bayerRegExCI)==0 || it.key().indexOf(objPrefixUpper)==0)
×
1876
                {
1877
                        if (maxNbItem<=0)
×
1878
                                break;
×
1879
                        QStringList names = getSciDesignation(it.value()).split(" - ");
×
1880
                        for (const auto &name : std::as_const(names))
×
1881
                        {
1882
                                if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
×
1883
                                        found = true;
×
1884
                                else found = (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive));
×
1885

1886
                                if (found)
×
1887
                                {
1888
                                        if (maxNbItem<=0)
×
1889
                                                break;
×
1890
                                        result.append(name);
×
1891
                                        --maxNbItem;
×
1892
                                }
1893
                        }
1894
                }
×
1895
                else if (it.key().at(0) != objPrefixUpper.at(0))
×
1896
                        break;
×
1897
        }
1898

1899
        QHashIterator<QString,StarId>ite(sciExtraDesignationsIndex);
×
1900
        while (ite.hasNext())
×
1901
        {
1902
                ite.next();
×
1903
                if (ite.key().indexOf(bayerRegEx)==0 || ite.key().indexOf(objPrefix)==0)
×
1904
                {
1905
                        if (maxNbItem<=0)
×
1906
                                break;
×
1907
                        QStringList names = getSciExtraDesignation(ite.value()).split(" - ");
×
1908
                        for (const auto &name : std::as_const(names))
×
1909
                        {
1910
                                if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
×
1911
                                        found = true;
×
1912
                                else found = (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive));
×
1913

1914
                                if (found)
×
1915
                                {
1916
                                        if (maxNbItem<=0)
×
1917
                                                break;
×
1918
                                        result.append(name);
×
1919
                                        --maxNbItem;
×
1920
                                }
1921
                        }
1922
                }
×
1923
                else if (ite.key().at(0) != objPrefix.at(0))
×
1924
                        break;
×
1925
        }
1926

1927
        ite.toFront(); // reset
×
1928
        while (ite.hasNext())
×
1929
        {
1930
                ite.next();
×
1931
                if (ite.key().indexOf(bayerRegExCI)==0 || ite.key().indexOf(objPrefixUpper)==0)
×
1932
                {
1933
                        if (maxNbItem<=0)
×
1934
                                break;
×
1935
                        QStringList names = getSciExtraDesignation(ite.value()).split(" - ");
×
1936
                        for (const auto &name : std::as_const(names))
×
1937
                        {
1938
                                if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
×
1939
                                        found = true;
×
1940
                                else found = (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive));
×
1941

1942
                                if (found)
×
1943
                                {
1944
                                        if (maxNbItem<=0)
×
1945
                                                break;
×
1946
                                        result.append(name);
×
1947
                                        --maxNbItem;
×
1948
                                }
1949
                        }
1950
                }
×
1951
                else if (ite.key().at(0) != objPrefixUpper.at(0))
×
1952
                        break;
×
1953
        }
1954

1955
        // Search for sci names for var stars
1956
        QHashIterator<QString,StarId>itv(varStarsIndex);
×
1957
        while (itv.hasNext())
×
1958
        {
1959
                itv.next();
×
1960
                if (itv.key().startsWith(objPrefixUpper))
×
1961
                {
1962
                        if (maxNbItem<=0)
×
1963
                                break;
×
1964
                        result << getGcvsDesignation(itv.value());
×
1965
                        --maxNbItem;
×
1966
                }
1967
                else
1968
                        break;
×
1969
        }
1970

1971
        StarId sid;
1972
        // Add exact Hp catalogue numbers. The final part (A/B/...) is ignored
1973
        static const QRegularExpression hpRx("^(HIP|HP)\\s*(\\d+)\\s*.*$", QRegularExpression::CaseInsensitiveOption);
×
1974
        QRegularExpressionMatch match=hpRx.match(objPrefixUpper);
×
1975
        if (match.hasMatch())
×
1976
        {
1977
                bool ok;
1978
                int hpNum = match.captured(2).toInt(&ok);
×
1979
                if (ok)
×
1980
                {
1981
                        StelObjectP s = searchHP(hpNum);
×
1982
                        if (s && maxNbItem>0)
×
1983
                        {
1984
                                result << QString("HIP%1").arg(hpNum);
×
1985
                                maxNbItem--;
×
1986
                        }
1987
                }
×
1988
        }
1989

1990
        // Add exact SAO catalogue numbers
1991
        static const QRegularExpression saoRx("^(SAO)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
1992
        match=saoRx.match(objPrefixUpper);
×
1993
        if (match.hasMatch())
×
1994
        {
1995
                int saoNum = match.captured(2).toInt();
×
1996
                if (saoStarsIndex.contains(saoNum))
×
1997
                {
1998
                        sid = saoStarsIndex.value(saoNum);
×
1999
                        StelObjectP s =  (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
2000
                        if (s && maxNbItem>0)
×
2001
                        {
2002
                                result << QString("SAO%1").arg(saoNum);
×
2003
                                maxNbItem--;
×
2004
                        }
2005
                }
×
2006
        }
2007

2008
        // Add exact HD catalogue numbers
2009
        static const QRegularExpression hdRx("^(HD)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
2010
        match=hdRx.match(objPrefixUpper);
×
2011
        if (match.hasMatch())
×
2012
        {
2013
                int hdNum = match.captured(2).toInt();
×
2014
                if (hdStarsIndex.contains(hdNum))
×
2015
                {
2016
                        sid = hdStarsIndex.value(hdNum);
×
2017
                        StelObjectP s =  (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
2018
                        if (s && maxNbItem>0)
×
2019
                        {
2020
                                result << QString("HD%1").arg(hdNum);
×
2021
                                maxNbItem--;
×
2022
                        }
2023
                }
×
2024
        }
2025

2026
        // Add exact HR catalogue numbers
2027
        static const QRegularExpression hrRx("^(HR)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
2028
        match=hrRx.match(objPrefixUpper);
×
2029
        if (match.hasMatch())
×
2030
        {
2031
                int hrNum = match.captured(2).toInt();
×
2032
                if (hrStarsIndex.contains(hrNum))
×
2033
                {
2034
                        sid = hrStarsIndex.value(hrNum);
×
2035
                        StelObjectP s =  (sid <= NR_OF_HIP) ? searchHP(sid) : searchGaia(sid);
×
2036
                        if (s && maxNbItem>0)
×
2037
                        {
2038
                                result << QString("HR%1").arg(hrNum);
×
2039
                                maxNbItem--;
×
2040
                        }
2041
                }
×
2042
        }
2043

2044
        // Add exact WDS catalogue numbers
2045
        static const QRegularExpression wdsRx("^(WDS)\\s*(\\S+)\\s*$", QRegularExpression::CaseInsensitiveOption);
×
2046
        if (wdsRx.match(objPrefixUpper).hasMatch())
×
2047
        {
2048
                QHashIterator<QString, StarId>wds(wdsStarsIndex);
×
2049
                while (wds.hasNext())
×
2050
                {
2051
                        wds.next();
×
2052
                        if (wds.key().startsWith(objPrefixUpper))
×
2053
                        {
2054
                                if (maxNbItem==0)
×
2055
                                        break;
×
2056
                                result << getWdsDesignation(wds.value());
×
2057
                                --maxNbItem;
×
2058
                        }
2059
                        else
2060
                                break;
×
2061
                }
2062
        }
×
2063

2064
        // Add exact Gaia DR3 catalogue numbers.
2065
        static const QRegularExpression gaiaRx("^\\s*(Gaia DR3)\\s*(\\d+)\\s*.*$", QRegularExpression::CaseInsensitiveOption);
×
2066
        match=gaiaRx.match(objPrefixUpper);
×
2067
        if (match.hasMatch())
×
2068
        {
2069
                bool ok;
2070
                StarId gaiaNum = match.captured(2).toLongLong(&ok);
×
2071
                if (ok)
×
2072
                {
2073
                        StelObjectP s = searchGaia(gaiaNum);
×
2074
                        if (s && maxNbItem>0)
×
2075
                        {
2076
                                result << QString("Gaia DR3 %1").arg(gaiaNum);
×
2077
                                maxNbItem--;
×
2078
                        }
2079
                }
×
2080
        }
2081

2082
        result.sort();        
×
2083
        return result;
×
2084
}
×
2085

2086
// Set display flag for Stars.
2087
void StarMgr::setFlagStars(bool b)
×
2088
{
2089
        starsFader=b;
×
2090
        StelApp::immediateSave("astro/flag_stars", b);
×
2091
        emit starsDisplayedChanged(b);
×
2092
}
×
2093
// Set display flag for Star names (labels).
2094
void StarMgr::setFlagLabels(bool b) {labelsFader=b; StelApp::immediateSave("astro/flag_star_name", b); emit starLabelsDisplayedChanged(b);}
×
2095
// Set the amount of star labels. The real amount is also proportional with FOV.
2096
// The limit is set in function of the stars magnitude
2097
// @param a the amount between 0 and 10. 0 is no labels, 10 is maximum of labels
2098
void StarMgr::setLabelsAmount(double a)
×
2099
{
2100
        if(!qFuzzyCompare(a,labelsAmount))
×
2101
        {
2102
                labelsAmount=a;
×
2103
                StelApp::immediateSave("stars/labels_amount", a);
×
2104
                emit labelsAmountChanged(a);
×
2105
        }
2106
}
×
2107

2108
// Define font file name and size to use for star names display
2109
void StarMgr::setFontSize(int newFontSize)
×
2110
{
2111
        starFont.setPixelSize(newFontSize);
×
2112
}
×
2113

2114
// Set flag for usage designations of stars for their labels instead common names.
2115
void StarMgr::setDesignationUsage(const bool flag)
×
2116
{
2117
        if(flagDesignations!=flag)
×
2118
        {
2119
                flagDesignations=flag;
×
2120
                StelApp::immediateSave("astro/flag_star_designation_usage", flag);
×
2121
                emit designationUsageChanged(flag);
×
2122
        }
2123
}
×
2124
// Set flag for usage traditional designations of double stars.
2125
void StarMgr::setFlagDblStarsDesignation(const bool flag)
×
2126
{
2127
        if(flagDblStarsDesignation!=flag)
×
2128
        {
2129
                flagDblStarsDesignation=flag;
×
2130
                StelApp::immediateSave("astro/flag_star_designation_dbl", flag);
×
2131
                emit flagDblStarsDesignationChanged(flag);
×
2132
        }
2133
}
×
2134
// Set flag for usage designations of variable stars.
2135
void StarMgr::setFlagVarStarsDesignation(const bool flag)
×
2136
{
2137
        if(flagVarStarsDesignation!=flag)
×
2138
        {
2139
                flagVarStarsDesignation=flag;
×
2140
                StelApp::immediateSave("astro/flag_star_designation_var", flag);
×
2141
                emit flagVarStarsDesignationChanged(flag);
×
2142
        }
2143
}
×
2144
// Set flag for usage Hipparcos catalog designations of stars.
2145
void StarMgr::setFlagHIPDesignation(const bool flag)
×
2146
{
2147
        if(flagHIPDesignation!=flag)
×
2148
        {
2149
                flagHIPDesignation=flag;
×
2150
                StelApp::immediateSave("astro/flag_star_designation_hip", flag);
×
2151
                emit flagHIPDesignationChanged(flag);
×
2152
        }
2153
}
×
2154
// Show additional star names.
2155
void StarMgr::setFlagAdditionalNames(bool flag)
×
2156
{
2157
        if (flagAdditionalStarNames!=flag)
×
2158
        {
2159
                flagAdditionalStarNames=flag;
×
2160
                StelApp::immediateSave("astro/flag_star_additional_names", flag);
×
2161
                emit flagAdditionalNamesDisplayedChanged(flag);
×
2162
        }
2163
}
×
2164

2165
void StarMgr::updateSkyCulture(const StelSkyCulture& skyCulture)
×
2166
{
2167
        const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
×
2168

2169
        const QString commonStarsFileName = StelFileMgr::findFile("skycultures/common_star_names.fab");
×
2170
        CommonNames commonNames;
×
2171
        if (commonStarsFileName.isEmpty())
×
2172
                qWarning() << "Could not load common_star_names.fab";
×
2173
        else
2174
                commonNames = loadCommonNames(commonStarsFileName);
×
2175

2176
        QMap<QString, int> commonNamesIndexToSearchWhileLoading = commonNames.hipByName;
×
2177
        commonNamesMap.clear();
×
2178
        commonNamesI18nMap.clear();
×
2179
        commonNamesI18nUppercaseIndex.clear();
×
2180
        commonNamesUppercaseIndex.clear();
×
2181
        culturalNamesMap.clear();
×
2182
        culturalNamesUppercaseIndex.clear();
×
2183

2184
        static QSettings* conf = StelApp::getInstance().getSettings();
×
2185
        Q_ASSERT(conf);
×
2186
        QString exclude=conf->value("SCExcludeReferences/"+skyCulture.id, QString()).toString();
×
2187
        QSet<int>excludeRefs;
×
2188
        if (!exclude.isEmpty())
×
2189
        {
2190
#if  (QT_VERSION<QT_VERSION_CHECK(5,14,0))
2191
                const QStringList excludeRefStrings = exclude.split(",", QString::SkipEmptyParts);
2192
#else
2193
                const QStringList excludeRefStrings = exclude.split(",", Qt::SkipEmptyParts);
×
2194
#endif
2195
                //qInfo() << "Skyculture" << skyCulture.id << "configured to exclude references" << excludeRefStrings;
2196
                for (const QString &s: excludeRefStrings)
×
2197
                {
2198
                        bool ok;
2199
                        int numRef=s.toInt(&ok); // ok=false for strings e.g. from asterisms
×
2200
                        if (ok)
×
2201
                                excludeRefs.insert(numRef);
×
2202
                }
2203
                qInfo() << "Skyculture" << skyCulture.id << "configured to exclude references" << excludeRefs;
×
2204
        }
×
2205

2206
        if (!skyCulture.names.isEmpty())
×
2207
                loadCultureSpecificNames(skyCulture.names, commonNamesIndexToSearchWhileLoading, excludeRefs);
×
2208

2209
        // Load common names (i.e., standardized IAU approved star names). These are used for reference if user includes "Modern" names in the display settings.
2210
        // If a skyculture has set fallbackToInternationalNames, it means the names will be incorporated in the actual skyculture.
2211
        for (auto it = commonNames.hipByName.begin(); it != commonNames.hipByName.end(); ++it)
×
2212
        {
2213
                const StarId HIP = it.value();
×
2214
                const QString& englishName = commonNames.byHIP[HIP];
×
2215
                const QString englishNameUpper = englishName.toUpper();
×
2216

2217
                const QString& i18nName = trans.qTranslateStar(commonNames.byHIP[HIP]);
×
2218
                const QString i18nNameUpper = i18nName.toUpper();
×
2219

2220
                // TODO: Why do we have to reload the commonname* structures? Just load once at program start? Translate if needed, OK.
2221
                if (!commonNamesMap.contains(HIP))
×
2222
                {
2223
                        commonNamesMap[HIP] = englishName;
×
2224
                        commonNamesUppercaseIndex[englishNameUpper] = HIP;
×
2225
                        commonNamesI18nMap[HIP] = i18nName;
×
2226
                        commonNamesI18nUppercaseIndex[i18nNameUpper] = HIP;
×
2227
                }
2228

2229
                if (skyCulture.fallbackToInternationalNames)
×
2230
                {
2231
                        // Add name from commonNames, but only if not already in list.
2232
                        auto starIds=culturalNamesUppercaseIndex.values(englishNameUpper);
×
2233
                        if (!starIds.contains(HIP))
×
2234
                        {
2235
                                StelObject::CulturalName cName{englishName, QString(), QString(),
×
2236
                                                        englishName, englishName, i18nName, QString()};
×
2237
                                //if (culturalNamesMap.contains(HIP))
2238
                                //        qInfo() << "Adding additional cultural name for HIP" << HIP << ":" <<  cName.native << "/" << cName.pronounceI18n << "/" << cName.translated << "/" << cName.translatedI18n;
2239
                                culturalNamesMap.insert(HIP, cName); // add as possibly multiple entry to HIP.
×
2240
                                if (!cName.native.isEmpty())
×
2241
                                        culturalNamesUppercaseIndex.insert(cName.native.toUpper(), HIP);
×
2242
                                if (!cName.translatedI18n.isEmpty())
×
2243
                                        culturalNamesUppercaseIndex.insert(cName.translatedI18n.toUpper(), HIP);
×
2244
                        }
×
2245
                }
×
2246
        }
×
2247

2248
        // Turn on sci names/catalog names for modern cultures only
2249
        setFlagSciNames(skyCulture.englishName.contains("modern", Qt::CaseInsensitive));
×
2250
        updateI18n();
×
2251
}
×
2252

2253
void StarMgr::increaseStarsMagnitudeLimit()
×
2254
{
2255
        static StelCore* core = StelApp::getInstance().getCore();
×
2256
        core->getSkyDrawer()->setCustomStarMagnitudeLimit(core->getSkyDrawer()->getCustomStarMagnitudeLimit() + 0.1);
×
2257
}
×
2258

2259
void StarMgr::reduceStarsMagnitudeLimit()
×
2260
{
2261
        static StelCore* core = StelApp::getInstance().getCore();
×
2262
        core->getSkyDrawer()->setCustomStarMagnitudeLimit(core->getSkyDrawer()->getCustomStarMagnitudeLimit() - 0.1);
×
2263
}
×
2264

2265
void StarMgr::populateStarsDesignations()
×
2266
{
2267
        QString filePath;
×
2268
        filePath = StelFileMgr::findFile("stars/hip_gaia3/name.fab");
×
2269
        if (filePath.isEmpty())
×
2270
                qWarning() << "Could not load scientific star names file: stars/hip_gaia3/name.fab";
×
2271
        else
2272
                loadSciDesignations(filePath, sciDesignationsMap, sciDesignationsIndex);
×
2273

2274
        filePath = StelFileMgr::findFile("stars/hip_gaia3/extra_name.fab");
×
2275
        if (filePath.isEmpty())
×
2276
                qWarning() << "Could not load scientific star extra names file: stars/hip_gaia3/extra_name.fab";
×
2277
        else
2278
                loadSciDesignations(filePath, sciExtraDesignationsMap, sciExtraDesignationsIndex);
×
2279

2280
        filePath = StelFileMgr::findFile("stars/hip_gaia3/gcvs.cat");
×
2281
        if (filePath.isEmpty())
×
2282
                qWarning() << "Could not load variable stars file: stars/hip_gaia3/gcvs.cat";
×
2283
        else
2284
                loadGcvs(filePath);
×
2285

2286
        filePath = StelFileMgr::findFile("stars/hip_gaia3/wds_hip_part.dat");
×
2287
        if (filePath.isEmpty())
×
2288
                qWarning() << "Could not load double stars file: stars/hip_gaia3/wds_hip_part.dat";
×
2289
        else
2290
                loadWds(filePath);
×
2291

2292
        filePath = StelFileMgr::findFile("stars/hip_gaia3/cross-id.cat");
×
2293
        if (filePath.isEmpty())
×
2294
                qWarning() << "Could not load cross-identification data file: stars/hip_gaia3/cross-id.cat";
×
2295
        else
2296
                loadCrossIdentificationData(filePath);
×
2297
        
2298
        filePath = StelFileMgr::findFile("stars/hip_gaia3/binary_orbitparam.dat");
×
2299
        if (filePath.isEmpty())
×
2300
                qWarning() << "Could not load binary orbital parameters data file: stars/hip_gaia3/binary_orbitparam.dat";
×
2301
        else
2302
                loadBinaryOrbitalData(filePath);
×
2303
}
×
2304

2305
QStringList StarMgr::listAllObjects(bool inEnglish) const
×
2306
{
2307
        QStringList result;
×
2308
        if (inEnglish)
×
2309
        {
2310
                result = commonNamesMap.values();
×
2311

2312
#if  (QT_VERSION<QT_VERSION_CHECK(6,0,0))
2313
                QHashIterator<StarId, StelObject::CulturalName> ci(culturalNamesMap);
2314
#else
2315
                QMultiHashIterator<StarId, StelObject::CulturalName> ci(culturalNamesMap);
×
2316
#endif
2317
                while (ci.hasNext())
×
2318
                {
2319
                        ci.next();
×
2320
                        result << ci.value().native;
×
2321
                        result << ci.value().translated;
×
2322
                        result << ci.value().pronounce;
×
2323
                        result << ci.value().transliteration;
×
2324
                }
2325
        }
×
2326
        else
2327
        {
2328
                result=commonNamesI18nMap.values();
×
2329

2330
#if  (QT_VERSION<QT_VERSION_CHECK(6,0,0))
2331
                QHashIterator<StarId, StelObject::CulturalName> ci(culturalNamesMap);
2332
#else
2333
                QMultiHashIterator<StarId, StelObject::CulturalName> ci(culturalNamesMap);
×
2334
#endif
2335
                while (ci.hasNext())
×
2336
                {
2337
                        ci.next();
×
2338
                        result << ci.value().native;
×
2339
                        result << ci.value().translatedI18n;
×
2340
                        result << ci.value().pronounceI18n;
×
2341
                        result << ci.value().transliteration;
×
2342
                }
2343
        }
×
2344
        result.removeDuplicates();
×
2345
        result.removeAll(QString(""));
×
2346
        result.removeAll(QString());
×
2347
        return result;
×
2348
}
×
2349

2350
QStringList StarMgr::listAllObjectsByType(const QString &objType, bool inEnglish) const
×
2351
{
2352
        QStringList result;        
×
2353
        // type 1
2354
        bool isStarT1 = false;
×
2355
        QList<StelObjectP> starsT1;
×
2356
        // type 2
2357
        bool isStarT2 = false;
×
2358
        QList<StelACStarData> starsT2;
×
2359
        int type = objType.toInt();        
×
2360
        switch (type)
×
2361
        {
2362
                case 0: // Interesting double stars
×
2363
                {
2364
                        static const QStringList doubleStars = {
2365
                                "21 Tau", "27 Tau", "77 Tau", "δ1 Tau", "V1016 Ori",
2366
                                "42 Ori", "ι Ori", "ζ Crv", "ζ UMa", "α2 Lib", "α1 Cru",
2367
                                "ω1 Sco", "λ Sco", "μ1 Sco", "ζ1 Sco", "ε1 Lyr", "ε2 Lyr",
2368
                                "δ1 Lyr",         "ν1 Sgr", "ο1 Cyg", "ο2 Cyg", "α2 Cap", "β1 Cyg",
2369
                                "β Ori", "γ1 And", "ξ Boo", "α1 Her", "T Dra", "ν1 Dra",
2370
                                "70 Oph", "α Gem", "ζ Her", "ο2 Eri", "γ1 Ari", "γ Vir",
2371
                                "γ1 Leo", "β Mon", "ε Boo", "44 Boo", "β1 Sco", "ζ1 Cnc",
2372
                                "φ2 Cnc", "α Leo", "α2 CVn", "ι Cas", "ε Ari", "κ Vel", "γ1 Del",
2373
                                "61 Cyg B", "55 Aqr", "σ Cas", "η Cas", "α UMi", "36 Oph",
2374
                                "α1 Cen",  "65 UMa", "σ2 UMa", "55 Cnc", "16 Cyg A",
2375
                                "HIP 28393", "HIP 84709"};
×
2376
                        result = doubleStars;
×
2377
                        break;
×
2378
                }
2379
                case 1: // Interesting variable stars
×
2380
                {
2381
                        static const QStringList variableStars = {
2382
                                "δ Cep", "β Per", "ο Cet", "λ Tau", "β Lyr", "ζ Gem", "μ Cep",
2383
                                "α1 Her", "η Gem", "η Aql", "γ Cas", "α Ori", "R And",
2384
                                "U Ant", "θ Aps", "R Aql", "V Aql", "R Aqr", "ε Aur", "R Aur",
2385
                                "AE Aur", "W Boo", "VZ Cam", "l Car", "WZ Cas",        "S Cen",
2386
                                "α Cen C", "T Cep", "U Cep", "R CMa", "VY CMa",
2387
                                "S Cnc", "α CrB", "R CrB", "T CrB", "U CrB", "R Cru",
2388
                                "SU Cyg", "EU Del", "β Dor", "R Gem", "30 Her", "68 Her",
2389
                                "R Hor", "R Lep", "R Leo", "RR Lyr", "U Mon", "R Hya", "χ Cyg",
2390
                                "δ Ori", "VV Ori", "κ Pav", "β Peg", "ε Peg", "ζ Phe", "R Sct",
2391
                                "U Sgr", "RY Sgr", "W UMa", "α UMi"};
×
2392
                        result = variableStars;
×
2393
                        break;
×
2394
                }
2395
                case 2: // Bright double stars
×
2396
                {
2397
                        starsT2 = doubleHipStars;
×
2398
                        isStarT2 = true;
×
2399
                        break;
×
2400
                }
2401
                case 3: // Bright variable stars
×
2402
                {
2403
                        starsT2 = variableHipStars;
×
2404
                        isStarT2 = true;
×
2405
                        break;
×
2406
                }
2407
                case 4:
×
2408
                {
2409
                        starsT2 = hipStarsHighPM;
×
2410
                        isStarT2 = true;
×
2411
                        break;
×
2412
                }
2413
                case 5: // Variable stars: Algol-type eclipsing systems
×
2414
                {
2415
                        starsT2 = algolTypeStars;
×
2416
                        isStarT2 = true;
×
2417
                        break;
×
2418
                }
2419
                case 6: // Variable stars: the classical cepheids
×
2420
                {
2421
                        starsT2 = classicalCepheidsTypeStars;
×
2422
                        isStarT2 = true;
×
2423
                        break;
×
2424
                }
2425
                case 7: // Bright carbon stars
×
2426
                {
2427
                        starsT1 = carbonStars;
×
2428
                        isStarT1 = true;
×
2429
                        break;
×
2430
                }
2431
                case 8: // Bright barium stars
×
2432
                {
2433
                        starsT1 = bariumStars;
×
2434
                        isStarT1 = true;
×
2435
                        break;
×
2436
                }
2437
                default:
×
2438
                {
2439
                        // No stars yet?
2440
                        break;
×
2441
                }
2442
        }
2443

2444
        QString starName;
×
2445
        if (isStarT1)
×
2446
        {
2447
                for (const auto& star : std::as_const(starsT1))
×
2448
                {
2449
                        starName = inEnglish ? star->getEnglishName() : star->getNameI18n();
×
2450
                        if (!starName.isEmpty())
×
2451
                                result << starName;
×
2452
                        else
2453
                                result << star->getID();
×
2454
                }
2455
        }
2456

2457
        if (isStarT2)
×
2458
        {
2459
                for (const auto& star : std::as_const(starsT2))
×
2460
                {
2461
                        starName = inEnglish ? star.first->getEnglishName() : star.first->getNameI18n();
×
2462
                        if (!starName.isEmpty())
×
2463
                                result << starName;
×
2464
                        else
2465
                                result << star.first->getID();
×
2466
                }
2467
        }
2468

2469
        result.removeDuplicates();
×
2470
        return result;
×
2471
}
×
2472

2473
QString StarMgr::getStelObjectType() const
×
2474
{
2475
        return STAR_TYPE;
×
2476
}
2477

2478
//! cultural names
2479
//! Return screen label (to be used in the sky display. Most users will use some short label)
2480
QString StarMgr::getCulturalScreenLabel(StarId hip)
×
2481
{
2482
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
2483
        QStringList list=getCultureLabels(hip, scMgr->getScreenLabelStyle());
×
2484
        //qDebug() << "culturalScreenLabel: " << list;
2485
        return list.isEmpty() ? QString() : list.constFirst();
×
2486
}
×
2487

2488
//! Return InfoString label (to be used in the InfoString).
2489
//! When dealing with foreign skycultures, many users will want this to be longer, with more name components.
2490
QString StarMgr::getCulturalInfoLabel(StarId hip)
×
2491
{
2492
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
2493
        QStringList list=getCultureLabels(hip, scMgr->getInfoLabelStyle());
×
2494
        return list.isEmpty() ? "" : list.join(" - ");
×
2495
}
×
2496

2497
QStringList StarMgr::getCultureLabels(StarId hip, StelObject::CulturalDisplayStyle style)
×
2498
{
2499
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
2500
        // Retrieve list in order as read from JSON
2501
        QList<StelObject::CulturalName>culturalNames=getCulturalNames(hip);
×
2502
        if (culturalNames.isEmpty())
×
2503
        {
2504
                return QStringList();
×
2505
        }
2506

2507
        QStringList labels;
×
2508
        for (auto &cName: culturalNames)
×
2509
                {
2510
                        QString commonNamei18=getCommonNameI18n(hip);
×
2511
                        if (commonNamei18.isEmpty())
×
2512
                        {
2513
                                QString sciDes=getSciDesignation(hip);
×
2514
                                if (sciDes.isEmpty())
×
2515
                                        sciDes=getSciExtraDesignation(hip);
×
2516

2517
                                commonNamei18=sciDes.split(" - ").first();
×
2518
                        }
×
2519
                        QString label=scMgr->createCulturalLabel(cName, style, commonNamei18);
×
2520
                        labels << label;
×
2521
                }
×
2522
        labels.removeDuplicates();
×
2523
        labels.removeAll(QString(""));
×
2524
        labels.removeAll(QString());
×
2525
        return labels;
×
2526
}
×
2527

2528

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