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

Stellarium / stellarium / 15291801018

28 May 2025 04:52AM UTC coverage: 11.931% (-0.02%) from 11.951%
15291801018

push

github

alex-w
Added new set of navigational stars (XIX century)

0 of 6 new or added lines in 2 files covered. (0.0%)

14124 existing lines in 74 files now uncovered.

14635 of 122664 relevant lines covered (11.93%)

18291.42 hits per line

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

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

21
#include "StelProjector.hpp"
22
#include "Constellation.hpp"
23
#include "StarMgr.hpp"
24

25
#include "StelTexture.hpp"
26
#include "StelPainter.hpp"
27
#include "StelApp.hpp"
28
#include "StelCore.hpp"
29
#include "StelUtils.hpp"
30
#include "ConstellationMgr.hpp"
31
#include "ZoneArray.hpp"
32
#include "StelModuleMgr.hpp"
33
#include "StelSkyCultureMgr.hpp"
34

35
#include <QString>
36
#include <QJsonArray>
37
#include <QTextStream>
38
#include <QDebug>
39
#include <QFontMetrics>
40
#include <QIODevice>
41

42
const QString Constellation::CONSTELLATION_TYPE = QStringLiteral("Constellation");
43

44
Vec3f Constellation::lineColor = Vec3f(0.4f,0.4f,0.8f);
45
Vec3f Constellation::labelColor = Vec3f(0.4f,0.4f,0.8f);
46
Vec3f Constellation::boundaryColor = Vec3f(0.8f,0.3f,0.3f);
47
bool Constellation::singleSelected = false;
48
bool Constellation::seasonalRuleEnabled = false;
49
float Constellation::artIntensityFovScale = 1.0f;
50

51
Constellation::Constellation()
×
52
        : numberOfSegments(0)
×
53
        , beginSeason(0)
×
54
        , endSeason(0)
×
UNCOV
55
        , singleStarConstellationRadius(cos(M_PI/360.)) // default radius of 1/2 degrees
×
56
        , artOpacity(1.f)
×
57
{
58
}
×
59

60
Constellation::~Constellation()
×
61
{
62
}
×
63

