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 "StelToneReproducer.hpp"
30 #include "StelTranslator.hpp"
31 #include "StelGeodesicGrid.hpp"
32 #include "StelApp.hpp"
33 #include "StelTextureMgr.hpp"
34 #include "StelObjectMgr.hpp"
35 #include "StelLocaleMgr.hpp"
36 #include "StelSkyCultureMgr.hpp"
37 #include "StelFileMgr.hpp"
38 #include "StelModuleMgr.hpp"
39 #include "StelCore.hpp"
40 #include "StelIniParser.hpp"
41 #include "StelPainter.hpp"
42 #include "StelJsonParser.hpp"
43 #include "ZoneArray.hpp"
44 #include "StelSkyDrawer.hpp"
45 #include "RefractionExtinction.hpp"
46 #include "StelModuleMgr.hpp"
47 #include "ConstellationMgr.hpp"
48 #include "Planet.hpp"
49 #include "StelUtils.hpp"
50 
51 #include <QTextStream>
52 #include <QFile>
53 #include <QSettings>
54 #include <QString>
55 #include <QRegularExpression>
56 #include <QDebug>
57 #include <QFileInfo>
58 #include <QDir>
59 #include <QCryptographicHash>
60 
61 #include <cstdlib>
62 
63 static QStringList spectral_array;
64 static QStringList component_array;
65 
66 // This number must be incremented each time the content or file format of the stars catalogs change
67 // It can also be incremented when the defaultStarsConfig.json file change.
68 // It should always matchs the version field of the defaultStarsConfig.json file
69 static const int StarCatalogFormatVersion = 12;
70 
71 // Initialise statics
72 bool StarMgr::flagSciNames = true;
73 bool StarMgr::flagAdditionalStarNames = true;
74 bool StarMgr::flagDesignations = false;
75 bool StarMgr::flagDblStarsDesignation = false;
76 bool StarMgr::flagVarStarsDesignation = false;
77 bool StarMgr::flagHIPDesignation = false;
78 QHash<int,QString> StarMgr::commonNamesMap;
79 QHash<int,QString> StarMgr::commonNamesMapI18n;
80 QHash<int,QString> StarMgr::additionalNamesMap;
81 QHash<int,QString> StarMgr::additionalNamesMapI18n;
82 QMap<QString,int> StarMgr::commonNamesIndexI18n;
83 QMap<QString,int> StarMgr::commonNamesIndex;
84 QMap<QString,int> StarMgr::additionalNamesIndex;
85 QMap<QString,int> StarMgr::additionalNamesIndexI18n;
86 QHash<int,QString> StarMgr::sciDesignationsMapI18n;
87 QMap<QString,int> StarMgr::sciDesignationsIndexI18n;
88 QHash<int,QString> StarMgr::sciExtraDesignationsMapI18n;
89 QMap<QString,int> StarMgr::sciExtraDesignationsIndexI18n;
90 QHash<int, varstar> StarMgr::varStarsMapI18n;
91 QMap<QString, int> StarMgr::varStarsIndexI18n;
92 QHash<int, wds> StarMgr::wdsStarsMapI18n;
93 QMap<QString, int> StarMgr::wdsStarsIndexI18n;
94 QMap<QString, crossid> StarMgr::crossIdMap;
95 QMap<int, int> StarMgr::saoStarsIndex;
96 QMap<int, int> StarMgr::hdStarsIndex;
97 QMap<int, int> StarMgr::hrStarsIndex;
98 QHash<int, QString> StarMgr::referenceMap;
99 QHash<int, float> StarMgr::hipParallaxErrors;
100 QHash<int, PMData> StarMgr::hipPMData;
101 
initStringListFromFile(const QString & file_name)102 QStringList initStringListFromFile(const QString& file_name)
103 {
104 	QStringList list;
105 	QFile f(file_name);
106 	if (f.open(QIODevice::ReadOnly | QIODevice::Text))
107 	{
108 		while (!f.atEnd())
109 		{
110 			QString s = QString::fromUtf8(f.readLine());
111 			s.chop(1);
112 			list << s;
113 		}
114 		f.close();
115 	}
116 	return list;
117 }
118 
convertToSpectralType(int index)119 QString StarMgr::convertToSpectralType(int index)
120 {
121 	if (index < 0 || index >= spectral_array.size())
122 	{
123 		qDebug() << "convertToSpectralType: bad index: " << index << ", max: " << spectral_array.size();
124 		return "";
125 	}
126 	return spectral_array.at(index);
127 }
128 
convertToComponentIds(int index)129 QString StarMgr::convertToComponentIds(int index)
130 {
131 	if (index < 0 || index >= component_array.size())
132 	{
133 		qDebug() << "convertToComponentIds: bad index: " << index << ", max: " << component_array.size();
134 		return "";
135 	}
136 	return component_array.at(index);
137 }
138 
139 
initTriangle(int lev,int index,const Vec3f & c0,const Vec3f & c1,const Vec3f & c2)140 void StarMgr::initTriangle(int lev,int index, const Vec3f &c0, const Vec3f &c1, const Vec3f &c2)
141 {
142 	gridLevels[lev]->initTriangle(index,c0,c1,c2);
143 }
144 
145 
StarMgr(void)146 StarMgr::StarMgr(void)
147 	: StelObjectModule()
148 	, flagStarName(false)
149 	, labelsAmount(0.)
150 	, gravityLabel(false)
151 	, maxGeodesicGridLevel(-1)
152 	, lastMaxSearchLevel(-1)
153 	, hipIndex(new HipIndexStruct[NR_OF_HIP+1])
154 {
155 	setObjectName("StarMgr");
156 	objectMgr = GETSTELMODULE(StelObjectMgr);
157 	Q_ASSERT(objectMgr);
158 }
159 
160 /*************************************************************************
161  Reimplementation of the getCallOrder method
162 *************************************************************************/
getCallOrder(StelModuleActionName actionName) const163 double StarMgr::getCallOrder(StelModuleActionName actionName) const
164 {
165 	if (actionName==StelModule::ActionDraw)
166 		return StelApp::getInstance().getModuleMgr().getModule("ConstellationMgr")->getCallOrder(actionName)+10;
167 	return 0;
168 }
169 
170 
~StarMgr(void)171 StarMgr::~StarMgr(void)
172 {
173 	for (auto* z : gridLevels)
174 		delete z;
175 	gridLevels.clear();
176 	if (hipIndex)
177 		delete[] hipIndex;
178 }
179 
180 // Allow untranslated name here if set in constellationMgr!
getCommonName(int hip)181 QString StarMgr::getCommonName(int hip)
182 {
183 	ConstellationMgr* cmgr=GETSTELMODULE(ConstellationMgr);
184 	if (cmgr->getConstellationDisplayStyle() == ConstellationMgr::constellationsNative)
185 		return getCommonEnglishName(hip);
186 
187 	auto it = commonNamesMapI18n.find(hip);
188 	if (it!=commonNamesMapI18n.end())
189 		return it.value();
190 	return QString();
191 }
192 
getAdditionalNames(int hip)193 QString StarMgr::getAdditionalNames(int hip)
194 {
195 	auto it = additionalNamesMapI18n.find(hip);
196 	if (it!=additionalNamesMapI18n.end())
197 		return it.value();
198 	return QString();
199 }
200 
getAdditionalEnglishNames(int hip)201 QString StarMgr::getAdditionalEnglishNames(int hip)
202 {
203 	auto it = additionalNamesMap.find(hip);
204 	if (it!=additionalNamesMap.end())
205 		return it.value();
206 	return QString();
207 }
208 
getCommonEnglishName(int hip)209 QString StarMgr::getCommonEnglishName(int hip)
210 {
211 	auto it = commonNamesMap.find(hip);
212 	if (it!=commonNamesMap.end())
213 		return it.value();
214 	return QString();
215 }
216 
217 
getSciName(int hip)218 QString StarMgr::getSciName(int hip)
219 {
220 	auto it = sciDesignationsMapI18n.find(hip);
221 	if (it!=sciDesignationsMapI18n.end())
222 		return it.value();
223 	return QString();
224 }
225 
getSciExtraName(int hip)226 QString StarMgr::getSciExtraName(int hip)
227 {
228 	auto it = sciExtraDesignationsMapI18n.find(hip);
229 	if (it!=sciExtraDesignationsMapI18n.end())
230 		return it.value();
231 	return QString();
232 }
233 
getCrossIdentificationDesignations(QString hip)234 QString StarMgr::getCrossIdentificationDesignations(QString hip)
235 {
236 	QStringList designations;
237 	auto cr = crossIdMap.find(hip);
238 	if (cr==crossIdMap.end() && hip.right(1).toUInt()==0)
239 		cr = crossIdMap.find(hip.left(hip.size()-1));
240 
241 	if (cr!=crossIdMap.end())
242 	{
243 		crossid crossIdData = cr.value();
244 		if (crossIdData.sao>0)
245 			designations << QString("SAO %1").arg(crossIdData.sao);
246 
247 		if (crossIdData.hd>0)
248 			designations << QString("HD %1").arg(crossIdData.hd);
249 
250 		if (crossIdData.hr>0)
251 			designations << QString("HR %1").arg(crossIdData.hr);
252 	}
253 
254 	return designations.join(" - ");
255 }
256 
getWdsName(int hip)257 QString StarMgr::getWdsName(int hip)
258 {
259 	auto it = wdsStarsMapI18n.find(hip);
260 	if (it!=wdsStarsMapI18n.end())
261 		return QString("WDS J%1").arg(it.value().designation);
262 	return QString();
263 }
264 
getWdsLastObservation(int hip)265 int StarMgr::getWdsLastObservation(int hip)
266 {
267 	auto it = wdsStarsMapI18n.find(hip);
268 	if (it!=wdsStarsMapI18n.end())
269 		return it.value().observation;
270 	return 0;
271 }
272 
getWdsLastPositionAngle(int hip)273 float StarMgr::getWdsLastPositionAngle(int hip)
274 {
275 	auto it = wdsStarsMapI18n.find(hip);
276 	if (it!=wdsStarsMapI18n.end())
277 		return it.value().positionAngle;
278 	return 0;
279 }
280 
getWdsLastSeparation(int hip)281 float StarMgr::getWdsLastSeparation(int hip)
282 {
283 	auto it = wdsStarsMapI18n.find(hip);
284 	if (it!=wdsStarsMapI18n.end())
285 		return it.value().separation;
286 	return 0.f;
287 }
288 
getGcvsName(int hip)289 QString StarMgr::getGcvsName(int hip)
290 {
291 	auto it = varStarsMapI18n.find(hip);
292 	if (it!=varStarsMapI18n.end())
293 		return it.value().designation;
294 	return QString();
295 }
296 
getGcvsVariabilityType(int hip)297 QString StarMgr::getGcvsVariabilityType(int hip)
298 {
299 	auto it = varStarsMapI18n.find(hip);
300 	if (it!=varStarsMapI18n.end())
301 		return it.value().vtype;
302 	return QString();
303 }
304 
getGcvsMaxMagnitude(int hip)305 float StarMgr::getGcvsMaxMagnitude(int hip)
306 {
307 	auto it = varStarsMapI18n.find(hip);
308 	if (it!=varStarsMapI18n.end())
309 		return it.value().maxmag;
310 	return -99.f;
311 }
312 
getGcvsMagnitudeFlag(int hip)313 int StarMgr::getGcvsMagnitudeFlag(int hip)
314 {
315 	auto it = varStarsMapI18n.find(hip);
316 	if (it!=varStarsMapI18n.end())
317 		return it.value().mflag;
318 	return 0;
319 }
320 
321 
getGcvsMinMagnitude(int hip,bool firstMinimumFlag)322 float StarMgr::getGcvsMinMagnitude(int hip, bool firstMinimumFlag)
323 {
324 	auto it = varStarsMapI18n.find(hip);
325 	if (it!=varStarsMapI18n.end())
326 	{
327 		if (firstMinimumFlag)
328 		{
329 			return it.value().min1mag;
330 		}
331 		else
332 		{
333 			return it.value().min2mag;
334 		}
335 	}
336 	return -99.f;
337 }
338 
getGcvsPhotometricSystem(int hip)339 QString StarMgr::getGcvsPhotometricSystem(int hip)
340 {
341 	auto it = varStarsMapI18n.find(hip);
342 	if (it!=varStarsMapI18n.end())
343 		return it.value().photosys;
344 	return QString();
345 }
346 
getGcvsEpoch(int hip)347 double StarMgr::getGcvsEpoch(int hip)
348 {
349 	auto it = varStarsMapI18n.find(hip);
350 	if (it!=varStarsMapI18n.end())
351 		return it.value().epoch;
352 	return -99.;
353 }
354 
getGcvsPeriod(int hip)355 double StarMgr::getGcvsPeriod(int hip)
356 {
357 	auto it = varStarsMapI18n.find(hip);
358 	if (it!=varStarsMapI18n.end())
359 		return it.value().period;
360 	return -99.;
361 }
362 
getGcvsMM(int hip)363 int StarMgr::getGcvsMM(int hip)
364 {
365 	auto it = varStarsMapI18n.find(hip);
366 	if (it!=varStarsMapI18n.end())
367 		return it.value().Mm;
368 	return -99;
369 }
370 
getPlxError(int hip)371 float StarMgr::getPlxError(int hip)
372 {
373 	auto it = hipParallaxErrors.find(hip);
374 	if (it!=hipParallaxErrors.end())
375 		return it.value();
376 	return 0.f;
377 }
378 
getProperMotion(int hip)379 PMData StarMgr::getProperMotion(int hip)
380 {
381 	auto it = hipPMData.find(hip);
382 	if (it!=hipPMData.end())
383 		return it.value();
384 	return QPair<float, float>(NAN, NAN);
385 }
386 
copyDefaultConfigFile()387 void StarMgr::copyDefaultConfigFile()
388 {
389 	try
390 	{
391 		StelFileMgr::makeSureDirExistsAndIsWritable(StelFileMgr::getUserDir()+"/stars/default");
392 		starConfigFileFullPath = StelFileMgr::getUserDir()+"/stars/default/starsConfig.json";
393 		qDebug() << "Creates file " << QDir::toNativeSeparators(starConfigFileFullPath);
394 		QFile::copy(StelFileMgr::getInstallationDir()+"/stars/default/defaultStarsConfig.json", starConfigFileFullPath);
395 		QFile::setPermissions(starConfigFileFullPath, QFile::permissions(starConfigFileFullPath) | QFileDevice::WriteOwner);
396 	}
397 	catch (std::runtime_error& e)
398 	{
399 		qWarning() << e.what();
400 		qFatal("Could not create configuration file stars/default/starsConfig.json");
401 	}
402 }
403 
init()404 void StarMgr::init()
405 {
406 	QSettings* conf = StelApp::getInstance().getSettings();
407 	Q_ASSERT(conf);
408 
409 	starConfigFileFullPath = StelFileMgr::findFile("stars/default/starsConfig.json", StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::File));
410 	if (starConfigFileFullPath.isEmpty())
411 	{
412 		qWarning() << "Could not find the starsConfig.json file: will copy the default one.";
413 		copyDefaultConfigFile();
414 	}
415 
416 	QFile fic(starConfigFileFullPath);
417 	if(fic.open(QIODevice::ReadOnly))
418 	{
419 		starSettings = StelJsonParser::parse(&fic).toMap();
420 		fic.close();
421 	}
422 
423 	// Increment the 1 each time any star catalog file change
424 	if (starSettings.value("version").toInt()!=StarCatalogFormatVersion)
425 	{
426 		qWarning() << "Found an old starsConfig.json file, upgrade..";
427 		fic.remove();
428 		copyDefaultConfigFile();
429 		QFile fic2(starConfigFileFullPath);
430 		if(fic2.open(QIODevice::ReadOnly))
431 		{
432 			starSettings = StelJsonParser::parse(&fic2).toMap();
433 			fic2.close();
434 		}
435 	}
436 
437 	loadData(starSettings);
438 
439 	populateStarsDesignations();
440 	populateHipparcosLists();
441 
442 	setFontSize(StelApp::getInstance().getScreenFontSize());
443 	connect(&StelApp::getInstance(), SIGNAL(screenFontSizeChanged(int)), this, SLOT(setFontSize(int)));
444 
445 	setFlagStars(conf->value("astro/flag_stars", true).toBool());
446 	setFlagLabels(conf->value("astro/flag_star_name",true).toBool());
447 	setFlagAdditionalNames(conf->value("astro/flag_star_additional_names",true).toBool());
448 	setDesignationUsage(conf->value("astro/flag_star_designation_usage", false).toBool());
449 	setFlagDblStarsDesignation(conf->value("astro/flag_star_designation_dbl", false).toBool());
450 	setFlagVarStarsDesignation(conf->value("astro/flag_star_designation_var", false).toBool());
451 	setFlagHIPDesignation(conf->value("astro/flag_star_designation_hip", false).toBool());
452 	setLabelsAmount(conf->value("stars/labels_amount",3.).toDouble());
453 
454 	objectMgr->registerStelObjectMgr(this);
455 	texPointer = StelApp::getInstance().getTextureManager().createTexture(StelFileMgr::getInstallationDir()+"/textures/pointeur2.png");   // Load pointer texture
456 
457 	StelApp::getInstance().getCore()->getGeodesicGrid(maxGeodesicGridLevel)->visitTriangles(maxGeodesicGridLevel,initTriangleFunc,this);
458 	for (auto* z : gridLevels)
459 		z->scaleAxis();
460 	StelApp *app = &StelApp::getInstance();
461 	connect(app, SIGNAL(languageChanged()), this, SLOT(updateI18n()));
462 	connect(&app->getSkyCultureMgr(), SIGNAL(currentSkyCultureChanged(QString)), this, SLOT(updateSkyCulture(const QString&)));
463 
464 	QString displayGroup = N_("Display Options");
465 	addAction("actionShow_Stars", displayGroup, N_("Stars"), "flagStarsDisplayed", "S");
466 	addAction("actionShow_Stars_Labels", displayGroup, N_("Stars labels"), "flagLabelsDisplayed", "Alt+S");
467 	// Details: https://github.com/Stellarium/stellarium/issues/174
468 	addAction("actionShow_Stars_MagnitudeLimitIncrease", displayGroup, N_("Increase the magnitude limit for stars"), "increaseStarsMagnitudeLimit()");
469 	addAction("actionShow_Stars_MagnitudeLimitReduce", displayGroup, N_("Reduce the magnitude limit for stars"), "reduceStarsMagnitudeLimit()");
470 }
471 
472 
drawPointer(StelPainter & sPainter,const StelCore * core)473 void StarMgr::drawPointer(StelPainter& sPainter, const StelCore* core)
474 {
475 	const QList<StelObjectP> newSelected = objectMgr->getSelectedObject("Star");
476 	if (!newSelected.empty())
477 	{
478 		const StelObjectP obj = newSelected[0];
479 		Vec3d pos=obj->getJ2000EquatorialPos(core);
480 
481 		Vec3f screenpos;
482 		// Compute 2D pos and return if outside screen
483 		if (!sPainter.getProjector()->project(pos, screenpos))
484 			return;
485 
486 		sPainter.setColor(obj->getInfoColor());
487 		texPointer->bind();
488 		sPainter.setBlending(true);
489 		sPainter.drawSprite2dMode(screenpos[0], screenpos[1], 13.f, static_cast<float>(StelApp::getInstance().getAnimationTime())*40.f);
490 	}
491 }
492 
checkAndLoadCatalog(const QVariantMap & catDesc)493 bool StarMgr::checkAndLoadCatalog(const QVariantMap& catDesc)
494 {
495 	const bool checked = catDesc.value("checked").toBool();
496 	QString catalogFileName = catDesc.value("fileName").toString();
497 
498 	// See if it is an absolute path, else prepend default path
499 	if (!(StelFileMgr::isAbsolute(catalogFileName)))
500 		catalogFileName = "stars/default/"+catalogFileName;
501 
502 	QString catalogFilePath = StelFileMgr::findFile(catalogFileName);
503 	if (catalogFilePath.isEmpty())
504 	{
505 		// The file is supposed to be checked, but we can't find it
506 		if (checked)
507 		{
508 			qWarning() << QString("Warning: could not find star catalog %1").arg(QDir::toNativeSeparators(catalogFileName));
509 			setCheckFlag(catDesc.value("id").toString(), false);
510 		}
511 		return false;
512 	}
513 	// Possibly fixes crash on Vista
514 	if (!StelFileMgr::isReadable(catalogFilePath))
515 	{
516 		qWarning() << QString("Warning: User does not have permissions to read catalog %1").arg(QDir::toNativeSeparators(catalogFilePath));
517 		return false;
518 	}
519 
520 	if (!checked)
521 	{
522 		// The file is not checked but we found it, maybe from a previous download/version
523 		qWarning() << "Found file " << QDir::toNativeSeparators(catalogFilePath) << ", checking md5sum..";
524 
525 		QFile file(catalogFilePath);
526 		if(file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
527 		{
528 			// Compute the MD5 sum
529 			QCryptographicHash md5Hash(QCryptographicHash::Md5);
530 			const qint64 cat_sz = file.size();
531 			qint64 maxStarBufMd5 = qMin(cat_sz, 9223372036854775807LL);
532 			uchar *cat = maxStarBufMd5 ? file.map(0, maxStarBufMd5) : Q_NULLPTR;
533 			if (!cat)
534 			{
535 				// The OS was not able to map the file, revert to slower not mmap based method
536 				static const qint64 maxStarBufMd5 = 1024*1024*8;
537 				char* mmd5buf = static_cast<char*>(malloc(maxStarBufMd5));
538 				while (!file.atEnd())
539 				{
540 					qint64 sz = file.read(mmd5buf, maxStarBufMd5);
541 					md5Hash.addData(mmd5buf, static_cast<int>(sz));
542 				}
543 				free(mmd5buf);
544 			}
545 			else
546 			{
547 				md5Hash.addData(reinterpret_cast<const char*>(cat), static_cast<int>(cat_sz));
548 				file.unmap(cat);
549 			}
550 			file.close();
551 			if (md5Hash.result().toHex()!=catDesc.value("checksum").toByteArray())
552 			{
553 				qWarning() << "Error: File " << QDir::toNativeSeparators(catalogFileName) << " is corrupt, MD5 mismatch! Found " << md5Hash.result().toHex() << " expected " << catDesc.value("checksum").toByteArray();
554 				file.remove();
555 				return false;
556 			}
557 			qWarning() << "MD5 sum correct!";
558 			setCheckFlag(catDesc.value("id").toString(), true);
559 		}
560 	}
561 
562 	ZoneArray* z = ZoneArray::create(catalogFilePath, true);
563 	if (z)
564 	{
565 		if (z->level<gridLevels.size())
566 		{
567 			qWarning() << QDir::toNativeSeparators(catalogFileName) << ", " << z->level << ": duplicate level";
568 			delete z;
569 			return true;
570 		}
571 		Q_ASSERT(z->level==maxGeodesicGridLevel+1);
572 		Q_ASSERT(z->level==gridLevels.size());
573 		++maxGeodesicGridLevel;
574 		gridLevels.append(z);
575 	}
576 	return true;
577 }
578 
setCheckFlag(const QString & catId,bool b)579 void StarMgr::setCheckFlag(const QString& catId, bool b)
580 {
581 	// Update the starConfigFileFullPath file to take into account that we now have a new catalog
582 	int idx=0;
583 	for (const auto& catV : catalogsDescription)
584 	{
585 		++idx;
586 		QVariantMap m = catV.toMap();
587 		if (m.value("id").toString()!=catId)
588 			continue;
589 		const bool checked = m.value("checked").toBool();
590 		if (checked==b)
591 			return;
592 		m["checked"]=b;
593 		catalogsDescription[idx-1]=m;
594 		starSettings["catalogs"]=catalogsDescription;
595 		QFile tmp(starConfigFileFullPath);
596 		if(tmp.open(QIODevice::WriteOnly))
597 		{
598 			StelJsonParser::write(starSettings, &tmp);
599 			tmp.close();
600 		}
601 	}
602 }
603 
loadData(QVariantMap starsConfig)604 void StarMgr::loadData(QVariantMap starsConfig)
605 {
606 	// Please do not init twice:
607 	Q_ASSERT(maxGeodesicGridLevel < 0);
608 
609 	qDebug() << "Loading star data ...";
610 
611 	catalogsDescription = starsConfig.value("catalogs").toList();
612 	foreach (const QVariant& catV, catalogsDescription)
613 	{
614 		QVariantMap m = catV.toMap();
615 		checkAndLoadCatalog(m);
616 	}
617 
618 	for (int i=0; i<=NR_OF_HIP; i++)
619 	{
620 		hipIndex[i].a = Q_NULLPTR;
621 		hipIndex[i].z = Q_NULLPTR;
622 		hipIndex[i].s = Q_NULLPTR;
623 	}
624 	for (auto* z : gridLevels)
625 		z->updateHipIndex(hipIndex);
626 
627 	const QString cat_hip_sp_file_name = starsConfig.value("hipSpectralFile").toString();
628 	if (cat_hip_sp_file_name.isEmpty())
629 	{
630 		qWarning() << "ERROR: stars:cat_hip_sp_file_name not found";
631 	}
632 	else
633 	{
634 		QString tmpFic = StelFileMgr::findFile("stars/default/" + cat_hip_sp_file_name);
635 		if (tmpFic.isEmpty())
636 			qWarning() << "ERROR while loading data from " << QDir::toNativeSeparators(("stars/default/" + cat_hip_sp_file_name));
637 		else
638 			spectral_array = initStringListFromFile(tmpFic);
639 	}
640 
641 	const QString cat_hip_cids_file_name = starsConfig.value("hipComponentsIdsFile").toString();
642 	if (cat_hip_cids_file_name.isEmpty())
643 	{
644 		qWarning() << "ERROR: stars:cat_hip_cids_file_name not found";
645 	}
646 	else
647 	{
648 		QString tmpFic = StelFileMgr::findFile("stars/default/" + cat_hip_cids_file_name);
649 		if (tmpFic.isEmpty())
650 			qWarning() << "ERROR while loading data from " << QDir::toNativeSeparators(("stars/default/" + cat_hip_cids_file_name));
651 		else
652 			component_array = initStringListFromFile(tmpFic);
653 	}
654 
655 	lastMaxSearchLevel = maxGeodesicGridLevel;
656 	qDebug() << "Finished loading star catalogue data, max_geodesic_level: " << maxGeodesicGridLevel;
657 }
658 
populateHipparcosLists()659 void StarMgr::populateHipparcosLists()
660 {
661 	hipparcosStars.clear();
662 	hipStarsHighPM.clear();
663 	doubleHipStars.clear();
664 	variableHipStars.clear();
665 	algolTypeStars.clear();
666 	classicalCepheidsTypeStars.clear();
667 	carbonStars.clear();
668 	bariumStars.clear();
669 	const int pmLimit = 1; // arc-second per year!
670 	for (int hip=0; hip<=NR_OF_HIP; hip++)
671 	{
672 		const Star1 *const s = hipIndex[hip].s;
673 		if (s)
674 		{
675 			const SpecialZoneArray<Star1> *const a = hipIndex[hip].a;
676 			const SpecialZoneData<Star1> *const z = hipIndex[hip].z;
677 			StelObjectP so = s->createStelObject(a,z);
678 			hipparcosStars.push_back(so);
679 			QString spectrum = convertToSpectralType(s->getSpInt());
680 			// Carbon stars have spectral type, which start with C letter
681 			if (spectrum.startsWith("C", Qt::CaseInsensitive))
682 				carbonStars.push_back(so);
683 
684 			// Barium stars have spectral class G to K and contains "Ba" string
685 			if ((spectrum.startsWith("G", Qt::CaseInsensitive) || spectrum.startsWith("K", Qt::CaseInsensitive)) && spectrum.contains("Ba", Qt::CaseSensitive))
686 				bariumStars.push_back(so);
687 
688 			if (!getGcvsVariabilityType(s->getHip()).isEmpty())
689 			{
690 				QMap<StelObjectP, float> sa;
691 				sa[so] = static_cast<float>(getGcvsPeriod(s->getHip()));
692 				variableHipStars.push_back(sa);
693 
694 				auto vartype = getGcvsVariabilityType(s->getHip());
695 				if (vartype.contains("EA"))
696 				{
697 					QMap<StelObjectP, float> sal;
698 					sal[so] = sa[so];
699 					algolTypeStars.push_back(sal);
700 				}
701 				if (vartype.contains("DCEP") && !vartype.contains("DCEPS"))
702 				{
703 					QMap<StelObjectP, float> sacc;
704 					sacc[so] = sa[so];
705 					classicalCepheidsTypeStars.push_back(sacc);
706 				}
707 			}
708 			if (!getWdsName(s->getHip()).isEmpty())
709 			{
710 				QMap<StelObjectP, float> sd;
711 				sd[so] = getWdsLastSeparation(s->getHip());
712 				doubleHipStars.push_back(sd);
713 			}
714 			// use separate variables for avoid the overflow (esp. for Barnard's star)
715 			PMData properMotion = getProperMotion(s->getHip());
716 			float pmX = properMotion.first;
717 			float pmY = properMotion.second;
718 			float pm = 0.001f * std::sqrt((pmX*pmX) + (pmY*pmY));
719 			if (qAbs(pm)>=pmLimit)
720 			{
721 				QMap<StelObjectP, float> spm;
722 				spm[so] = pm;
723 				hipStarsHighPM.push_back(spm);
724 			}
725 		}
726 	}
727 }
728 
729 // Load common names from file
loadCommonNames(const QString & commonNameFile)730 int StarMgr::loadCommonNames(const QString& commonNameFile)
731 {
732 	commonNamesMap.clear();
733 	commonNamesMapI18n.clear();
734 	additionalNamesMap.clear();
735 	additionalNamesMapI18n.clear();
736 	commonNamesIndexI18n.clear();
737 	commonNamesIndex.clear();
738 	additionalNamesIndex.clear();
739 	additionalNamesIndexI18n.clear();
740 
741 	qDebug() << "Loading star names from" << QDir::toNativeSeparators(commonNameFile);
742 	QFile cnFile(commonNameFile);
743 	if (!cnFile.open(QIODevice::ReadOnly | QIODevice::Text))
744 	{
745 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(commonNameFile);
746 		return 0;
747 	}
748 
749 	int readOk=0;
750 	int totalRecords=0;
751 	int lineNumber=0;
752 	QString record;
753 	// Allow empty and comment lines where first char (after optional blanks) is #
754 	QRegularExpression commentRx("^(\\s*#.*|\\s*)$");
755 	// record structure is delimited with a | character.  We will
756 	// use a QRegularExpression to extract the fields. with white-space padding permitted
757 	// (i.e. it will be stripped automatically) Example record strings:
758 	// "   677|_("Alpheratz")"
759 	// "113368|_("Fomalhaut")"
760 	// Note: Stellarium doesn't support sky cultures made prior to version 0.10.6 now!
761 	QRegularExpression recordRx("^\\s*(\\d+)\\s*\\|[_]*[(]\"(.*)\"[)]\\s*([\\,\\d\\s]*)\\n");
762 
763 	while(!cnFile.atEnd())
764 	{
765 		record = QString::fromUtf8(cnFile.readLine());
766 		lineNumber++;
767 		if (commentRx.match(record).hasMatch())
768 			continue;
769 
770 		totalRecords++;
771 		QRegularExpressionMatch recMatch=recordRx.match(record);
772 		if (!recMatch.hasMatch())
773 		{
774 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(commonNameFile)
775 				   << " - record does not match record pattern";
776 			continue;
777 		}
778 		else
779 		{
780 			// The record is the right format.  Extract the fields
781 			bool ok;
782 			int hip = recMatch.captured(1).toInt(&ok);
783 			if (!ok)
784 			{
785 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(commonNameFile)
786 					   << " - failed to convert " << recMatch.captured(1) << "to a number";
787 				continue;
788 			}
789 			QString englishCommonName = recMatch.captured(2).trimmed();
790 			if (englishCommonName.isEmpty())
791 			{
792 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(commonNameFile)
793 					   << " - empty name field";
794 				continue;
795 			}
796 
797 			const QString englishNameCap = englishCommonName.toUpper();
798 			if (commonNamesMap.find(hip)!=commonNamesMap.end())
799 			{
800 				if (additionalNamesMap.find(hip)!=additionalNamesMap.end())
801 				{
802 					QString sname = additionalNamesMap[hip].append(" - " + englishCommonName);
803 					additionalNamesMap[hip] = sname;
804 					additionalNamesMapI18n[hip] = sname;
805 					additionalNamesIndex[englishNameCap] = hip;
806 					additionalNamesIndexI18n[englishNameCap] = hip;
807 				}
808 				else
809 				{
810 					additionalNamesMap[hip] = englishCommonName;
811 					additionalNamesMapI18n[hip] = englishCommonName;
812 					additionalNamesIndex[englishNameCap] = hip;
813 					additionalNamesIndexI18n[englishNameCap] = hip;
814 				}
815 			}
816 			else
817 			{
818 				commonNamesMap[hip] = englishCommonName;
819 				commonNamesMapI18n[hip] = englishCommonName;
820 				commonNamesIndexI18n[englishNameCap] = hip;
821 				commonNamesIndex[englishNameCap] = hip;
822 			}
823 
824 			QString reference = recMatch.captured(3).trimmed();
825 			if (!reference.isEmpty())
826 			{
827 				if (referenceMap.find(hip)!=referenceMap.end())
828 					referenceMap[hip] = referenceMap[hip].append("," + reference);
829 				else
830 					referenceMap[hip] = reference;
831 			}
832 
833 			readOk++;
834 		}
835 	}
836 	cnFile.close();
837 
838 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "common star names";
839 	return 1;
840 }
841 
842 
843 // Load scientific names from file
loadSciNames(const QString & sciNameFile,const bool extraData)844 void StarMgr::loadSciNames(const QString& sciNameFile, const bool extraData)
845 {
846 	if (extraData)
847 	{
848 		sciExtraDesignationsMapI18n.clear();
849 		sciExtraDesignationsIndexI18n.clear();
850 		qDebug() << "Loading scientific star extra names from" << QDir::toNativeSeparators(sciNameFile);
851 	}
852 	else
853 	{
854 		sciDesignationsMapI18n.clear();
855 		sciDesignationsIndexI18n.clear();
856 		qDebug() << "Loading scientific star names from" << QDir::toNativeSeparators(sciNameFile);
857 	}
858 
859 	QFile snFile(sciNameFile);
860 	if (!snFile.open(QIODevice::ReadOnly | QIODevice::Text))
861 	{
862 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(sciNameFile);
863 		return;
864 	}
865 	const QStringList& allRecords = QString::fromUtf8(snFile.readAll()).split('\n');
866 	snFile.close();
867 
868 	int readOk=0;
869 	int totalRecords=0;
870 	int lineNumber=0;
871 	// record structure is delimited with a | character. Example record strings:
872 	// " 10819|c_And"
873 	// "113726|1_And"
874 	for (const auto& record : allRecords)
875 	{
876 		++lineNumber;
877 		// skip comments and empty lines
878 		if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
879 			continue;
880 
881 		++totalRecords;
882 		const QStringList& fields = record.split('|');
883 		if (fields.size()!=2)
884 		{
885 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(sciNameFile)
886 				   << " - record does not match record pattern";
887 			continue;
888 		}
889 		else
890 		{
891 			// The record is the right format.  Extract the fields
892 			bool ok;
893 			int hip = fields.at(0).toInt(&ok);
894 			if (!ok)
895 			{
896 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(sciNameFile)
897 					   << " - failed to convert " << fields.at(0) << "to a number";
898 				continue;
899 			}
900 
901 			QString sci_name = fields.at(1).trimmed();
902 			if (sci_name.isEmpty())
903 			{
904 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(sciNameFile)
905 					   << " - empty name field";
906 				continue;
907 			}
908 
909 			sci_name.replace('_',' ');
910 			if (extraData)
911 			{
912 				// Don't set the main sci name if it's already set - it's additional sci name
913 				if (sciExtraDesignationsMapI18n.find(hip)!=sciExtraDesignationsMapI18n.end())
914 				{
915 					QString sname = sciExtraDesignationsMapI18n[hip].append(" - " + sci_name);
916 					sciExtraDesignationsMapI18n[hip] = sname;
917 				}
918 				else
919 					sciExtraDesignationsMapI18n[hip] = sci_name;
920 				sciExtraDesignationsIndexI18n[sci_name] = hip;
921 			}
922 			else
923 			{
924 				// Don't set the main sci name if it's already set - it's additional sci name
925 				if (sciDesignationsMapI18n.find(hip)!=sciDesignationsMapI18n.end())
926 				{
927 					QString sname = sciDesignationsMapI18n[hip].append(" - " + sci_name);
928 					sciDesignationsMapI18n[hip] = sname;
929 				}
930 				else
931 					sciDesignationsMapI18n[hip] = sci_name;
932 				sciDesignationsIndexI18n[sci_name] = hip;
933 			}
934 			++readOk;
935 		}
936 	}
937 
938 	if (extraData)
939 		qDebug() << "Loaded" << readOk << "/" << totalRecords << "scientific star extra names";
940 	else
941 		qDebug() << "Loaded" << readOk << "/" << totalRecords << "scientific star names";
942 }
943 
944 // Load GCVS from file
loadGcvs(const QString & GcvsFile)945 void StarMgr::loadGcvs(const QString& GcvsFile)
946 {
947 	varStarsMapI18n.clear();
948 	varStarsIndexI18n.clear();
949 
950 	qDebug() << "Loading variable stars from" << QDir::toNativeSeparators(GcvsFile);
951 	QFile vsFile(GcvsFile);
952 	if (!vsFile.open(QIODevice::ReadOnly | QIODevice::Text))
953 	{
954 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(GcvsFile);
955 		return;
956 	}
957 	const QStringList& allRecords = QString::fromUtf8(vsFile.readAll()).split('\n');
958 	vsFile.close();
959 
960 	int readOk=0;
961 	int totalRecords=0;
962 	int lineNumber=0;
963 
964 	// record structure is delimited with a tab character.
965 	for (const auto& record : allRecords)
966 	{
967 		++lineNumber;
968 		// skip comments and empty lines
969 		if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
970 			continue;
971 
972 		++totalRecords;
973 		const QStringList& fields = record.split('\t');
974 
975 		bool ok;
976 		int hip = fields.at(0).toInt(&ok);
977 		if (!ok)
978 		{
979 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(GcvsFile)
980 				   << " - failed to convert " << fields.at(0) << "to a number";
981 			continue;
982 		}
983 
984 		// Don't set the star if it's already set
985 		if (varStarsMapI18n.find(hip)!=varStarsMapI18n.end())
986 			continue;
987 
988 		varstar variableStar;
989 
990 		variableStar.designation = fields.at(1).trimmed();
991 		variableStar.vtype = fields.at(2).trimmed();
992 		if (fields.at(3).isEmpty())
993 			variableStar.maxmag = 99.f;
994 		else
995 			variableStar.maxmag = fields.at(3).toFloat();
996 		variableStar.mflag = fields.at(4).toInt();
997 		if (fields.at(5).isEmpty())
998 			variableStar.min1mag = 99.f;
999 		else
1000 			variableStar.min1mag = fields.at(5).toFloat();
1001 		if (fields.at(6).isEmpty())
1002 			variableStar.min2mag = 99.f;
1003 		else
1004 			variableStar.min2mag = fields.at(6).toFloat();
1005 		variableStar.photosys = fields.at(7).trimmed();
1006 		variableStar.epoch = fields.at(8).toDouble();
1007 		variableStar.period = fields.at(9).toDouble();
1008 		variableStar.Mm = fields.at(10).toInt();
1009 		variableStar.stype = fields.at(11).trimmed();
1010 
1011 		varStarsMapI18n[hip] = variableStar;
1012 		varStarsIndexI18n[variableStar.designation.toUpper()] = hip;
1013 		++readOk;
1014 	}
1015 
1016 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "variable stars";
1017 }
1018 
1019 // Load WDS from file
loadWds(const QString & WdsFile)1020 void StarMgr::loadWds(const QString& WdsFile)
1021 {
1022 	wdsStarsMapI18n.clear();
1023 	wdsStarsIndexI18n.clear();
1024 
1025 	qDebug() << "Loading double stars from" << QDir::toNativeSeparators(WdsFile);
1026 	QFile dsFile(WdsFile);
1027 	if (!dsFile.open(QIODevice::ReadOnly | QIODevice::Text))
1028 	{
1029 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(WdsFile);
1030 		return;
1031 	}
1032 	const QStringList& allRecords = QString::fromUtf8(dsFile.readAll()).split('\n');
1033 	dsFile.close();
1034 
1035 	int readOk=0;
1036 	int totalRecords=0;
1037 	int lineNumber=0;
1038 
1039 	// record structure is delimited with a tab character.
1040 	for (const auto& record : allRecords)
1041 	{
1042 		++lineNumber;
1043 		// skip comments and empty lines
1044 		if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
1045 			continue;
1046 
1047 		++totalRecords;
1048 		const QStringList& fields = record.split('\t');
1049 
1050 		bool ok;
1051 		int hip = fields.at(0).toInt(&ok);
1052 		if (!ok)
1053 		{
1054 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(WdsFile)
1055 				   << " - failed to convert " << fields.at(0) << "to a number";
1056 			continue;
1057 		}
1058 
1059 		// Don't set the star if it's already set
1060 		if (wdsStarsMapI18n.find(hip)!=wdsStarsMapI18n.end())
1061 			continue;
1062 
1063 		wds doubleStar;
1064 
1065 		doubleStar.designation = fields.at(1).trimmed();
1066 		doubleStar.observation = fields.at(2).toInt();
1067 		doubleStar.positionAngle = fields.at(3).toFloat();
1068 		doubleStar.separation = fields.at(4).toFloat();
1069 
1070 		wdsStarsMapI18n[hip] = doubleStar;
1071 		wdsStarsIndexI18n[QString("WDS J%1").arg(doubleStar.designation.toUpper())] = hip;
1072 		++readOk;
1073 	}
1074 
1075 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "double stars";
1076 }
1077 
1078 // Load cross-identification data from file
loadCrossIdentificationData(const QString & crossIdFile)1079 void StarMgr::loadCrossIdentificationData(const QString& crossIdFile)
1080 {
1081 	crossIdMap.clear();
1082 	saoStarsIndex.clear();
1083 	hdStarsIndex.clear();
1084 	hrStarsIndex.clear();
1085 
1086 	qDebug() << "Loading cross-identification data from" << QDir::toNativeSeparators(crossIdFile);
1087 	QFile ciFile(crossIdFile);
1088 	if (!ciFile.open(QIODevice::ReadOnly | QIODevice::Text))
1089 	{
1090 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(crossIdFile);
1091 		return;
1092 	}
1093 	const QStringList& allRecords = QString::fromUtf8(ciFile.readAll()).split('\n');
1094 	ciFile.close();
1095 
1096 	crossid crossIdData;
1097 
1098 	int readOk=0;
1099 	int totalRecords=0;
1100 	int lineNumber=0;
1101 	// record structure is delimited with a 'tab' character. Example record strings:
1102 	// "1	128522	224700"
1103 	// "2	165988	224690"
1104 	for (const auto& record : allRecords)
1105 	{
1106 		++lineNumber;
1107 		// skip comments and empty lines
1108 		if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
1109 			continue;
1110 
1111 		++totalRecords;
1112 		const QStringList& fields = record.split('\t');
1113 		if (fields.size()!=5)
1114 		{
1115 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(crossIdFile)
1116 				   << " - record does not match record pattern";
1117 			continue;
1118 		}
1119 		else
1120 		{
1121 			// The record is the right format.  Extract the fields
1122 			bool ok;
1123 			int hip = fields.at(0).toInt(&ok);
1124 			if (!ok)
1125 			{
1126 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(crossIdFile)
1127 					   << " - failed to convert " << fields.at(0) << "to a number";
1128 				continue;
1129 			}
1130 
1131 			QString hipstar = QString("%1%2").arg(hip).arg(fields.at(1).trimmed());
1132 			crossIdData.sao = fields.at(2).toInt(&ok);
1133 			crossIdData.hd = fields.at(3).toInt(&ok);
1134 			crossIdData.hr = fields.at(4).toInt(&ok);
1135 
1136 			crossIdMap[hipstar] = crossIdData;
1137 			if (crossIdData.sao>0)
1138 				saoStarsIndex[crossIdData.sao] = hip;
1139 			if (crossIdData.hd>0)
1140 				hdStarsIndex[crossIdData.hd] = hip;
1141 			if (crossIdData.hr>0)
1142 				hrStarsIndex[crossIdData.hr] = hip;
1143 
1144 			++readOk;
1145 		}
1146 	}
1147 
1148 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "cross-identification data records for stars";
1149 }
1150 
loadPlxErr(const QString & plxErrFile)1151 void StarMgr::loadPlxErr(const QString& plxErrFile)
1152 {
1153 	// TODO: This is temporary solution for display parallax errors until format of stars catalogs will not be changed!
1154 	hipParallaxErrors.clear();
1155 
1156 	qDebug() << "Loading parallax errors data from" << QDir::toNativeSeparators(plxErrFile);
1157 	QFile ciFile(plxErrFile);
1158 	if (!ciFile.open(QIODevice::ReadOnly | QIODevice::Text))
1159 	{
1160 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(plxErrFile);
1161 		return;
1162 	}
1163 	const QStringList& allRecords = QString::fromUtf8(ciFile.readAll()).split('\n');
1164 	ciFile.close();
1165 
1166 	int readOk=0;
1167 	int totalRecords=0;
1168 	int lineNumber=0;
1169 	// record structure is delimited with a 'tab' character. Example record strings:
1170 	// "1	0.0606"
1171 	// "2	0.3193"
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()!=2)
1182 		{
1183 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(plxErrFile)
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;
1191 			int hip = fields.at(0).toInt(&ok);
1192 			if (!ok)
1193 			{
1194 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(plxErrFile)
1195 					   << " - failed to convert " << fields.at(0) << "to a number";
1196 				continue;
1197 			}
1198 			hipParallaxErrors[hip] = fields.at(1).toFloat(&ok);
1199 
1200 			++readOk;
1201 		}
1202 	}
1203 
1204 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "parallax error data records for stars";
1205 }
1206 
loadPMData(const QString & pmDataFile)1207 void StarMgr::loadPMData(const QString &pmDataFile)
1208 {
1209 	// TODO: This is temporary solution for display parallax errors until format of stars catalogs will not be changed!
1210 	hipPMData.clear();
1211 
1212 	qDebug() << "Loading proper motion data from" << QDir::toNativeSeparators(pmDataFile);
1213 	QFile ciFile(pmDataFile);
1214 	if (!ciFile.open(QIODevice::ReadOnly | QIODevice::Text))
1215 	{
1216 		qWarning() << "WARNING - could not open" << QDir::toNativeSeparators(pmDataFile);
1217 		return;
1218 	}
1219 	const QStringList& allRecords = QString::fromUtf8(ciFile.readAll()).split('\n');
1220 	ciFile.close();
1221 
1222 	int readOk=0;
1223 	int totalRecords=0;
1224 	int lineNumber=0;
1225 	// record structure is delimited with a 'tab' character. Example record strings:
1226 	// "1	-4.58	-1.61"
1227 	// "2	179.70	1.40"
1228 	for (const auto& record : allRecords)
1229 	{
1230 		++lineNumber;
1231 		// skip comments and empty lines
1232 		if (record.startsWith("//") || record.startsWith("#") || record.isEmpty())
1233 			continue;
1234 
1235 		++totalRecords;
1236 		const QStringList& fields = record.split('\t');
1237 		if (fields.size()!=3)
1238 		{
1239 			qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(pmDataFile)
1240 				   << " - record does not match record pattern";
1241 			continue;
1242 		}
1243 		else
1244 		{
1245 			// The record is the right format.  Extract the fields
1246 			bool ok;
1247 			int hip = fields.at(0).toInt(&ok);
1248 			if (!ok)
1249 			{
1250 				qWarning() << "WARNING - parse error at line" << lineNumber << "in" << QDir::toNativeSeparators(pmDataFile)
1251 					   << " - failed to convert " << fields.at(0) << "to a number";
1252 				continue;
1253 			}
1254 			PMData properMotion;
1255 			properMotion.first = fields.at(1).toFloat(&ok);
1256 			properMotion.second = fields.at(2).toFloat(&ok);
1257 			hipPMData[hip] = properMotion;
1258 
1259 			++readOk;
1260 		}
1261 	}
1262 
1263 	qDebug() << "Loaded" << readOk << "/" << totalRecords << "proper motion data records for stars";
1264 }
1265 
getMaxSearchLevel() const1266 int StarMgr::getMaxSearchLevel() const
1267 {
1268 	int rval = -1;
1269 	for (const auto* z : gridLevels)
1270 	{
1271 		const float mag_min = 0.001f*z->mag_min;
1272 		RCMag rcmag;
1273 		if (StelApp::getInstance().getCore()->getSkyDrawer()->computeRCMag(mag_min, &rcmag)==false)
1274 			break;
1275 		rval = z->level;
1276 	}
1277 	return rval;
1278 }
1279 
1280 
1281 // Draw all the stars
draw(StelCore * core)1282 void StarMgr::draw(StelCore* core)
1283 {
1284 	const StelProjectorP prj = core->getProjection(StelCore::FrameJ2000);
1285 	StelSkyDrawer* skyDrawer = core->getSkyDrawer();
1286 	// If stars are turned off don't waste time below
1287 	// projecting all stars just to draw disembodied labels
1288 	if (!static_cast<bool>(starsFader.getInterstate()))
1289 		return;
1290 
1291 	int maxSearchLevel = getMaxSearchLevel();
1292 	QVector<SphericalCap> viewportCaps = prj->getViewportConvexPolygon()->getBoundingSphericalCaps();
1293 	viewportCaps.append(core->getVisibleSkyArea());
1294 	const GeodesicSearchResult* geodesic_search_result = core->getGeodesicGrid(maxSearchLevel)->search(viewportCaps,maxSearchLevel);
1295 
1296 	// Set temporary static variable for optimization
1297 	const float names_brightness = labelsFader.getInterstate() * starsFader.getInterstate();
1298 
1299 	// prepare for aberration: Explan. Suppl. 2013, (7.38)
1300 	const bool withAberration=core->getUseAberration();
1301 	Vec3d vel(0.);
1302 	if (withAberration)
1303 	{
1304 		vel=core->getCurrentPlanet()->getHeliocentricEclipticVelocity();
1305 		StelCore::matVsop87ToJ2000.transfo(vel);
1306 		vel*=core->getAberrationFactor()*(AU/(86400.0*SPEED_OF_LIGHT));
1307 	}
1308 	const Vec3f velf=vel.toVec3f();
1309 
1310 	// Prepare openGL for drawing many stars
1311 	StelPainter sPainter(prj);
1312 	sPainter.setFont(starFont);
1313 	skyDrawer->preDrawPointSource(&sPainter);
1314 
1315 	// Prepare a table for storing precomputed RCMag for all ZoneArrays
1316 	RCMag rcmag_table[RCMAG_TABLE_SIZE];
1317 
1318 	// Draw all the stars of all the selected zones
1319 	for (const auto* z : gridLevels)
1320 	{
1321 		int limitMagIndex=RCMAG_TABLE_SIZE;
1322 		const float mag_min = 0.001f*z->mag_min;
1323 		const float k = (0.001f*z->mag_range)/z->mag_steps; // MagStepIncrement
1324 		for (int i=0;i<RCMAG_TABLE_SIZE;++i)
1325 		{
1326 			const float mag = mag_min+k*i;
1327 			if (skyDrawer->computeRCMag(mag, &rcmag_table[i])==false)
1328 			{
1329 				if (i==0)
1330 					goto exit_loop;
1331 
1332 				// The last magnitude at which the star is visible
1333 				limitMagIndex = i-1;
1334 
1335 				// We reached the point where stars are not visible anymore
1336 				// Fill the rest of the table with zero and leave.
1337 				for (;i<RCMAG_TABLE_SIZE;++i)
1338 				{
1339 					rcmag_table[i].luminance=0;
1340 					rcmag_table[i].radius=0;
1341 				}
1342 				break;
1343 			}
1344 			rcmag_table[i].radius *= starsFader.getInterstate();
1345 		}
1346 		lastMaxSearchLevel = z->level;
1347 
1348 		int maxMagStarName = 0;
1349 		if (labelsFader.getInterstate()>0.f)
1350 		{
1351 			// Adapt magnitude limit of the stars labels according to FOV and labelsAmount
1352 			float maxMag = (skyDrawer->getLimitMagnitude()-6.5f)*0.7f+(static_cast<float>(labelsAmount)*1.2f)-2.f;
1353 			int x = static_cast<int>((maxMag-mag_min)/k);
1354 			if (x > 0)
1355 				maxMagStarName = x;
1356 		}
1357 		int zone;
1358 
1359 		for (GeodesicSearchInsideIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
1360 			z->draw(&sPainter, zone, true, rcmag_table, limitMagIndex, core, maxMagStarName, names_brightness, viewportCaps, withAberration, velf);
1361 		for (GeodesicSearchBorderIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
1362 			z->draw(&sPainter, zone, false, rcmag_table, limitMagIndex, core, maxMagStarName,names_brightness, viewportCaps, withAberration, velf);
1363 	}
1364 	exit_loop:
1365 
1366 	// Finish drawing many stars
1367 	skyDrawer->postDrawPointSource(&sPainter);
1368 
1369 	if (objectMgr->getFlagSelectedObjectPointer())
1370 		drawPointer(sPainter, core);
1371 }
1372 
1373 
1374 // Return a QList containing the stars located
1375 // inside the limFov circle around position vv (in J2000 frame without aberration)
searchAround(const Vec3d & vv,double limFov,const StelCore * core) const1376 QList<StelObjectP > StarMgr::searchAround(const Vec3d& vv, double limFov, const StelCore* core) const
1377 {
1378 	QList<StelObjectP > result;
1379 	if (!getFlagStars())
1380 		return result;
1381 
1382 	Vec3d v(vv);
1383 	v.normalize();
1384 
1385 	// find any vectors h0 and h1 (length 1), so that h0*v=h1*v=h0*h1=0
1386 	int i;
1387 	{
1388 		const double a0 = fabs(v[0]);
1389 		const double a1 = fabs(v[1]);
1390 		const double a2 = fabs(v[2]);
1391 		if (a0 <= a1)
1392 		{
1393 			if (a0 <= a2) i = 0;
1394 			else i = 2;
1395 		} else
1396 		{
1397 			if (a1 <= a2) i = 1;
1398 			else i = 2;
1399 		}
1400 	}
1401 	Vec3d h0(0.0,0.0,0.0);
1402 	h0[i] = 1.0;
1403 	Vec3d h1 = h0 ^ v;
1404 	h1.normalize();
1405 	h0 = h1 ^ v;
1406 	h0.normalize();
1407 
1408 	// Now we have h0*v=h1*v=h0*h1=0.
1409 	// Construct a region with 4 corners e0,e1,e2,e3 inside which all desired stars must be:
1410 	double f = 1.4142136 * tan(limFov * M_PI/180.0);
1411 	h0 *= f;
1412 	h1 *= f;
1413 	Vec3d e0 = v + h0;
1414 	Vec3d e1 = v + h1;
1415 	Vec3d e2 = v - h0;
1416 	Vec3d e3 = v - h1;
1417 	f = 1.0/e0.length();
1418 	e0 *= f;
1419 	e1 *= f;
1420 	e2 *= f;
1421 	e3 *= f;
1422 	// Search the triangles
1423 	SphericalConvexPolygon c(e3, e2, e2, e0);
1424 	const GeodesicSearchResult* geodesic_search_result = core->getGeodesicGrid(lastMaxSearchLevel)->search(c.getBoundingSphericalCaps(),lastMaxSearchLevel);
1425 
1426 	// Iterate over the stars inside the triangles
1427 	f = cos(limFov * M_PI/180.);
1428 	for (auto* z : gridLevels)
1429 	{
1430 		//qDebug() << "search inside(" << it->first << "):";
1431 		int zone;
1432 		for (GeodesicSearchInsideIterator it1(*geodesic_search_result,z->level);(zone = it1.next()) >= 0;)
1433 		{
1434 			z->searchAround(core, zone,v,f,result);
1435 			//qDebug() << " " << zone;
1436 		}
1437 		//qDebug() << StelUtils::getEndLineChar() << "search border(" << it->first << "):";
1438 		for (GeodesicSearchBorderIterator it1(*geodesic_search_result,z->level); (zone = it1.next()) >= 0;)
1439 		{
1440 			z->searchAround(core, zone,v,f,result);
1441 			//qDebug() << " " << zone;
1442 		}
1443 	}
1444 	return result;
1445 }
1446 
1447 
1448 //! Update i18 names from english names according to passed translator.
1449 //! The translation is done using gettext with translated strings defined in translations.h
updateI18n()1450 void StarMgr::updateI18n()
1451 {
1452 	const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
1453 	commonNamesMapI18n.clear();
1454 	commonNamesIndexI18n.clear();
1455 	additionalNamesMapI18n.clear();
1456 	additionalNamesIndexI18n.clear();
1457 	for (QHash<int,QString>::ConstIterator it(commonNamesMap.constBegin());it!=commonNamesMap.constEnd();it++)
1458 	{
1459 		const int i = it.key();
1460 		const QString t(trans.qtranslate(it.value()));
1461 		commonNamesMapI18n[i] = t;
1462 		commonNamesIndexI18n[t.toUpper()] = i;
1463 	}
1464 	for (QHash<int,QString>::ConstIterator ita(additionalNamesMap.constBegin());ita!=additionalNamesMap.constEnd();ita++)
1465 	{
1466 		const int i = ita.key();
1467 		QStringList a = ita.value().split(" - ");
1468 		QStringList tn;
1469 		for (const auto& str : a)
1470 		{
1471 			QString tns = trans.qtranslate(str);
1472 			tn << tns;
1473 			additionalNamesIndexI18n[tns.toUpper()] = i;
1474 		}
1475 		const QString r = tn.join(" - ");
1476 		additionalNamesMapI18n[i] = r;
1477 	}
1478 }
1479 
1480 // Search the star by HP number
searchHP(int hp) const1481 StelObjectP StarMgr::searchHP(int hp) const
1482 {
1483 	if (0 < hp && hp <= NR_OF_HIP)
1484 	{
1485 		const Star1 *const s = hipIndex[hp].s;
1486 		if (s)
1487 		{
1488 			const SpecialZoneArray<Star1> *const a = hipIndex[hp].a;
1489 			const SpecialZoneData<Star1> *const z = hipIndex[hp].z;
1490 			return s->createStelObject(a,z);
1491 		}
1492 	}
1493 	return StelObjectP();
1494 }
1495 
searchByNameI18n(const QString & nameI18n) const1496 StelObjectP StarMgr::searchByNameI18n(const QString& nameI18n) const
1497 {
1498 	QString objw = nameI18n.toUpper();
1499 
1500 	// Search by I18n common name
1501 	auto it = commonNamesIndexI18n.find(objw);
1502 	if (it!=commonNamesIndexI18n.end())
1503 		return searchHP(it.value());
1504 
1505 	if (getFlagAdditionalNames())
1506 	{
1507 		// Search by I18n additional common names
1508 		auto ita = additionalNamesIndexI18n.find(objw);
1509 		if (ita!=additionalNamesIndexI18n.end())
1510 			return searchHP(ita.value());
1511 	}
1512 
1513 	return searchByName(nameI18n);
1514 }
1515 
1516 
searchByName(const QString & name) const1517 StelObjectP StarMgr::searchByName(const QString& name) const
1518 {
1519 	QString objw = name.toUpper();
1520 
1521 	// Search by HP number if it's an HP formatted number
1522 	QRegularExpression rx("^\\s*(HP|HIP)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1523 	QRegularExpressionMatch match=rx.match(objw);
1524 	if (match.hasMatch())
1525 		return searchHP(match.captured(2).toInt());
1526 
1527 	// Search by SAO number if it's an SAO formatted number
1528 	QRegularExpression rx2("^\\s*(SAO)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1529 	match=rx2.match(objw);
1530 	if (match.hasMatch())
1531 	{
1532 		auto sao = saoStarsIndex.find(match.captured(2).toInt());
1533 		if (sao!=saoStarsIndex.end())
1534 			return searchHP(sao.value());
1535 	}
1536 
1537 	// Search by HD number if it's an HD formatted number
1538 	QRegularExpression rx3("^\\s*(HD)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1539 	match=rx3.match(objw);
1540 	if (match.hasMatch())
1541 	{
1542 		auto hd = hdStarsIndex.find(match.captured(2).toInt());
1543 		if (hd!=hdStarsIndex.end())
1544 			return searchHP(hd.value());
1545 	}
1546 
1547 	// Search by HR number if it's an HR formatted number
1548 	QRegularExpression rx4("^\\s*(HR)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1549 	match=rx4.match(objw);
1550 	if (match.hasMatch())
1551 	{
1552 		auto hr = hrStarsIndex.find(match.captured(2).toInt());
1553 		if (hr!=hrStarsIndex.end())
1554 			return searchHP(hr.value());
1555 	}
1556 
1557 	// Search by English common name
1558 	auto it = commonNamesIndex.find(objw);
1559 	if (it!=commonNamesIndex.end())
1560 		return searchHP(it.value());
1561 
1562 	if (getFlagAdditionalNames())
1563 	{
1564 		// Search by English additional common names
1565 		auto ita = additionalNamesIndex.find(objw);
1566 		if (ita!=additionalNamesIndex.end())
1567 			return searchHP(ita.value());
1568 	}
1569 
1570 	// Search by scientific name
1571 	auto itd = sciDesignationsIndexI18n.find(name); // case sensitive!
1572 	if (itd!=sciDesignationsIndexI18n.end())
1573 		return searchHP(itd.value());
1574 	auto itdi = sciDesignationsIndexI18n.find(objw); // case insensitive!
1575 	if (itdi!=sciDesignationsIndexI18n.end())
1576 		return searchHP(itdi.value());
1577 
1578 	// Search by scientific name
1579 	auto eitd = sciExtraDesignationsIndexI18n.find(name); // case sensitive!
1580 	if (eitd!=sciExtraDesignationsIndexI18n.end())
1581 		return searchHP(eitd.value());
1582 	auto eitdi = sciExtraDesignationsIndexI18n.find(objw); // case insensitive!
1583 	if (eitdi!=sciExtraDesignationsIndexI18n.end())
1584 		return searchHP(eitdi.value());
1585 
1586 	// Search by GCVS name
1587 	auto it4 = varStarsIndexI18n.find(objw);
1588 	if (it4!=varStarsIndexI18n.end())
1589 		return searchHP(it4.value());
1590 
1591 	// Search by WDS name
1592 	auto wdsIt = wdsStarsIndexI18n.find(objw);
1593 	if (wdsIt!=wdsStarsIndexI18n.end())
1594 		return searchHP(wdsIt.value());
1595 
1596 	return StelObjectP();
1597 }
1598 
searchByID(const QString & id) const1599 StelObjectP StarMgr::searchByID(const QString &id) const
1600 {
1601 	return searchByName(id);
1602 }
1603 
1604 //! Find and return the list of at most maxNbItem objects auto-completing the passed object name.
listMatchingObjects(const QString & objPrefix,int maxNbItem,bool useStartOfWords) const1605 QStringList StarMgr::listMatchingObjects(const QString& objPrefix, int maxNbItem, bool useStartOfWords) const
1606 {
1607 	QStringList result;
1608 	if (maxNbItem <= 0 || !getFlagStars())
1609 		return result;
1610 
1611 	QString objw = objPrefix.toUpper();
1612 	bool found;
1613 
1614 	// Search for common names
1615 	QMapIterator<QString, int> i(commonNamesIndexI18n);
1616 	while (i.hasNext())
1617 	{
1618 		i.next();
1619 		if (useStartOfWords && i.key().startsWith(objw))
1620 			found = true;
1621 		else if (!useStartOfWords && i.key().contains(objw))
1622 			found = true;
1623 		else
1624 			found = false;
1625 
1626 		if (found)
1627 		{
1628 			if (maxNbItem<=0)
1629 				break;
1630 			result.append(getCommonName(i.value()));
1631 			--maxNbItem;
1632 		}
1633 	}
1634 
1635 	QMapIterator<QString, int> j(commonNamesIndex);
1636 	while (j.hasNext())
1637 	{
1638 		j.next();
1639 		if (useStartOfWords && j.key().startsWith(objw))
1640 			found = true;
1641 		else if (!useStartOfWords && j.key().contains(objw))
1642 			found = true;
1643 		else
1644 			found = false;
1645 
1646 		if (found)
1647 		{
1648 			if (maxNbItem<=0)
1649 				break;
1650 			result.append(getCommonEnglishName(j.value()));
1651 			--maxNbItem;
1652 		}
1653 	}
1654 
1655 	if (getFlagAdditionalNames())
1656 	{
1657 		QMapIterator<QString, int> k(additionalNamesIndexI18n);
1658 		while (k.hasNext())
1659 		{
1660 			k.next();
1661 			QStringList names = getAdditionalNames(k.value()).split(" - ");
1662 			for (const auto &name : qAsConst(names))
1663 			{
1664 				if (useStartOfWords && name.startsWith(objw, Qt::CaseInsensitive))
1665 					found = true;
1666 				else if (!useStartOfWords && name.contains(objw, Qt::CaseInsensitive))
1667 					found = true;
1668 				else
1669 					found = false;
1670 
1671 				if (found)
1672 				{
1673 					if (maxNbItem<=0)
1674 						break;
1675 					result.append(name);
1676 					--maxNbItem;
1677 				}
1678 			}
1679 		}
1680 
1681 		QMapIterator<QString, int> l(additionalNamesIndex);
1682 		while (l.hasNext())
1683 		{
1684 			l.next();
1685 			QStringList names = getAdditionalEnglishNames(l.value()).split(" - ");
1686 			for (const auto &name : qAsConst(names))
1687 			{
1688 				if (useStartOfWords && name.startsWith(objw, Qt::CaseInsensitive))
1689 					found = true;
1690 				else if (!useStartOfWords && name.contains(objw, Qt::CaseInsensitive))
1691 					found = true;
1692 				else
1693 					found = false;
1694 
1695 				if (found)
1696 				{
1697 					if (maxNbItem<=0)
1698 						break;
1699 					result.append(name);
1700 					--maxNbItem;
1701 				}
1702 			}
1703 		}
1704 	}
1705 
1706 	// Search for sci names
1707 	QString bayerPattern = objPrefix;
1708 	QRegularExpression bayerRegEx(bayerPattern);
1709 	QString bayerPatternCI = objw;
1710 	QRegularExpression bayerRegExCI(bayerPatternCI);
1711 
1712 	// if the first character is a Greek letter, check if there's an index
1713 	// after it, such as "alpha1 Cen".
1714 	if (objPrefix.at(0).unicode() >= 0x0391 && objPrefix.at(0).unicode() <= 0x03A9)
1715 		bayerRegEx.setPattern(bayerPattern.insert(1,"\\d?"));
1716 	if (objw.at(0).unicode() >= 0x0391 && objw.at(0).unicode() <= 0x03A9)
1717 		bayerRegExCI.setPattern(bayerPatternCI.insert(1,"\\d?"));
1718 
1719 	for (auto it = sciDesignationsIndexI18n.lowerBound(objPrefix); it != sciDesignationsIndexI18n.end(); ++it)
1720 	{
1721 		if (it.key().indexOf(bayerRegEx)==0 || it.key().indexOf(objPrefix)==0)
1722 		{
1723 			if (maxNbItem<=0)
1724 				break;
1725 			QStringList names = getSciName(it.value()).split(" - ");
1726 			for (const auto &name : qAsConst(names))
1727 			{
1728 				if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
1729 					found = true;
1730 				else if (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive))
1731 					found = true;
1732 				else
1733 					found = false;
1734 
1735 				if (found)
1736 				{
1737 					if (maxNbItem<=0)
1738 						break;
1739 					result.append(name);
1740 					--maxNbItem;
1741 				}
1742 			}
1743 		}
1744 		else if (it.key().at(0) != objPrefix.at(0))
1745 			break;
1746 	}
1747 
1748 	for (auto it = sciDesignationsIndexI18n.lowerBound(objw); it != sciDesignationsIndexI18n.end(); ++it)
1749 	{
1750 		if (it.key().indexOf(bayerRegExCI)==0 || it.key().indexOf(objw)==0)
1751 		{
1752 			if (maxNbItem<=0)
1753 				break;
1754 			QStringList names = getSciName(it.value()).split(" - ");
1755 			for (const auto &name : qAsConst(names))
1756 			{
1757 				if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
1758 					found = true;
1759 				else if (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive))
1760 					found = true;
1761 				else
1762 					found = false;
1763 
1764 				if (found)
1765 				{
1766 					if (maxNbItem<=0)
1767 						break;
1768 					result.append(name);
1769 					--maxNbItem;
1770 				}
1771 			}
1772 		}
1773 		else if (it.key().at(0) != objw.at(0))
1774 			break;
1775 	}
1776 
1777 	for (auto it = sciExtraDesignationsIndexI18n.lowerBound(objPrefix); it != sciExtraDesignationsIndexI18n.end(); ++it)
1778 	{
1779 		if (it.key().indexOf(bayerRegEx)==0 || it.key().indexOf(objPrefix)==0)
1780 		{
1781 			if (maxNbItem<=0)
1782 				break;
1783 			QStringList names = getSciExtraName(it.value()).split(" - ");
1784 			for (const auto &name : qAsConst(names))
1785 			{
1786 				if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
1787 					found = true;
1788 				else if (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive))
1789 					found = true;
1790 				else
1791 					found = false;
1792 
1793 				if (found)
1794 				{
1795 					if (maxNbItem<=0)
1796 						break;
1797 					result.append(name);
1798 					--maxNbItem;
1799 				}
1800 			}
1801 		}
1802 		else if (it.key().at(0) != objPrefix.at(0))
1803 			break;
1804 	}
1805 
1806 	for (auto it = sciExtraDesignationsIndexI18n.lowerBound(objw); it != sciExtraDesignationsIndexI18n.end(); ++it)
1807 	{
1808 		if (it.key().indexOf(bayerRegExCI)==0 || it.key().indexOf(objw)==0)
1809 		{
1810 			if (maxNbItem<=0)
1811 				break;
1812 			QStringList names = getSciExtraName(it.value()).split(" - ");
1813 			for (const auto &name : qAsConst(names))
1814 			{
1815 				if (useStartOfWords && name.startsWith(objPrefix, Qt::CaseInsensitive))
1816 					found = true;
1817 				else if (!useStartOfWords && name.contains(objPrefix, Qt::CaseInsensitive))
1818 					found = true;
1819 				else
1820 					found = false;
1821 
1822 				if (found)
1823 				{
1824 					if (maxNbItem<=0)
1825 						break;
1826 					result.append(name);
1827 					--maxNbItem;
1828 				}
1829 			}
1830 		}
1831 		else if (it.key().at(0) != objw.at(0))
1832 			break;
1833 	}
1834 
1835 	// Search for sci names for var stars
1836 	for (auto it = varStarsIndexI18n.lowerBound(objw); it != varStarsIndexI18n.end(); ++it)
1837 	{
1838 		if (it.key().startsWith(objw))
1839 		{
1840 			if (maxNbItem<=0)
1841 				break;
1842 			result << getGcvsName(it.value());
1843 			--maxNbItem;
1844 		}
1845 		else
1846 			break;
1847 	}
1848 
1849 	// Add exact Hp catalogue numbers
1850 	QRegularExpression hpRx("^(HIP|HP)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1851 	QRegularExpressionMatch match=hpRx.match(objw);
1852 	if (match.hasMatch())
1853 	{
1854 		bool ok;
1855 		int hpNum = match.captured(2).toInt(&ok);
1856 		if (ok)
1857 		{
1858 			StelObjectP s = searchHP(hpNum);
1859 			if (s && maxNbItem>0)
1860 			{
1861 				result << QString("HIP%1").arg(hpNum);
1862 				maxNbItem--;
1863 			}
1864 		}
1865 	}
1866 
1867 	// Add exact SAO catalogue numbers
1868 	QRegularExpression saoRx("^(SAO)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1869 	match=saoRx.match(objw);
1870 	if (match.hasMatch())
1871 	{
1872 		int saoNum = match.captured(2).toInt();
1873 		auto sao = saoStarsIndex.find(saoNum);
1874 		if (sao!=saoStarsIndex.end())
1875 		{
1876 			StelObjectP s = searchHP(sao.value());
1877 			if (s && maxNbItem>0)
1878 			{
1879 				result << QString("SAO%1").arg(saoNum);
1880 				maxNbItem--;
1881 			}
1882 		}
1883 	}
1884 
1885 	// Add exact HD catalogue numbers
1886 	QRegularExpression hdRx("^(HD)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1887 	match=hdRx.match(objw);
1888 	if (match.hasMatch())
1889 	{
1890 		int hdNum = match.captured(2).toInt();
1891 		auto hd = hdStarsIndex.find(hdNum);
1892 		if (hd!=hdStarsIndex.end())
1893 		{
1894 			StelObjectP s = searchHP(hd.value());
1895 			if (s && maxNbItem>0)
1896 			{
1897 				result << QString("HD%1").arg(hdNum);
1898 				maxNbItem--;
1899 			}
1900 		}
1901 	}
1902 
1903 	// Add exact HR catalogue numbers
1904 	QRegularExpression hrRx("^(HR)\\s*(\\d+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1905 	match=hrRx.match(objw);
1906 	if (match.hasMatch())
1907 	{
1908 		int hrNum = match.captured(2).toInt();
1909 		auto hr = hrStarsIndex.find(hrNum);
1910 		if (hr!=hrStarsIndex.end())
1911 		{
1912 			StelObjectP s = searchHP(hr.value());
1913 			if (s && maxNbItem>0)
1914 			{
1915 				result << QString("HR%1").arg(hrNum);
1916 				maxNbItem--;
1917 			}
1918 		}
1919 	}
1920 
1921 	// Add exact WDS catalogue numbers
1922 	QRegularExpression wdsRx("^(WDS)\\s*(\\S+)\\s*$", QRegularExpression::CaseInsensitiveOption);
1923 	if (wdsRx.match(objw).hasMatch())
1924 	{
1925 		for (auto wds = wdsStarsIndexI18n.lowerBound(objw); wds != wdsStarsIndexI18n.end(); ++wds)
1926 		{
1927 			if (wds.key().startsWith(objw))
1928 			{
1929 				if (maxNbItem==0)
1930 					break;
1931 				result << getWdsName(wds.value());
1932 				--maxNbItem;
1933 			}
1934 			else
1935 				break;
1936 		}
1937 	}
1938 
1939 	result.sort();
1940 	return result;
1941 }
1942 
1943 //! Define font file name and size to use for star names display
setFontSize(int newFontSize)1944 void StarMgr::setFontSize(int newFontSize)
1945 {
1946 	starFont.setPixelSize(newFontSize);
1947 }
1948 
updateSkyCulture(const QString & skyCultureDir)1949 void StarMgr::updateSkyCulture(const QString& skyCultureDir)
1950 {
1951 	// Load culture star names in english
1952 	QString fic = StelFileMgr::findFile("skycultures/" + skyCultureDir + "/star_names.fab");
1953 	if (fic.isEmpty())
1954 		qDebug() << "Could not load star_names.fab for sky culture " << QDir::toNativeSeparators(skyCultureDir);
1955 	else
1956 		loadCommonNames(fic);
1957 
1958 	// Turn on sci names/catalog names for western cultures only
1959 	setFlagSciNames(skyCultureDir.contains("western", Qt::CaseInsensitive));
1960 	updateI18n();
1961 }
1962 
increaseStarsMagnitudeLimit()1963 void StarMgr::increaseStarsMagnitudeLimit()
1964 {
1965 	StelCore* core = StelApp::getInstance().getCore();
1966 	core->getSkyDrawer()->setCustomStarMagnitudeLimit(core->getSkyDrawer()->getCustomStarMagnitudeLimit() + 0.1);
1967 }
1968 
reduceStarsMagnitudeLimit()1969 void StarMgr::reduceStarsMagnitudeLimit()
1970 {
1971 	StelCore* core = StelApp::getInstance().getCore();
1972 	core->getSkyDrawer()->setCustomStarMagnitudeLimit(core->getSkyDrawer()->getCustomStarMagnitudeLimit() - 0.1);
1973 }
1974 
populateStarsDesignations()1975 void StarMgr::populateStarsDesignations()
1976 {
1977 	QString fic;
1978 	fic = StelFileMgr::findFile("stars/default/name.fab");
1979 	if (fic.isEmpty())
1980 		qWarning() << "WARNING: could not load scientific star names file: stars/default/name.fab";
1981 	else
1982 		loadSciNames(fic, false);
1983 
1984 	fic = StelFileMgr::findFile("stars/default/extra_name.fab");
1985 	if (fic.isEmpty())
1986 		qWarning() << "WARNING: could not load scientific star extra names file: stars/default/extra_name.fab";
1987 	else
1988 		loadSciNames(fic, true);
1989 
1990 	fic = StelFileMgr::findFile("stars/default/gcvs_hip_part.dat");
1991 	if (fic.isEmpty())
1992 		qWarning() << "WARNING: could not load variable stars file: stars/default/gcvs_hip_part.dat";
1993 	else
1994 		loadGcvs(fic);
1995 
1996 	fic = StelFileMgr::findFile("stars/default/wds_hip_part.dat");
1997 	if (fic.isEmpty())
1998 		qWarning() << "WARNING: could not load double stars file: stars/default/wds_hip_part.dat";
1999 	else
2000 		loadWds(fic);
2001 
2002 	fic = StelFileMgr::findFile("stars/default/cross-id.dat");
2003 	if (fic.isEmpty())
2004 		qWarning() << "WARNING: could not load cross-identification data file: stars/default/cross-id.dat";
2005 	else
2006 		loadCrossIdentificationData(fic);
2007 
2008 	fic = StelFileMgr::findFile("stars/default/hip_plx_err.dat");
2009 	if (fic.isEmpty())
2010 		qWarning() << "WARNING: could not load parallax errors data file: stars/default/hip_plx_err.dat";
2011 	else
2012 		loadPlxErr(fic);
2013 
2014 	fic = StelFileMgr::findFile("stars/default/hip_pm.dat");
2015 	if (fic.isEmpty())
2016 		qWarning() << "WARNING: could not load proper motion data file: stars/default/hip_pm.dat";
2017 	else
2018 		loadPMData(fic);
2019 }
2020 
listAllObjects(bool inEnglish) const2021 QStringList StarMgr::listAllObjects(bool inEnglish) const
2022 {
2023 	QStringList result;
2024 	if (inEnglish)
2025 	{
2026 		QMapIterator<QString, int> i(commonNamesIndex);
2027 		while (i.hasNext())
2028 		{
2029 			i.next();
2030 			result << getCommonEnglishName(i.value());
2031 		}
2032 	}
2033 	else
2034 	{
2035 		QMapIterator<QString, int> i(commonNamesIndexI18n);
2036 		while (i.hasNext())
2037 		{
2038 			i.next();
2039 			result << getCommonName(i.value());
2040 		}
2041 	}
2042 	return result;
2043 }
2044 
listAllObjectsByType(const QString & objType,bool inEnglish) const2045 QStringList StarMgr::listAllObjectsByType(const QString &objType, bool inEnglish) const
2046 {
2047 	QStringList result;
2048 	// type 1
2049 	bool isStarT1 = false;
2050 	QList<StelObjectP> starsT1;
2051 	// type 2
2052 	bool isStarT2 = false;
2053 	QList<QMap<StelObjectP, float>> starsT2;
2054 	int type = objType.toInt();
2055 	// Use SkyTranslator for translation star names
2056 	const StelTranslator& trans = StelApp::getInstance().getLocaleMgr().getSkyTranslator();
2057 	switch (type)
2058 	{
2059 		case 0: // Interesting double stars
2060 		{
2061 			static const QStringList doubleStars = {
2062 				"Asterope", "Atlas", "77 Tau", "δ1 Tau", "V1016 Ori",
2063 				"42 Ori", "ι Ori", "ζ Crv", "Mizar", "Zubenelgenubi",
2064 				"ω1 Sco", "λ Sco", "μ1 Sco", "ζ1 Sco", "ε1 Lyr", "δ1 Lyr",
2065 				"ν1 Sgr", "ο1 Cyg", "ο2 Cyg", "Algedi", "Albireo", "Rigel",
2066 				"Almaak", "ξ Boo", "Rasalgethi", "T Dra", "Kuma", "70 Oph",
2067 				"Castor", "ζ Her", "Keid", "Mesarthim", "Porrima", "Algieba",
2068 				"β Mon", "Izar", "44 Boo", "Acrab", "Tegmine", "φ2 Cnc",
2069 				"Regulus", "Cor Caroli", "ι Cas", "ε Ari", "Markeb", "γ1 Del",
2070 				"Bessel's Star", "55 Aqr", "σ Cas", "Achird", "Polaris", "36 Oph",
2071 				"65 UMa", "σ2 UMa", "55 Cnc", "16 Cyg", "HIP 28393", "HIP 84709"};
2072 			if (inEnglish)
2073 			{
2074 				result = doubleStars;
2075 			}
2076 			else
2077 			{
2078 				for (const auto &star : doubleStars)
2079 				{
2080 					result << trans.qtranslate(star);
2081 				}
2082 			}
2083 			break;
2084 		}
2085 		case 1: // Interesting variable stars
2086 		{
2087 			static const QStringList variableStars = {
2088 				"δ Cep", "Algol", "Mira", "λ Tau", "Sheliak", "ζ Gem", "μ Cep",
2089 				"Rasalgethi", "η Gem", "η Aql", "γ Cas", "Betelgeuse", "R And",
2090 				"U Ant", "θ Aps", "R Aql", "V Aql", "R Aqr", "ε Aur", "R Aur",
2091 				"AE Aur", "W Boo", "VZ Cam", "l Car", "γ Cas", "WZ Cas",
2092 				"S Cen", "Proxima", "T Cep", "U Cep", "R CMa", "VY CMa",
2093 				"S Cnc", "Alphekka", "R CrB", "T CrB", "U CrB", "R Cru",
2094 				"SU Cyg", "EU Del", "β Dor", "R Gem", "30 Her", "68 Her",
2095 				"R Hor", "Hind's Crimson Star", "R Leo", "RR Lyr", "U Mon",
2096 				"Mintaka", "VV Ori", "κ Pav", "β Peg", "Enif", "ζ Phe", "R Sct",
2097 				"U Sgr", "RY Sgr", "W UMa", "Polaris"};
2098 			if (inEnglish)
2099 			{
2100 				result = variableStars;
2101 			}
2102 			else
2103 			{
2104 				for (const auto &star : variableStars)
2105 				{
2106 					result << trans.qtranslate(star);
2107 				}
2108 			}
2109 			break;
2110 		}
2111 		case 2: // Bright double stars
2112 		{
2113 			starsT2 = doubleHipStars;
2114 			isStarT2 = true;
2115 			break;
2116 		}
2117 		case 3: // Bright variable stars
2118 		{
2119 			starsT2 = variableHipStars;
2120 			isStarT2 = true;
2121 			break;
2122 		}
2123 		case 4:
2124 		{
2125 			starsT2 = hipStarsHighPM;
2126 			isStarT2 = true;
2127 			break;
2128 		}
2129 		case 5: // Variable stars: Algol-type eclipsing systems
2130 		{
2131 			starsT2 = algolTypeStars;
2132 			isStarT2 = true;
2133 			break;
2134 		}
2135 		case 6: // Variable stars: the classical cepheids
2136 		{
2137 			starsT2 = classicalCepheidsTypeStars;
2138 			isStarT2 = true;
2139 			break;
2140 		}
2141 		case 7: // Bright carbon stars
2142 		{
2143 			starsT1 = carbonStars;
2144 			isStarT1 = true;
2145 			break;
2146 		}
2147 		case 8: // Bright barium stars
2148 		{
2149 			starsT1 = bariumStars;
2150 			isStarT1 = true;
2151 			break;
2152 		}
2153 		default:
2154 		{
2155 			// No stars yet?
2156 			break;
2157 		}
2158 	}
2159 
2160 	QString starName;
2161 	if (isStarT1)
2162 	{
2163 		for (const auto& star : qAsConst(starsT1))
2164 		{
2165 			starName = inEnglish ? star->getEnglishName() : star->getNameI18n();
2166 			if (!starName.isEmpty())
2167 				result << starName;
2168 			else
2169 				result << star->getID();
2170 		}
2171 	}
2172 
2173 	if (isStarT2)
2174 	{
2175 		for (const auto& star : qAsConst(starsT2))
2176 		{
2177 			starName = inEnglish ? star.firstKey()->getEnglishName() : star.firstKey()->getNameI18n();
2178 			if (!starName.isEmpty())
2179 				result << starName;
2180 			else
2181 				result << star.firstKey()->getID();
2182 		}
2183 	}
2184 
2185 	result.removeDuplicates();
2186 	return result;
2187 }
2188 
getStelObjectType() const2189 QString StarMgr::getStelObjectType() const
2190 {
2191 	return STAR_TYPE;
2192 }
2193