64
bool Constellation::read(const QJsonObject& data, StarMgr *starMgr)
×
65
{
66
        const QString id = data["id"].toString();
×
UNCOV
67
        const QStringList idParts = id.split(" ");
×
68
        if (idParts.size() == 3 && idParts[0] == "CON")
×
69
        {
UNCOV
70
                abbreviation = idParts[2].trimmed();
×
71
        }
72
        else
73
        {
UNCOV
74
                qWarning().nospace() << "Bad constellation id: expected \"CON cultureName Abbrev\", got " << id;
×
UNCOV
75
                return false;
×
76
        }
77

78
        const QJsonValue names = data["common_name"].toObject();
×
79
        culturalName.translated = names["english"].toString().trimmed();
×
80
        culturalName.native = names["native"].toString().trimmed();
×
81
        culturalName.pronounce = names["pronounce"].toString().trimmed();
×
82
        culturalName.IPA = names["IPA"].toString().trimmed();
×
UNCOV
83
        culturalName.transliteration = names["transliteration"].toString().trimmed();
×
84

85
        context = names["context"].toString().trimmed();
×
86
        if (culturalName.translated.isEmpty() && culturalName.native.isEmpty() && culturalName.pronounce.isEmpty())
×
UNCOV
87
                qWarning() << "No name for constellation" << id;
×
88

89
        constellation.clear();
×
UNCOV
90
        const QJsonArray &linesArray=data["lines"].toArray();
×
91
        if (linesArray.isEmpty())
×
92
        {
UNCOV
93
                qWarning().nospace() << "Empty lines array found for constellation " << id << " (" << culturalName.native << ")";
×
94
                return false;
×
95
        }
96

97
        const bool isDarkConstellation = (!linesArray[0].toArray().isEmpty() && linesArray[0].toArray()[0].isArray());
×
UNCOV
98
        if (isDarkConstellation)
×
99
        {
100
                for (const auto& polyLineObj : linesArray) // auto=[[ra, dec], [ra, dec], ...]
×
101
                {
102
                        const auto& polyLine = polyLineObj.toArray();
×
103
                        if (polyLine.size() < 2) continue; // one point doesn't define a segment
×
104

105
                        const auto numSegments = polyLine.size() - 1;
×
UNCOV
106
                        dark_constellation.reserve(dark_constellation.size() + 2 * numSegments);
×
107

108
                        Vec3d prevPoint = Vec3d(0.);
×
UNCOV
109
                        for (qsizetype i = 0; i < polyLine.size(); ++i)
×
110
                        {
111
                                if (polyLine[i].isString())
×
112
                                {
113
                                        // Can be "thin" or "bold", but we don't support these modifiers yet, so ignore this entry
UNCOV
114
                                        const auto s = polyLine[i].toString();
×
115
                                        if (s == "thin" || s == "bold")
×
116
                                                continue;
×
UNCOV
117
                                }
×
118

UNCOV
119
                                if (!polyLine[i].isArray())
×
120
                                {
121
                                        qWarning().nospace() << "Error in constellation " << id << ": bad point"
×
UNCOV
122
                                                             << polyLine[i].toString() << ": isn't an array of two numbers (RA and dec)";
×
123
                                        return false;
×
124
                                }
125
                                const QJsonArray arr = polyLine[i].toArray();
×
UNCOV
126
                                if (arr.size() != 2 || !arr[0].isDouble() || !arr[1].isDouble())
×
127
                                {
128
                                        qWarning().nospace() << "Error in constellation " << id << ": bad point"
×
UNCOV
129
                                                             << polyLine[i].toString() << ": isn't an array of two numbers (RA and dec)";
×
130
                                        return false;
×
131
                                }
132

UNCOV
133
                                const double RA = arr[0].toDouble() * (M_PI_180*15.);
×
UNCOV
134
                                const double DE = arr[1].toDouble() * M_PI_180;
×
135
                                Vec3d newPoint;
×
136
                                StelUtils::spheToRect(RA, DE, newPoint);
×
137

138
                                if (prevPoint != Vec3d(0.))
×
139
                                {
140
                                        dark_constellation.push_back(prevPoint);
×
UNCOV
141
                                        dark_constellation.push_back(newPoint);
×
142
                                }
143
                                prevPoint = newPoint;
×
144
                        }
×
145
                }
×
146
                numberOfSegments = dark_constellation.size() / 2;
×
147
                // Name tag should go to constellation's centre of gravity
148
                XYZname.set(0.,0.,0.);
×
UNCOV
149
                for(unsigned int ii=0;ii<numberOfSegments*2;++ii)
×
150
                {
151
                        XYZname+= dark_constellation[ii];
×
152
                }
153
        }
154
        else
155
        {
UNCOV
156
                for (const auto& polyLineObj : linesArray)
×
157
                {
158
                        const auto& polyLine = polyLineObj.toArray();
×
UNCOV
159
                        if (polyLine.size() < 2) continue; // one point doesn't define a segment
×
160

UNCOV
161
                        const auto numSegments = polyLine.size() - 1;
×
162
                        constellation.reserve(constellation.size() + 2 * numSegments);
×
163

UNCOV
164
                        StelObjectP prevPoint = nullptr;
×
165
                        for (qsizetype i = 0; i < polyLine.size(); ++i)
×
166
                        {
167
                                if (polyLine[i].isString())
×
168
                                {
169
                                        // Can be "thin" or "bold", but we don't support these modifiers yet, so ignore this entry
170
                                        const auto s = polyLine[i].toString();
×
171
                                        if (s == "thin" || s == "bold")
×
UNCOV
172
                                                continue;
×
173
                                }
×
174
                                const StarId HP = StelUtils::getLongLong(polyLine[i]);
×
175
                                if (HP == 0)
×
176
                                {
177
                                        qWarning().nospace() << "Error in constellation " << abbreviation << ": bad HIP " << HP;
×
UNCOV
178
                                        return false;
×
179
                                }
180

181
                                const auto newPoint = HP <= NR_OF_HIP ? starMgr->searchHP(HP)
×
182
                                                                      : starMgr->searchGaia(HP);
×
UNCOV
183
                                if (!newPoint)
×
184
                                {
UNCOV
185
                                        qWarning().nospace() << "Error in constellation " << abbreviation << ": can't find star HIP " << HP;
×
UNCOV
186
                                        return false;
×
187
                                }
UNCOV
188
                                if (prevPoint)
×
189
                                {
UNCOV
190
                                        constellation.push_back(prevPoint);
×
191
                                        constellation.push_back(newPoint);
×
192
                                }
UNCOV
193
                                prevPoint = newPoint;
×
194
                        }
×
UNCOV
195
                }
×
196

197
                numberOfSegments = constellation.size() / 2;
×
UNCOV
198
                if (data.contains("single_star_radius"))
×
199
                {
200
                        double rd = data["single_star_radius"].toDouble(0.5);
×
201
                        singleStarConstellationRadius = cos(rd*M_PI/180.);
×
202
                }
203

204
                // Name tag should go to constellation's centre of gravity
205
                XYZname.set(0.,0.,0.);
×
206
                for(unsigned int ii=0;ii<numberOfSegments*2;++ii)
×
207
                {
208
                        XYZname+= constellation[ii]->getJ2000EquatorialPos(StelApp::getInstance().getCore());
×
209
                }
210
        }
211

212
        // At this point we have either a constellation or a dark_constellation filled
213

214

215
        XYZname.normalize();
×
216
        // Sometimes label placement is suboptimal. Allow a correction from the automatic solution in label_offset:[dRA_deg, dDec_deg]
UNCOV
217
        if (data.contains("label_offset"))
×
218
        {
UNCOV
219
                QJsonArray offset=data["label_offset"].toArray();
×
220
                if (offset.size()!=2)
×
UNCOV
221
                        qWarning() << "Bad constellation label offset given for " << id << ", ignoring";
×
222
                else
223
                {
224
                        double ra, dec;
225
                        StelUtils::rectToSphe(&ra, &dec, XYZname);
×
UNCOV
226
                        ra  += offset[0].toDouble()*M_PI_180;
×
UNCOV
227
                        dec += offset[1].toDouble()*M_PI_180;
×
228
                        StelUtils::spheToRect(ra, dec, XYZname);
×
229
                }
UNCOV
230
        }
×
231

UNCOV
232
        beginSeason = 1;
×
UNCOV
233
        endSeason = 12;
×
234
        const auto visib = data["visibility"];
×
UNCOV
235
        if (visib.isUndefined()) return true;
×
UNCOV
236
        const auto visibility = visib.toObject();
×
UNCOV
237
        const auto months = visibility["months"].toArray();
×
UNCOV
238
        if (months.size() != 2)
×
239
        {
UNCOV
240
                qWarning() << "Unexpected format of \"visibility\" entry in constellation" << id;
×
UNCOV
241
                return true; // not critical
×
242
        }
243
        beginSeason = months[0].toInt();
×
UNCOV
244
        endSeason = months[1].toInt();
×
UNCOV
245
        seasonalRuleEnabled = true;
×
246

UNCOV
247
        return true;
×
UNCOV
248
}
×
249

UNCOV
250
QString Constellation::getScreenLabel() const
×
251
{
UNCOV
252
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
UNCOV
253
        return getCultureLabel(scMgr->getScreenLabelStyle());
×
254
}
UNCOV
255
QString Constellation::getInfoLabel() const
×
256
{
UNCOV
257
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
UNCOV
258
        return getCultureLabel(scMgr->getInfoLabelStyle());
×
259
}
260

UNCOV
261
QString Constellation::getCultureLabel(StelObject::CulturalDisplayStyle style) const
×
262
{
UNCOV
263
        static StelSkyCultureMgr *scMgr=GETSTELMODULE(StelSkyCultureMgr);
×
UNCOV
264
        return scMgr->createCulturalLabel(culturalName, style, getNameI18n(), abbreviationI18n);
×
265
}
266

UNCOV
267
void Constellation::drawOptim(StelPainter& sPainter, const StelCore* core, const SphericalCap& viewportHalfspace) const
×
268
{
UNCOV
269
        if (lineFader.getInterstate()<=0.0001f)
×
270
                return;
×
271
        if (!isSeasonallyVisible())
×
272
                return;
×
273

274
        const bool isDarkConstellation = !(dark_constellation.empty());
×
UNCOV
275
        const float darkFactor = (isDarkConstellation? 0.6667f : 1.0f);
×
276
        sPainter.setColor(lineColor*darkFactor, lineFader.getInterstate());
×
277

278
        if (isDarkConstellation)
×
279
                for (unsigned int i=0;i<numberOfSegments;++i)
×
280
                {
281
                        Vec3d pos1=dark_constellation[2*i];
×
282
                        Vec3d pos2=dark_constellation[2*i+1];
×
283
                        //pos1.normalize();
284
                        //pos2.normalize();
UNCOV
285
                        sPainter.drawGreatCircleArc(pos1, pos2, &viewportHalfspace);
×
286
                }
287
        else
288
                for (unsigned int i=0;i<numberOfSegments;++i)
×
289
                {
UNCOV
290
                        Vec3d star1=constellation[2*i]->getJ2000EquatorialPos(core);
×
291
                        Vec3d star2=constellation[2*i+1]->getJ2000EquatorialPos(core);
×
UNCOV
292
                        star1.normalize();
×
293
                        star2.normalize();
×
UNCOV
294
                        if (star1.fuzzyEquals(star2))
×
295
                        {
296
                                // draw single-star segment as circle
UNCOV
297
                                SphericalCap scCircle(star1, singleStarConstellationRadius);
×
298
                                sPainter.drawSphericalRegion(&scCircle, StelPainter::SphericalPolygonDrawModeBoundary);
×
UNCOV
299
                        }
×
300
                        else
301
                                sPainter.drawGreatCircleArc(star1, star2, &viewportHalfspace);
×
302
                }
303
}
304

305
void Constellation::drawName(StelPainter& sPainter) const
×
306
{
UNCOV
307
        if (nameFader.getInterstate()==0.0f)
×
UNCOV
308
                return;
×
309

310
        // TODO: Find a solution of fallbacks when components are missing?
UNCOV
311
        if (isSeasonallyVisible())
×
312
        {
313
                QString name = getScreenLabel();
×
314
                sPainter.setColor(labelColor, nameFader.getInterstate());
×
UNCOV
315
                sPainter.drawText(static_cast<float>(XYname[0]), static_cast<float>(XYname[1]), name, 0., -sPainter.getFontMetrics().boundingRect(name).width()/2, 0, false);
×
316
        }
×
317
}
318

319
void Constellation::drawArtOptim(StelPainter& sPainter, const SphericalRegion& region, const Vec3d& obsVelocity) const
×
320
{
UNCOV
321
        if (isSeasonallyVisible())
×
322
        {
323
                const float intensity = artFader.getInterstate() * artOpacity * artIntensityFovScale;
×
324
                if (artTexture && intensity > 0.0f && region.intersects(boundingCap))
×
325
                {
UNCOV
326
                        sPainter.setColor(intensity,intensity,intensity);
×
327

328
                        // The texture is not fully loaded
329
                        if (artTexture->bind()==false)
×
330
                                return;
×
331

332
#ifdef Q_OS_LINUX
333
                        // Unfortunately applying aberration to the constellation artwork causes ugly artifacts visible on Linux.
334
                        // It is better to disable aberration in this case and have a tiny texture shift where it usually does not need to critically match.
335
                        sPainter.drawStelVertexArray(artPolygon, false, Vec3d(0.));
×
336
#else
337
                        sPainter.drawStelVertexArray(artPolygon, false, obsVelocity);
338
#endif
339
                }
340
        }
341
}
342

343
// Draw the art texture
344
void Constellation::drawArt(StelPainter& sPainter) const
×
345
{
346
        // Is this ever used?
347
        Q_ASSERT(0);
×
348
        sPainter.setBlending(true, GL_ONE, GL_ONE);
349
        sPainter.setCullFace(true);
350
        SphericalRegionP region = sPainter.getProjector()->getViewportConvexPolygon();
351
        drawArtOptim(sPainter, *region, Vec3d(0.));
352
        sPainter.setCullFace(false);
353
}
354

355
const Constellation* Constellation::isStarIn(const StelObject* s) const
×
356
{
357
        if (constellation.empty())
×
UNCOV
358
                return nullptr;
×
359

360
        for(unsigned int i=0;i<numberOfSegments*2;++i)
×
361
        {
362
                // constellation[i]==s test was not working
363
                if (constellation[i]->getID()==s->getID()) // don't compare englishNames, we may have duplicate names!
×
364
                {
365
                        // qDebug() << "Const matched. " << getEnglishName();
366
                        return this;
×
367
                }
368
        }
369
        return nullptr;
×
370
}
371

372
void Constellation::update(int deltaTime)
×
373
{
UNCOV
374
        lineFader.update(deltaTime);
×
375
        nameFader.update(deltaTime);
×
UNCOV
376
        artFader.update(deltaTime);
×
377
        boundaryFader.update(deltaTime);
×
378
}
×
379

380
void Constellation::drawBoundaryOptim(StelPainter& sPainter, const Vec3d& obsVelocity) const
×
381
{
UNCOV
382
        if (boundaryFader.getInterstate()==0.0f)
×
UNCOV
383
                return;
×
384

385
        sPainter.setBlending(true);
×
UNCOV
386
        sPainter.setColor(boundaryColor, boundaryFader.getInterstate());
×
387

388
        unsigned int i, j;
389
        size_t size;
390
        std::vector<Vec3d> *points;
391

UNCOV
392
        if (singleSelected) size = isolatedBoundarySegments.size();
×
UNCOV
393
        else size = sharedBoundarySegments.size();
×
394

UNCOV
395
        const SphericalCap& viewportHalfspace = sPainter.getProjector()->getBoundingCap();
×
396

UNCOV
397
        for (i=0;i<size;i++)
×
398
        {
UNCOV
399
                if (singleSelected) points = isolatedBoundarySegments[i];
×
UNCOV
400
                else points = sharedBoundarySegments[i];
×
401

UNCOV
402
                for (j=0;j<points->size()-1;j++)
×
403
                {
UNCOV
404
                        Vec3d point0=points->at(j)+obsVelocity;
×
UNCOV
405
                        point0.normalize();
×
UNCOV
406
                        Vec3d point1=points->at(j+1)+obsVelocity;
×
UNCOV
407
                        point1.normalize();
×
408

UNCOV
409
                        sPainter.drawGreatCircleArc(point0, point1, &viewportHalfspace);
×
410
                }
411
        }
412
}
413

UNCOV
414
bool Constellation::isSeasonallyVisible() const
×
415
{
416
        // Is supported seasonal rules by current sky culture?
UNCOV
417
        if (!seasonalRuleEnabled)
×
UNCOV
418
                return true;
×
419

UNCOV
420
        bool visible = false;
×
421
        int year, month, day;
422
        // Get the current month
UNCOV
423
        StelUtils::getDateFromJulianDay(StelApp::getInstance().getCore()->getJD(), &year, &month, &day);
×
UNCOV
424
        if (endSeason >= beginSeason)
×
425
        {
426
                // OK, it's a "normal" season rule...
UNCOV
427
                if ((month >= beginSeason) && (month <= endSeason))
×
UNCOV
428
                        visible = true;
×
429
        }
430
        else
431
        {
432
                // ...oops, it's a "inverted" season rule
UNCOV
433
                if (((month>=1) && (month<=endSeason)) || ((month>=beginSeason) && (month<=12)))
×
UNCOV
434
                        visible = true;
×
435
        }
UNCOV
436
        return visible;
×
437
}
438

UNCOV
439
QString Constellation::getInfoString(const StelCore *core, const InfoStringGroup &flags) const
×
440
{
441
        Q_UNUSED(core)
UNCOV
442
        QString str;
×
UNCOV
443
        QTextStream oss(&str);
×
444

UNCOV
445
        if (flags&Name)
×
446
        {
UNCOV
447
                oss << "<h2>" << getInfoLabel() << "</h2>";
×
448
        }
449

UNCOV
450
        if (flags&ObjectType)
×
UNCOV
451
                oss << QString("%1: <b>%2</b>").arg(q_("Type"), getObjectTypeI18n()) << "<br />";
×
452

UNCOV
453
        getSolarLunarInfoString(core, flags);
×
UNCOV
454
        postProcessInfoString(str, flags);
×
455

UNCOV
456
        return str;
×
UNCOV
457
}
×
458

UNCOV
459
StelObjectP Constellation::getBrightestStarInConstellation(void) const
×
460
{
UNCOV
461
        if (constellation.empty())
×
UNCOV
462
                return nullptr;
×
463

UNCOV
464
        float maxMag = 99.f;
×
UNCOV
465
        StelObjectP brightest;
×
466
        // maybe the brightest star has always odd index,
467
        // so check all segment endpoints:
UNCOV
468
        for (int i=2*static_cast<int>(numberOfSegments)-1;i>=0;i--)
×
469
        {
UNCOV
470
                const float Mag = constellation[i]->getVMagnitude(Q_NULLPTR);
×
UNCOV
471
                if (Mag < maxMag)
×
472
                {
UNCOV
473
                        brightest = constellation[i];
×
UNCOV
474
                        maxMag = Mag;
×
475
                }
476
        }
UNCOV
477
        return brightest;
×
UNCOV
478
}
×
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