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

Stellarium / stellarium / 7151446429

09 Dec 2023 01:19PM UTC coverage: 11.565% (-0.001%) from 11.566%
7151446429

Pull #3538

github

gzotti
Satellites: Avoid empty info subtitle
Pull Request #3538: Satellites: Restore shadow circle at arbitrary altitude

0 of 63 new or added lines in 4 files covered. (0.0%)

6 existing lines in 2 files now uncovered.

14843 of 128342 relevant lines covered (11.57%)

29000.96 hits per line

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

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

20
#include "Satellite.hpp"
21
#include "StelObject.hpp"
22
#include "StelObjectMgr.hpp"
23
#include "StelPainter.hpp"
24
#include "StelApp.hpp"
25
#include "StelCore.hpp"
26
#include "StelTexture.hpp"
27
#include "VecMath.hpp"
28
#include "StelUtils.hpp"
29
#include "StelTranslator.hpp"
30
#include "StelModuleMgr.hpp"
31
#include "StelLocaleMgr.hpp"
32

33
#include <QTextStream>
34
#include <QDebug>
35
#include <QVariant>
36
#include <QSettings>
37
#include <QByteArray>
38

39
#include <QVector3D>
40
#include <QMatrix4x4>
41

42
#include "gsatellite/gTime.hpp"
43
//#include "gsatellite/stdsat.h"
44

45
#include <cmath>
46

47
#define sqr(a) ((a)*(a))
48

49
const QString Satellite::SATELLITE_TYPE = QStringLiteral("Satellite");
15✔
50

51
// static data members - will be initialised in the Satellites class (the StelObjectMgr)
52
StelTextureSP Satellite::hintTexture;
53
bool Satellite::showLabels = true;
54
float Satellite::hintBrightness = 0.f;
55
float Satellite::hintScale = 1.f;
56
SphericalCap Satellite::viewportHalfspace = SphericalCap();
57
int Satellite::orbitLineSegments = 90;
58
int Satellite::orbitLineFadeSegments = 4;
59
int Satellite::orbitLineSegmentDuration = 20;
60
int Satellite::orbitLineThickness = 1;
61
bool Satellite::orbitLinesFlag = true;
62
bool Satellite::iconicModeFlag = false;
63
bool Satellite::hideInvisibleSatellitesFlag = false;
64
bool Satellite::coloredInvisibleSatellitesFlag = true;
65
Vec3f Satellite::invisibleSatelliteColor = Vec3f(0.2f,0.2f,0.2f);
66
Vec3f Satellite::transitSatelliteColor = Vec3f(0.f,0.f,0.f);
67
double Satellite::timeRateLimit = 1.0; // one JD per second by default
68
int Satellite::tleEpochAge = 30; // default age of TLE's epoch to mark TLE as outdated (used for filters)
69

70
bool Satellite::flagCFKnownStdMagnitude = false;
71
bool Satellite::flagCFApogee = false;
72
double Satellite::minCFApogee = 20000.;
73
double Satellite::maxCFApogee = 55000.;
74
bool Satellite::flagCFPerigee = false;
75
double Satellite::minCFPerigee = 200.;
76
double Satellite::maxCFPerigee = 1500.;
77
bool Satellite::flagCFEccentricity = false;
78
double Satellite::minCFEccentricity = 0.3;
79
double Satellite::maxCFEccentricity = 0.9;
80
bool Satellite::flagCFPeriod = false;
81
double Satellite::minCFPeriod = 0.;
82
double Satellite::maxCFPeriod = 150.;
83
bool Satellite::flagCFInclination = false;
84
double Satellite::minCFInclination = 120.;
85
double Satellite::maxCFInclination = 360.;
86
bool Satellite::flagCFRCS = false;
87
double Satellite::minCFRCS = 0.1;
88
double Satellite::maxCFRCS = 100.;
89
bool Satellite::flagVFAltitude = false;
90
double Satellite::minVFAltitude = 200.;
91
double Satellite::maxVFAltitude = 500.;
92
bool Satellite::flagVFMagnitude = false;
93
double Satellite::minVFMagnitude = 8.;
94
double Satellite::maxVFMagnitude = -8.;
95

96
#if (SATELLITES_PLUGIN_IRIDIUM == 1)
97
double Satellite::sunReflAngle = 180.;
98
//double Satellite::timeShift = 0.;
99
#endif
100

101
Satellite::Satellite(const QString& identifier, const QVariantMap& map)
×
102
        : initialized(false)
×
103
        , displayed(false)
×
104
        , orbitDisplayed(false)
×
105
        , userDefined(false)
×
106
        , newlyAdded(false)
×
107
        , orbitValid(false)
×
108
        , jdLaunchYearJan1(0)
×
109
        , stdMag(99.)
×
110
        , RCS(-1.)
×
111
        , status(StatusUnknown)
×
112
        , height(0.)
×
113
        , range(0.)
×
114
        , rangeRate(0.)
×
115
        , hintColor(0.f,0.f,0.f)
×
116
        , lastUpdated()        
×
117
        , isISS(false)        
×
118
        , pSatWrapper(nullptr)
×
119
        , visibility(gSatWrapper::UNKNOWN)
×
120
        , phaseAngle(0.)
×
121
        , infoColor(0.f,0.f,0.f)
×
122
        , orbitColor(0.f,0.f,0.f)
×
123
        , lastEpochCompForOrbit(0.)
×
124
        , epochTime(0.)
×
125
{
126
        // return initialized if the mandatory fields are not present
127
        if (identifier.isEmpty())
×
128
                return;
×
129
        if (!map.contains("name") || !map.contains("tle1") || !map.contains("tle2"))
×
130
                return;
×
131

132
        id = identifier;
×
133
        name  = map.value("name").toString();
×
134
        if (name.isEmpty())
×
135
                return;
×
136
        
137
        // If there are no such keys, these will be initialized with the default
138
        // values given them above.
139
        description = map.value("description", description).toString().trimmed();
×
140
        displayed = map.value("visible", displayed).toBool();
×
141
        orbitDisplayed = map.value("orbitVisible", orbitDisplayed).toBool();
×
142
        userDefined = map.value("userDefined", userDefined).toBool();
×
143
        stdMag = map.value("stdMag", 99.).toDouble();
×
144
        RCS = map.value("rcs", -1.).toDouble();
×
145
        status = map.value("status", StatusUnknown).toInt();
×
146
        // Satellite hint color
147
        QVariantList list = map.value("hintColor", QVariantList()).toList();
×
148
        if (list.count() == 3)
×
149
        {
150
                hintColor[0] = list.at(0).toFloat();
×
151
                hintColor[1] = list.at(1).toFloat();
×
152
                hintColor[2] = list.at(2).toFloat();
×
153
        }
154

155
        // Satellite orbit section color
156
        list = map.value("orbitColor", QVariantList()).toList();
×
157
        if (list.count() == 3)
×
158
        {
159
                orbitColor[0] = list.at(0).toFloat();
×
160
                orbitColor[1] = list.at(1).toFloat();
×
161
                orbitColor[2] = list.at(2).toFloat();
×
162
        }
163
        else
164
        {
165
                orbitColor = hintColor;
×
166
        }
167

168
        // Satellite info color
169
        list = map.value("infoColor", QVariantList()).toList();
×
170
        if (list.count() == 3)
×
171
        {
172
                infoColor[0] = list.at(0).toFloat();
×
173
                infoColor[1] = list.at(1).toFloat();
×
174
                infoColor[2] = list.at(2).toFloat();
×
175
        }
176
        else
177
        {
178
                infoColor = hintColor;
×
179
        }
180

181
        if (map.contains("comms"))
×
182
        {
183
                for (const auto& comm : map.value("comms").toList())
×
184
                {
185
                        QVariantMap commMap = comm.toMap();
×
186
                        CommLink c;
×
187
                        if (commMap.contains("frequency")) c.frequency = commMap.value("frequency").toDouble();
×
188
                        if (commMap.contains("modulation")) c.modulation = commMap.value("modulation").toString();
×
189
                        if (commMap.contains("description")) c.description = commMap.value("description").toString();
×
190
                        comms.append(c);
×
191
                }
×
192
        }
193

194
        QVariantList groupList =  map.value("groups", QVariantList()).toList();
×
195
        if (!groupList.isEmpty())
×
196
        {
197
                for (const auto& group : std::as_const(groupList))
×
198
                        groups.insert(group.toString());
×
199
        }
200

201
        // TODO: Somewhere here - some kind of TLE validation.
202
        QString line1 = map.value("tle1").toString();
×
203
        QString line2 = map.value("tle2").toString();
×
204
        setNewTleElements(line1, line2);
×
205
        // This also sets the international designator and launch year.
206

207
        QString dateString = map.value("lastUpdated").toString();
×
208
        if (!dateString.isEmpty())
×
209
                lastUpdated = QDateTime::fromString(dateString, Qt::ISODate);
×
210

211
        orbitValid = true;
×
212
        initialized = true;
×
213
        isISS = (name=="ISS" || name=="ISS (ZARYA)" || name=="ISS (NAUKA)");
×
214
        moon = GETSTELMODULE(SolarSystem)->getMoon();
×
215
        sun = GETSTELMODULE(SolarSystem)->getSun();
×
216

217
        visibilityDescription={
218
                { gSatWrapper::RADAR_SUN,        N_("The satellite and the observer are in sunlight") },
×
219
                { gSatWrapper::VISIBLE,                N_("The satellite is sunlit and the observer is in the dark") },
×
220
                { gSatWrapper::RADAR_NIGHT,        N_("The satellite isn't sunlit") },
×
221
                { gSatWrapper::PENUMBRAL,        N_("The satellite is in penumbra") },
×
222
                { gSatWrapper::ANNULAR,                N_("The satellite is eclipsed") },
×
223
                { gSatWrapper::BELOW_HORIZON,   N_("The satellite is below the horizon") }
×
224
        };
×
225

226
        update(0.);
×
227
}
×
228

229
Satellite::~Satellite()
×
230
{
231
        if (pSatWrapper)
×
232
        {
233
                delete pSatWrapper;
×
234
                pSatWrapper = nullptr;
×
235
        }
236
}
×
237

238
double Satellite::roundToDp(float n, int dp)
×
239
{
240
        // round n to dp decimal places
241
        return floor(static_cast<double>(n) * pow(10., dp) + .5) / pow(10., dp);
×
242
}
243

244
QString Satellite::getNameI18n() const
×
245
{
246
        return q_(name);
×
247
}
248

249
QVariantMap Satellite::getMap(void)
×
250
{
251
        QVariantMap map;
×
252
        map["name"] = name;        
×
253
        map["stdMag"] = stdMag;
×
254
        map["rcs"] = RCS;
×
255
        map["status"] = status;
×
256
        map["tle1"] = tleElements.first;
×
257
        map["tle2"] = tleElements.second;
×
258

259
        if (!description.isEmpty())
×
260
                map["description"] = description;
×
261

262
        map["visible"] = displayed;
×
263
        map["orbitVisible"] = orbitDisplayed;
×
264
        if (userDefined)
×
265
                map.insert("userDefined", userDefined);
×
266
        QVariantList col, orbitCol, infoCol;
×
267
        col << roundToDp(hintColor[0],3) << roundToDp(hintColor[1], 3) << roundToDp(hintColor[2], 3);
×
268
        orbitCol << roundToDp(orbitColor[0], 3) << roundToDp(orbitColor[1], 3) << roundToDp(orbitColor[2],3);
×
269
        infoCol << roundToDp(infoColor[0], 3) << roundToDp(infoColor[1], 3) << roundToDp(infoColor[2],3);
×
270
        map["hintColor"] = col;
×
271
        map["orbitColor"] = orbitCol;
×
272
        map["infoColor"] = infoCol;
×
273
        QVariantList commList;
×
274
        for (const auto& c : std::as_const(comms))
×
275
        {
276
                QVariantMap commMap;
×
277
                commMap["frequency"] = c.frequency;
×
278
                if (!c.modulation.isEmpty()) commMap["modulation"] = c.modulation;
×
279
                if (!c.description.isEmpty()) commMap["description"] = c.description;
×
280
                commList << commMap;
×
281
        }
×
282
        map["comms"] = commList;
×
283
        QVariantList groupList;
×
284
        for (const auto& g : std::as_const(groups))
×
285
        {
286
                groupList << g;
×
287
        }
288
        map["groups"] = groupList;
×
289

290
        if (!lastUpdated.isNull())
×
291
        {
292
                // A raw QDateTime is not a recognised JSON data type. --BM
293
                map["lastUpdated"] = lastUpdated.toString(Qt::ISODate);
×
294
        }
295

296
        return map;
×
297
}
×
298

299
float Satellite::getSelectPriority(const StelCore* core) const
×
300
{
301
        float limit = -10.f;
×
302
        if (flagVFAltitude) // the visual filter is enabled
×
303
                limit = (minVFAltitude<=height && height<=maxVFAltitude) ? -10.f : 50.f;
×
304

305
        if (flagVFMagnitude) // the visual filter is enabled
×
306
                limit = ((maxVFMagnitude<=getVMagnitude(core) && getVMagnitude(core)<=minVFMagnitude) && (stdMag<99. || RCS>0.)) ? -10.f : 50.f;
×
307

308
        return limit;
×
309
}
310

311
QString Satellite::getInfoString(const StelCore *core, const InfoStringGroup& flags) const
×
312
{
313
        QString str;
×
314
        QTextStream oss(&str);
×
315
        QString degree = QChar(0x00B0);
×
316
        const bool withDecimalDegree = StelApp::getInstance().getFlagShowDecimalDegrees();
×
317
        
318
        if (flags & Name)
×
319
        {
320
                oss << "<h2>" << getNameI18n() << "</h2>";
×
321
                if (!description.isEmpty())
×
322
                {
323
                        // Let's convert possible \n chars into <br/> in description of satellite
324
                        oss << q_(description).replace("\n", "<br/>") << "<br/>";
×
325
                }
326
        }
327
        
328
        if (flags & CatalogNumber)
×
329
        {
330
                QString catalogNumbers;
×
331
                if (internationalDesignator.isEmpty())
×
332
                        catalogNumbers = QString("NORAD %1").arg(id);
×
333
                else
334
                        catalogNumbers = QString("NORAD %1; %2 (COSPAR/NSSDC): %3").arg(id, q_("International Designator"), internationalDesignator);
×
335
                oss << catalogNumbers << "<br/><br/>";
×
336
        }
×
337

338
        if (flags & ObjectType)
×
339
                oss << QString("%1: <b>%2</b>").arg(q_("Type"), getObjectTypeI18n())  << "<br/>";
×
340
        
341
        if ((flags & Magnitude) && ((stdMag<99.) || (RCS>0.)) && (visibility==gSatWrapper::VISIBLE))
×
342
        {
343
                const int decimals = 2;
×
344
                const float airmass = getAirmass(core);
×
345
                oss << QString("%1: <b>%2</b>").arg(q_("Approx. magnitude"), QString::number(getVMagnitude(core), 'f', decimals));
×
346
                if (airmass>-1.f) // Don't show extincted magnitude much below horizon where model is meaningless.
×
347
                        oss << QString(" (%1 <b>%2</b> %3 <b>%4</b> %5)").arg(q_("reduced to"), QString::number(getVMagnitudeWithExtinction(core), 'f', decimals), q_("by"), QString::number(airmass, 'f', decimals), q_("Airmasses"));
×
348
                oss << "<br />";
×
349
        }
350

351
        // Ra/Dec etc.
352
        oss << getCommonInfoString(core, flags);
×
353

354
        if (flags&Distance)
×
355
        {
356
                QString km = qc_("km", "distance");
×
357
                // TRANSLATORS: Slant range: distance between the satellite and the observer
358
                oss << QString("%1: %2 %3").arg(q_("Range")).arg(qRound(range)).arg(km) << "<br/>";
×
359
                // TRANSLATORS: Rate at which the distance changes
360
                oss << QString("%1: %2 %3").arg(q_("Range rate")).arg(rangeRate, 5, 'f', 3).arg(qc_("km/s", "speed")) << "<br/>";
×
361
                // TRANSLATORS: Satellite altitude
362
                oss << QString("%1: %2 %3").arg(q_("Altitude")).arg(qRound(height)).arg(km) << "<br/>";
×
363
                oss << QString("%1 (WGS-84): %2 %3 / %4 %5").arg(q_("Perigee/apogee altitudes")).arg(qRound(perigee)).arg(km).arg(qRound(apogee)).arg(km) << "<br/>";
×
364
        }
×
365

366
        if (flags&Size && RCS>0.)
×
367
        {
368
                const double angularSize = getAngularRadius(core)*2.*M_PI_180;
×
369
                QString sizeStr = "";
×
370
                if (withDecimalDegree)
×
371
                        sizeStr = StelUtils::radToDecDegStr(angularSize, 5, false, true);
×
372
                else
373
                        sizeStr = StelUtils::radToDmsPStr(angularSize, 2);
×
374
                oss << QString("%1: %2").arg(q_("Approx. angular size"), sizeStr) << "<br />";
×
375
        }
×
376

377
        if (flags & Extra)
×
378
        {
379
                double orbitalPeriod = pSatWrapper->getOrbitalPeriod();
×
380
                if (orbitalPeriod>0.0)
×
381
                {
382
                        // TRANSLATORS: Revolutions per day - measurement of the frequency of a rotation
383
                        QString rpd = qc_("rpd","frequency");
×
384
                        // TRANSLATORS: minutes - orbital period for artificial satellites
385
                        QString mins = qc_("min", "period");
×
386
                        oss << QString("%1: %2 %3 (%4 &mdash; %5 %6)")
×
387
                               .arg(q_("Orbital period")).arg(orbitalPeriod, 5, 'f', 2)
×
388
                               .arg(mins, StelUtils::hoursToHmsStr(orbitalPeriod/60.0, true))
×
389
                               .arg(1440.0/orbitalPeriod, 9, 'f', 5).arg(rpd) << "<br/>";
×
390
                }
×
391
                double inclination = pSatWrapper->getOrbitalInclination();
×
392
                oss << QString("%1: %2 (%3%4)").arg(q_("Inclination"), StelUtils::decDegToDmsStr(inclination), QString::number(inclination, 'f', 4), degree) << "<br/>";
×
393
                oss << QString("%1: %2&deg;/%3&deg;").arg(q_("SubPoint (Lat./Long.)")).arg(latLongSubPointPosition[0], 5, 'f', 2).arg(latLongSubPointPosition[1], 5, 'f', 3) << "<br/>";
×
394
                
395
                //TODO: This one can be done better
396
                const char* xyz = "<b>X:</b> %1, <b>Y:</b> %2, <b>Z:</b> %3";
×
397
                QString temeCoords = QString(xyz).arg(qRound(position[0])).arg(qRound(position[1])).arg(qRound(position[2]));
×
398
                // TRANSLATORS: TEME (True Equator, Mean Equinox) is an Earth-centered inertial coordinate system
399
                oss << QString("%1: %2 %3").arg(q_("TEME coordinates"), temeCoords, qc_("km", "distance")) << "<br/>";
×
400
                
401
                QString temeVel = QString(xyz).arg(velocity[0], 5, 'f', 2).arg(velocity[1], 5, 'f', 2).arg(velocity[2], 5, 'f', 2);
×
402
                // TRANSLATORS: TEME (True Equator, Mean Equinox) is an Earth-centered inertial coordinate system
403
                oss << QString("%1: %2 %3").arg(q_("TEME velocity"), temeVel, qc_("km/s", "speed")) << "<br/>";
×
404

405
                QString pha = StelApp::getInstance().getFlagShowDecimalDegrees() ?
×
406
                                StelUtils::radToDecDegStr(phaseAngle,4,false,true) :
×
407
                                StelUtils::radToDmsStr(phaseAngle, true);
×
408
                oss << QString("%1: %2").arg(q_("Phase angle"), pha) << "<br />";
×
409

410
#if (SATELLITES_PLUGIN_IRIDIUM == 1)
411
                if (sunReflAngle>0)
412
                {  // Iridium
413
                        oss << QString("%1: %2%3").arg(q_("Sun reflection angle"))
414
                               .arg(sunReflAngle,0,'f',1)
415
                               .arg(degree);
416
                        oss << "<br />";
417
                }
418
#endif
419
                QString updDate;
×
420
                if (!lastUpdated.isValid())
×
421
                        updDate = qc_("unknown", "unknown date");
×
422
                else
423
                {
424
                        QDate sd = lastUpdated.date();
×
425
                        double hours = lastUpdated.time().hour() + lastUpdated.time().minute()/60. + lastUpdated.time().second()/3600.;
×
426
                        updDate = QString("%1 %2 %3 %4 %5").arg(sd.day())
×
427
                                        .arg(StelLocaleMgr::longGenitiveMonthName(sd.month())).arg(sd.year())
×
428
                                        .arg(qc_("at","at time"), StelUtils::hoursToHmsStr(hours, true));
×
429
                }
430
                oss << QString("%1: %2").arg(q_("Last updated TLE"), updDate) << "<br />";
×
431
                oss << QString("%1: %2").arg(q_("Epoch of the TLE"), tleEpoch) << "<br />";
×
432
                if (RCS>0.)
×
433
                        oss << QString("%1: %2 %3<sup>2</sup>").arg(q_("Radar cross-section (RCS)"), QString::number(RCS, 'f', 3), qc_("m","distance")) << "<br />";
×
434

435
                // Groups of the artificial satellites
436
                QStringList groupList;
×
437
                for (const auto&g : groups)
×
438
                        groupList << q_(g);
×
439

440
                if (!groupList.isEmpty())
×
441
                {
442
                        QString group = groups.count()>1 ? q_("Group") : q_("Groups");
×
443
                        oss << QString("%1: %2").arg(group, groupList.join(", ")) << "<br />";
×
444
                }
×
445

446
                if (status!=StatusUnknown)
×
447
                        oss << QString("%1: %2").arg(q_("Operational status"), getOperationalStatus()) << "<br />";
×
448
                //Visibility: Full text                
449
                oss << q_(visibilityDescription.value(visibility, "")) << "<br />";
×
450

NEW
451
                if (!comms.isEmpty() && (status!=Satellite::StatusNonoperational))
×
452
                {
453
                        oss << q_("Radio communication") << ":<br/>";
×
454
                        for (const auto& c : comms)
×
455
                        {
456
                                oss << getCommLinkInfo(c);
×
457
                        }
458
                }
459
                // Umbra circle info. May not generally be interesting.
460
                //oss << QString("%1: %2 km, %3: %4 km").arg(q_("Umbra distance"), QString::number(umbraDistance, 'f', 3), q_("Radius"), QString::number(umbraRadius, 'f', 3)) << "<br/>";
461
                //oss << QString("%1: %2 km, %3: %4 km").arg(q_("Penumbra distance"), QString::number(penumbraDistance, 'f', 3), q_("Radius"), QString::number(penumbraRadius, 'f', 3)) << "<br/>";
462
        }
×
463

464
        oss << getSolarLunarInfoString(core, flags);
×
465
        postProcessInfoString(str, flags);
×
466
        return str;
×
467
}
×
468

469
QString Satellite::getCommLinkInfo(CommLink comm) const
×
470
{
471
        QString commLinkData;
×
UNCOV
472
        if (!comm.modulation.isEmpty()) // OK, the signal modulation mode is exist
×
473
                commLinkData = comm.modulation;
×
474

475
        if (commLinkData.isEmpty()) // description cannot be empty!
×
476
                commLinkData = comm.description;
×
477
        else
478
                commLinkData.append(QString(" %1").arg(comm.description));
×
479

480
        if (commLinkData.isEmpty())
×
481
                return QString();
×
482

483
        // Translate some specific communications terms
484
        // See end of Satellites.cpp file to define translatable terms
485
        static const QStringList commTerms={ "uplink", "downlink", "beacon", "telemetry", "video", "broadband", "command", "meteorological", "maritime", "mobile telephony", "mobile", "repeater", "digipeater", "-band", "crew voice", "imaging", "mode" };
×
486
        for (auto& term: commTerms)
×
487
        {
488
                commLinkData.replace(term, qc_(term, "comms"));
×
489
        }
490
        commLinkData.replace("&", q_("and"));
×
491

492
        double dop = getDoppler(comm.frequency);
×
493
        double ddop = dop;
×
494
        QString sign;
×
495
        if (dop<0.)
×
496
        {
497
                sign='-';
×
498
                ddop*=-1;
×
499
        }
500
        else
501
                sign='+';
×
502

503
        commLinkData.append(QString(": %1 %2 (%3%4 %5)<br />").arg(QString::number(comm.frequency, 'f', 3), qc_("MHz", "frequency"), sign, QString::number(ddop, 'f', 3), qc_("kHz", "frequency")));
×
UNCOV
504
        return commLinkData;
×
505
}
×
506

507
// Calculate perigee and apogee altitudes for mean Earth radius
508
void Satellite::calculateSatDataFromLine2(QString tle)
×
509
{
510
        // Details: http://www.satobs.org/seesat/Dec-2002/0197.html
511
        const double k = 8681663.653;
×
512
        const double meanMotion = tle.left(63).right(11).toDouble();
×
513
        const double semiMajorAxis = std::cbrt((k/meanMotion)*(k/meanMotion));
×
514
        eccentricity = QString("0.%1").arg(tle.left(33).right(7)).toDouble();
×
515
        perigee = semiMajorAxis*(1.0 - eccentricity) - EARTH_RADIUS;
×
516
        apogee = semiMajorAxis*(1.0 + eccentricity) - EARTH_RADIUS;
×
517
        inclination = QString(tle.left(16).right(8)).toDouble();
×
518
}
×
519

520
// Calculate epoch of TLE
521
void Satellite::calculateEpochFromLine1(QString tle)
×
522
{
523
        QString epochStr;
×
524
        // Details: https://celestrak.org/columns/v04n03/ or https://en.wikipedia.org/wiki/Two-line_element_set
525
        int year = tle.left(20).right(2).toInt();
×
526
        if (year>=0 && year<57)
×
527
                year += 2000;
×
528
        else
529
                year += 1900;
×
530
        const double dayOfYear = tle.left(32).right(12).toDouble();
×
531
        QDate epoch = QDate(year, 1, 1).addDays(dayOfYear - 1);
×
532
        if (!epoch.isValid())
×
533
                epochStr = qc_("unknown", "unknown date");
×
534
        else
535
                epochStr = QString("%1 %2 %3, %4 UTC").arg(epoch.day())
×
536
                                .arg(StelLocaleMgr::longGenitiveMonthName(epoch.month())).arg(year)
×
537
                                .arg(StelUtils::hoursToHmsStr(24.*(dayOfYear-static_cast<int>(dayOfYear)), true));
×
538

539
        tleEpoch = epochStr;
×
540
        tleEpochJD = epoch.toJulianDay();
×
541
}
×
542

543
QVariantMap Satellite::getInfoMap(const StelCore *core) const
×
544
{
545
        QVariantMap map = StelObject::getInfoMap(core);
×
546

547
        map.insert("description", QString(description).replace("\n", " - "));
×
548
        map.insert("catalog", id);
×
549
        map.insert("tle1", tleElements.first);
×
550
        map.insert("tle2", tleElements.second);
×
551
        map.insert("tle-epoch", tleEpoch);
×
552

553
        if (!internationalDesignator.isEmpty())
×
554
                map.insert("international-designator", internationalDesignator);
×
555

556
        if (stdMag>98.) // replace whatever has been computed
×
557
        {
558
                map.insert("vmag", "?");
×
559
                map.insert("vmage", "?");
×
560
        }
561

562
        map.insert("range", range);
×
563
        map.insert("rangerate", rangeRate);
×
564
        map.insert("height", height);
×
565
        map.insert("subpoint-lat", latLongSubPointPosition[0]);
×
566
        map.insert("subpoint-long", latLongSubPointPosition[1]);
×
567
        map.insert("TEME-km-X", position[0]);
×
568
        map.insert("TEME-km-Y", position[1]);
×
569
        map.insert("TEME-km-Z", position[2]);
×
570
        map.insert("TEME-speed-X", velocity[0]);
×
571
        map.insert("TEME-speed-Y", velocity[1]);
×
572
        map.insert("TEME-speed-Z", velocity[2]);
×
573
        map.insert("inclination", pSatWrapper->getOrbitalInclination());
×
574
        map.insert("period", pSatWrapper->getOrbitalPeriod());
×
575
        map.insert("perigee-altitude", perigee);
×
576
        map.insert("apogee-altitude", apogee);
×
577
#if (SATELLITES_PLUGIN_IRIDIUM == 1)
578
        if (sunReflAngle>0.)
579
        {  // Iridium
580
                map.insert("sun-reflection-angle", sunReflAngle);
581
        }
582
#endif
583
        map.insert("operational-status", getOperationalStatus());
×
584
        map.insert("phase-angle", phaseAngle);
×
585
        map.insert("phase-angle-dms", StelUtils::radToDmsStr(phaseAngle));
×
586
        map.insert("phase-angle-deg", StelUtils::radToDecDegStr(phaseAngle));
×
587
        map.insert("visibility", visibilityDescription.value(visibility, ""));
×
588

589
        return map;
×
590
}
×
591

592
Vec3d Satellite::getJ2000EquatorialPos(const StelCore* core) const
×
593
{
594
        // Bugfix LP:1654331. I assume the elAzPosition has been computed without refraction! We must say this definitely.
595
        return core->altAzToJ2000(elAzPosition, StelCore::RefractionOff);
×
596
}
597

598
Vec3f Satellite::getInfoColor(void) const
×
599
{
600
        return infoColor;
×
601
}
602

603
float Satellite::getVMagnitude(const StelCore* core) const
×
604
{        
605
        Q_UNUSED(core)
606
        float vmag = 7.f; // Optimistic value of magnitude for artificial satellite without data for standard magnitude
×
607
        if (iconicModeFlag)
×
608
                vmag = 5.0;
×
609

610
        if (!iconicModeFlag && visibility != gSatWrapper::VISIBLE)
×
611
                vmag = 17.f; // Artificial satellite is invisible and 17 is hypothetical value of magnitude
×
612

613
        if (visibility==gSatWrapper::VISIBLE)
×
614
        {
615
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
616
                sunReflAngle = -1.;
617
#endif
618
                if (pSatWrapper && name.startsWith("STARLINK"))
×
619
                {
620
                        // Calculation of approx. visual magnitude for Starlink satellites
621
                        // described here: http://www.satobs.org/seesat/Aug-2020/0079.html
622
                        vmag = static_cast<float>(5.93 + 5 * std::log10 ( range / 1000 ));
×
623
                        if (name.contains("DARKSAT", Qt::CaseInsensitive)) // See https://arxiv.org/abs/2006.08422
×
624
                                vmag *= 0.78f;
×
625
                }
626
                else if (stdMag<99.) // OK, artificial satellite has value for standard magnitude
×
627
                {
628
                        // Calculation of approx. visual magnitude for artificial satellites
629
                        //
630
                        // The standard magnitude may be an estimate based on the mean cross-
631
                        // sectional area derived from its dimensions, or it may be a mean
632
                        // value derived from visual observations. The former are denoted by a
633
                        // letter "d" in column 37; the latter by a "v". To estimate the
634
                        // magnitude at other ranges and illuminations, use the following formula:
635
                        //
636
                        // mag = stdmag - 15.75 + 2.5 * log10 (range * range / fracil)
637
                        //
638
                        // where : stdmag = standard magnitude as defined above
639
                        //
640
                        // range = distance from observer to satellite, km
641
                        //
642
                        // fracil = fraction of satellite illuminated,
643
                        //            [ 0 <= fracil <= 1 ]
644
                        //
645
                        // Original description: http://mmccants.org/tles/mccdesc.html
646

647
                        double fracil = qMax(0.000001, static_cast<double>(calculateIlluminatedFraction()));
×
648

649
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
650
                        if (pSatWrapper && name.startsWith("IRIDIUM"))
651
                        {
652
                                Vec3d Sun3d = pSatWrapper->getSunECIPos();
653
                                QVector3D sun(Sun3d.data()[0],Sun3d.data()[1],Sun3d.data()[2]);
654
                                QVector3D sunN = sun; sunN.normalize();
655

656
                                // position, velocity are known
657
                                QVector3D Vx(velocity.data()[0],velocity.data()[1],velocity.data()[2]); Vx.normalize();
658

659
                                Vec3d vy = (position^velocity);
660
                                QVector3D Vy(vy.data()[0],vy.data()[1],vy.data()[2]); Vy.normalize();
661
                                QVector3D Vz = QVector3D::crossProduct(Vx,Vy); Vz.normalize();
662

663
                                // move this to constructor for optimizing
664
                                QMatrix4x4 m0;
665
                                m0.rotate(40, Vy);
666
                                QVector3D Vx0 = m0.mapVector(Vx);
667

668
                                QMatrix4x4 m[3];
669
                                //m[2] = m[1] = m[0];
670
                                m[0].rotate(0, Vz);
671
                                m[1].rotate(120, Vz);
672
                                m[2].rotate(-120, Vz);
673

674
                                StelLocation loc   = StelApp::getInstance().getCore()->getCurrentLocation();
675
                                const double  radLatitude    = loc.getLatitude() * KDEG2RAD;
676
                                const double  theta          = pSatWrapper->getEpoch().toThetaLMST(loc.getLongitude() * KDEG2RAD);
677
                                const double sinRadLatitude=sin(radLatitude);
678
                                const double cosRadLatitude=cos(radLatitude);
679
                                const double sinTheta=sin(theta);
680
                                const double cosTheta=cos(theta);
681

682
                                Vec3d observerECIPos;
683
                                Vec3d observerECIVel;
684
                                pSatWrapper->calcObserverECIPosition(observerECIPos, observerECIVel);
685

686
                                sunReflAngle = 180.;
687
                                QVector3D mirror;
688
                                for (int i = 0; i<3; i++)
689
                                {
690
                                        mirror = m[i].mapVector(Vx0);
691
                                        mirror.normalize();
692

693
                                        // reflection R = 2*(V dot N)*N - V
694
                                        QVector3D rsun =  2*QVector3D::dotProduct(sun,mirror)*mirror - sun;
695
                                        rsun = -rsun;
696
                                        Vec3d rSun(rsun.x(),rsun.y(),rsun.z());
697

698
                                        //Vec3d satECIPos  = getTEMEPos();
699
                                        Vec3d slantRange = rSun - observerECIPos;
700
                                        Vec3d topoRSunPos;
701
                                        //top_s
702
                                        topoRSunPos[0] = (sinRadLatitude * cosTheta * slantRange[0]
703
                                                        + sinRadLatitude * sinTheta * slantRange[1]
704
                                                        - cosRadLatitude * slantRange[2]);
705
                                        //top_e
706
                                        topoRSunPos[1] = ((-1.0) * sinTheta * slantRange[0]
707
                                                        + cosTheta * slantRange[1]);
708

709
                                        //top_z
710
                                        topoRSunPos[2] = (cosRadLatitude * cosTheta * slantRange[0]
711
                                                        + cosRadLatitude * sinTheta * slantRange[1]
712
                                                        + sinRadLatitude * slantRange[2]);
713
                                        sunReflAngle = qMin(elAzPosition.angle(topoRSunPos) * KRAD2DEG, sunReflAngle) ;
714
                                }
715

716
                                // very simple flare model
717
                                double iridiumFlare = 100;
718
                                if (sunReflAngle<0.5)
719
                                        iridiumFlare = -8.92 + sunReflAngle*6;
720
                                else        if (sunReflAngle<0.7)
721
                                        iridiumFlare = -5.92 + (sunReflAngle-0.5)*10;
722
                                else
723
                                        iridiumFlare = -3.92 + (sunReflAngle-0.7)*5;
724

725
                                 vmag = qMin(stdMag, iridiumFlare);
726
                        }
727
                        else // not Iridium
728
#endif
729
                                vmag = static_cast<float>(stdMag);
×
730

731
                        vmag += -15.75f + 2.5f * static_cast<float>(std::log10(range * range / fracil));
×
732
                }
733
                else if (RCS>0.) // OK, artificial satellite has RCS value and no standard magnitude
×
734
                {
735
                        // Let's try calculate approx. magnitude from RCS value (see DOI: 10.1117/12.2014623)
736
                        double albedo = 0.2;
×
737
                        if (0.436<=phaseAngle && phaseAngle<1.745)
×
738
                                albedo = (((((3.1765*phaseAngle - 22.0968)*phaseAngle + 62.182)*phaseAngle - 90.0993)*phaseAngle + 70.3031)*phaseAngle - 27.9227)*phaseAngle + 4.7373;
×
739
                        else if (1.745<=phaseAngle && phaseAngle<=2.618)
×
740
                                albedo = ((0.510905*phaseAngle - 2.72607)*phaseAngle + 4.96646)*phaseAngle - 3.02085;
×
741

742
                        double rm = range*1000.;
×
743
                        double fdiff = (std::sin(phaseAngle) + (M_PI - phaseAngle)*std::cos(phaseAngle))*(2.*albedo*RCS)/(3.* M_PI * M_PI * rm * rm);
×
744

745
                        vmag = static_cast<float>(-26.74 - 2.5*std::log10(fdiff));
×
746
                }
747
        }
748
        return vmag;
×
749
}
750

751
// Calculate illumination fraction of artificial satellite
752
float Satellite::calculateIlluminatedFraction() const
×
753
{
754
        return (1.f + cos(static_cast<float>(phaseAngle)))*0.5f;
×
755
}
756

757
QString Satellite::getOperationalStatus() const
×
758
{
759
        const QMap<int,QString>map={
760
                { StatusOperational,          qc_("operational", "operational status")},
×
761
                { StatusNonoperational,       qc_("non-operational", "operational status")},
×
762
                { StatusPartiallyOperational, qc_("partially operational", "operational status")},
×
763
                { StatusStandby,              qc_("standby", "operational status")},
×
764
                { StatusSpare,                qc_("spare", "operational status")},
×
765
                { StatusExtendedMission,      qc_("extended mission", "operational status")},
×
766
                { StatusDecayed,              qc_("decayed", "operational status")},
×
767
        };
×
768
        return map.value(status,              qc_("unknown", "operational status"));
×
769
}
×
770

771
double Satellite::getAngularRadius(const StelCore*) const
×
772
{
773
        double radius = 0.05 / 3600.; // assume 0.1 arcsecond default diameter
×
774
        if (RCS>0.)
×
775
        {
776
                double halfSize = isISS ?
×
777
                         109. * 0.5 :         // Special case: let's use max. size of ISS (109 meters: https://www.nasa.gov/feature/facts-and-figures)
778
                         std::sqrt(RCS/M_PI); // Let's assume spherical satellites/circular cross-section
×
779
                radius = std::atan(halfSize/(1000.*range))*M_180_PI; // Computing an angular size of artificial satellite ("halfSize" in metres, "range" in kilometres)
×
780
        }
781
        return radius;
×
782
}
783

784
void Satellite::setNewTleElements(const QString& tle1, const QString& tle2)
×
785
{
786
        if (pSatWrapper)
×
787
        {
788
                gSatWrapper *old = pSatWrapper;
×
789
                pSatWrapper = nullptr;
×
790
                delete old;
×
791
        }
792

793
        tleElements.first = tle1;
×
794
        tleElements.second = tle2;
×
795

796
        pSatWrapper = new gSatWrapper(id, tle1, tle2);
×
797
        orbitPoints.clear();
×
798
        visibilityPoints.clear();
×
799
        
800
        parseInternationalDesignator(tle1);
×
801
        calculateEpochFromLine1(tle1);
×
802
        calculateSatDataFromLine2(tle2);
×
803
}
×
804

805
void Satellite::recomputeSatData()
×
806
{
807
        calculateEpochFromLine1(tleElements.first);
×
808
        calculateSatDataFromLine2(tleElements.second);
×
809
}
×
810

811
void Satellite::update(double)
×
812
{
813
        if (pSatWrapper && orbitValid)
×
814
        {
815
                StelCore* core = StelApp::getInstance().getCore();
×
816
                epochTime = core->getJD(); // + timeShift; // We have "true" JD (UTC) from core, satellites don't need JDE!
×
817

818
                pSatWrapper->setEpoch(epochTime);
×
819
                position                 = pSatWrapper->getTEMEPos();
×
820
                velocity                 = pSatWrapper->getTEMEVel();
×
821
                latLongSubPointPosition  = pSatWrapper->getSubPoint();
×
822
                height                   = latLongSubPointPosition[2]; // km
×
823
                /*
824
                Vec2d pa                 = pSatWrapper->getPerigeeApogeeAltitudes();
825
                perigee                  = pa[0];
826
                apogee                   = pa[1];
827
                */
NEW
828
                if (height < 80) // way below Kármán line: Satellite is certainly lost, at least TLE not applicable.
×
829
                {
830
                        // The orbit is no longer valid.  Causes include very out of date
831
                        // TLE, system date and time out of a reasonable range, and orbital
832
                        // degradation and re-entry of a satellite.  In any of these cases
833
                        // we might end up with a problem - usually a crash of Stellarium
834
                        // because of a div/0 or something.
835
                        qWarning() << "Satellite has invalid orbit:" << name << id;
×
836
                        orbitValid = false;
×
837
                        displayed = false; // It shouldn't be displayed!
×
838
                        return;
×
839
                }
840

841
                elAzPosition = pSatWrapper->getAltAz();
×
842
                elAzPosition.normalize();
×
843
                XYZ = Satellite::getJ2000EquatorialPos(core);
×
844

845
                pSatWrapper->getSlantRange(range, rangeRate);
×
846
                visibility = pSatWrapper->getVisibilityPredict();
×
847
                phaseAngle = pSatWrapper->getPhaseAngle();
×
848

849
                // Compute orbit points to draw orbit line.
850
                if (orbitDisplayed) computeOrbitPoints();
×
851
        }
852
}
853

854
// Get radii and distances of shadow circles
855
Vec4d Satellite::getUmbraData()
×
856
{
857
        static PlanetP earth=GETSTELMODULE(SolarSystem)->getEarth();
×
858
        // Compute altitudes of umbra and penumbra circles. These should show where the satellite enters/exits umbra/penumbra.
859
        // The computation follows ideas from https://celestrak.org/columns/v03n01/
860
        // These sources mention ECI coordinates (Earth Centered Inertial). Presumably TEME (True Equator Mean Equinox) are equivalent, at least for our purposes.
861
        const double rhoE=position.norm(); // geocentric Satellite distance, km
×
862
        const double rS=earth->getHeliocentricEclipticPos().norm()*AU; // distance earth...sun
×
863
        const double thetaE=asin((earth->getEquatorialRadius()*AU)/(rhoE));
×
864
        // Accurate distance sat...sun from ECI sunpos
865
        Vec3d sunTEME=pSatWrapper->getSunECIPos() - pSatWrapper->getObserverECIPos(); // km
×
866
        const double thetaS=asin((sun->getEquatorialRadius()*AU)/(sunTEME.norm()));
×
867
        Q_ASSERT(thetaE>thetaS);
×
868
        const double theta=thetaE-thetaS; // angle so that satellite dives into umbra
×
869
        // angle at Sun:
870
        const double sigma=asin(sin(theta)*rhoE/rS);
×
871
        // angle in geocenter
872
        const double eta=M_PI-sigma-theta;
×
873
        // complement
874
        const double mu=M_PI-eta;
×
875
        // geocentric distance of shadow circle towards antisun
876
        umbraDistance=rhoE*cos(mu);
×
877
        // radius of shadow circle
878
        umbraRadius=rhoE*sin(mu);
×
879
        // Repeat for penumbra
880
        const double thetaP=thetaE+thetaS; // angle so that satellite touches penumbra
×
881
        // angle at Sun:
882
        const double sigmaP=asin(sin(thetaP)*rhoE/rS);
×
883
        // angle in geocenter
884
        const double etaP=M_PI-sigmaP-thetaP;
×
885
        // complement
886
        const double muP=M_PI-etaP;
×
887
        // geocentric distance of shadow circle towards antisun
888
        penumbraDistance=rhoE*cos(muP);
×
889
        // radius of shadow circle
890
        penumbraRadius=rhoE*sin(muP);
×
891
        //// DBG out
892
        //StelObjectMgr *om=GETSTELMODULE(StelObjectMgr);
893
        //om->setExtraInfoString(StelObject::DebugAid, QString("&rho;<sub>E</sub> %1, r<sub>S</sub> %2, &theta;<sub>E</sub> %3°, &theta;<sub>S</sub> %4° <br/>")
894
        //                       .arg(QString::number(rhoE, 'f', 3),
895
        //                            QString::number(rS, 'f', 3),
896
        //                            QString::number(thetaE*M_180_PI, 'f', 3),
897
        //                            QString::number(thetaS*M_180_PI, 'f', 3)
898
        //                            )
899
        //                       );
900
        //om->addToExtraInfoString(StelObject::DebugAid, QString("&theta; %1°, &sigma; %2°, &eta; %3°, &mu; %4° <br/>")
901
        //                       .arg(
902
        //                            QString::number(theta*M_180_PI, 'f', 3),
903
        //                            QString::number(sigma*M_180_PI, 'f', 3),
904
        //                            QString::number(eta*M_180_PI, 'f', 3),
905
        //                            QString::number(mu*M_180_PI, 'f', 3)
906
        //                            )
907
        //                       );
908
        //om->addToExtraInfoString(StelObject::DebugAid, QString("&theta;<sub>P</sub> %1°, &sigma; %2°, &eta; %3°, &mu; %4° <br/>")
909
        //                       .arg(
910
        //                            QString::number(thetaP*M_180_PI, 'f', 3),
911
        //                            QString::number(sigmaP*M_180_PI, 'f', 3),
912
        //                            QString::number(etaP*M_180_PI, 'f', 3),
913
        //                            QString::number(muP*M_180_PI, 'f', 3)
914
        //                            )
915
        //                       );
916
        return Vec4d(umbraDistance, umbraRadius, penumbraDistance, penumbraRadius);
×
917
}
918

919
//! Get radii and geocentric distances of shadow circles in km for a hypothetical object in dist_km above the (spherical) Earth.
920
//! Vec4d(umbraDistance, umbraRadius, penumbraDistance, penumbraRadius);
NEW
921
Vec4d Satellite::getUmbraData(double dist_km)
×
922
{
NEW
923
        static PlanetP earth=GETSTELMODULE(SolarSystem)->getEarth();
×
NEW
924
        static PlanetP sun = GETSTELMODULE(SolarSystem)->getSun();
×
925

926
        // Compute altitudes of umbra and penumbra circles. These should show where the satellite enters/exits umbra/penumbra.
927
        // The computation follows ideas from https://celestrak.org/columns/v03n01/
928
        // These sources mention ECI coordinates (Earth Centered Inertial). Presumably TEME (True Equator Mean Equinox) are equivalent, at least for our purposes.
NEW
929
        const double rhoE=earth->getEquatorialRadius()*AU+dist_km; // geocentric Satellite distance, km
×
NEW
930
        const double rS=earth->getHeliocentricEclipticPos().norm()*AU; // distance earth...sun
×
NEW
931
        const double thetaE=asin((earth->getEquatorialRadius()*AU)/(rhoE));
×
932
        // Accurate distance sat...sun from ECI sunpos
933
        //Vec3d sunTEME=pSatWrapper->getSunECIPos() - pSatWrapper->getObserverECIPos(); // km
NEW
934
        Vec3d sunEquinoxEqPos = sun->getEquinoxEquatorialPos(StelApp::getInstance().getCore());
×
NEW
935
        Vec3d sunTEME=sunEquinoxEqPos*AU;
×
NEW
936
        const double thetaS=asin((sun->getEquatorialRadius()*AU)/(sunTEME.norm()));
×
NEW
937
        Q_ASSERT(thetaE>thetaS);
×
NEW
938
        const double theta=thetaE-thetaS; // angle so that satellite dives into umbra
×
939
        // angle at Sun:
NEW
940
        const double sigma=asin(sin(theta)*rhoE/rS);
×
941
        // angle in geocenter
NEW
942
        const double eta=M_PI-sigma-theta;
×
943
        // complement
NEW
944
        const double mu=M_PI-eta;
×
945
        // geocentric distance of shadow circle towards antisun
NEW
946
        double umbraDistance=rhoE*cos(mu);
×
947
        // radius of shadow circle
NEW
948
        double umbraRadius=rhoE*sin(mu);
×
949
        // Repeat for penumbra
NEW
950
        const double thetaP=thetaE+thetaS; // angle so that satellite touches penumbra
×
951
        // angle at Sun:
NEW
952
        const double sigmaP=asin(sin(thetaP)*rhoE/rS);
×
953
        // angle in geocenter
NEW
954
        const double etaP=M_PI-sigmaP-thetaP;
×
955
        // complement
NEW
956
        const double muP=M_PI-etaP;
×
957
        // geocentric distance of shadow circle towards antisun
NEW
958
        double penumbraDistance=rhoE*cos(muP);
×
959
        // radius of shadow circle
NEW
960
        double penumbraRadius=rhoE*sin(muP);
×
961
        //// DBG OUT
962
        //StelObjectMgr *om=GETSTELMODULE(StelObjectMgr);
963
        //om->setExtraInfoString(StelObject::DebugAid, QString("&rho;<sub>E</sub> %1, r<sub>S</sub> %2, &theta;<sub>E</sub> %3°, &theta;<sub>S</sub> %4° <br/>")
964
        //                       .arg(QString::number(rhoE, 'f', 3),
965
        //                            QString::number(rS, 'f', 3),
966
        //                            QString::number(thetaE*M_180_PI, 'f', 3),
967
        //                            QString::number(thetaS*M_180_PI, 'f', 3)
968
        //                            )
969
        //                       );
970
        //om->addToExtraInfoString(StelObject::DebugAid, QString("&theta; %1°, &sigma; %2°, &eta; %3°, &mu; %4° <br/>")
971
        //                       .arg(
972
        //                            QString::number(theta*M_180_PI, 'f', 3),
973
        //                            QString::number(sigma*M_180_PI, 'f', 3),
974
        //                            QString::number(eta*M_180_PI, 'f', 3),
975
        //                            QString::number(mu*M_180_PI, 'f', 3)
976
        //                            )
977
        //                       );
978
        //om->addToExtraInfoString(StelObject::DebugAid, QString("&theta;<sub>P</sub> %1°, &sigma; %2°, &eta; %3°, &mu; %4° <br/>")
979
        //                       .arg(
980
        //                            QString::number(thetaP*M_180_PI, 'f', 3),
981
        //                            QString::number(sigmaP*M_180_PI, 'f', 3),
982
        //                            QString::number(etaP*M_180_PI, 'f', 3),
983
        //                            QString::number(muP*M_180_PI, 'f', 3)
984
        //                            )
985
        //                       );
NEW
986
        return Vec4d(umbraDistance, umbraRadius, penumbraDistance, penumbraRadius);
×
987
}
988

989
double Satellite::getDoppler(double freq) const
×
990
{
991
        return  -freq*((rangeRate*1000.0)/SPEED_OF_LIGHT);
×
992
}
993

994
void Satellite::recalculateOrbitLines(void)
×
995
{
996
        orbitPoints.clear();
×
997
        visibilityPoints.clear();
×
998
}
×
999

1000
SatFlags Satellite::getFlags() const
×
1001
{
1002
        // There's also a faster, but less readable way: treating them as uint.
1003
        SatFlags flags;
×
1004
        double orbitalPeriod = pSatWrapper->getOrbitalPeriod();
×
1005
        if (displayed)
×
1006
                flags |= SatDisplayed;
×
1007
        else
1008
                flags |= SatNotDisplayed;
×
1009
        if (orbitDisplayed)
×
1010
                flags |= SatOrbit;
×
1011
        if (userDefined)
×
1012
                flags |= SatUser;
×
1013
        if (newlyAdded)
×
1014
                flags |= SatNew;
×
1015
        if (!orbitValid)
×
1016
                flags |= SatError;
×
1017
        if (RCS>0. && RCS <= 0.1)
×
1018
                flags |= SatSmallSize;
×
1019
        if (RCS>0.1 && RCS <= 1.0)
×
1020
                flags |= SatMediumSize;
×
1021
        if (RCS>1.0)
×
1022
                flags |= SatLargeSize;
×
1023
        if (eccentricity < 0.25 && (inclination>=0. && inclination<=180.) && apogee<4400.)
×
1024
                flags |= SatLEO;
×
1025
        if (eccentricity < 0.25 && inclination<25. && (orbitalPeriod>=1100. && orbitalPeriod<=2000.))
×
1026
                flags |= SatGSO;
×
1027
        if (eccentricity < 0.25 && (inclination>=0. && inclination<=180.) && apogee>=4400. && orbitalPeriod<1100.)
×
1028
                flags |= SatMEO;
×
1029
        if (eccentricity >= 0.25 && (inclination>=0. && inclination<=180.) && perigee<=70000. && orbitalPeriod<=14000.)
×
1030
                flags |= SatHEO;
×
1031
        if (eccentricity < 0.25 && (inclination>=25. && inclination<=180.) && (orbitalPeriod>=1100. && orbitalPeriod<=2000.))
×
1032
                flags |= SatHGSO;
×
1033
        if (qAbs(inclination) >= 89.5 && qAbs(inclination) <= 90.5)
×
1034
                flags |= SatPolarOrbit;
×
1035
        if (qAbs(inclination) <= 0.5)
×
1036
                flags |= SatEquatOrbit;
×
1037
        if ((qAbs(inclination) >= 97.5 && qAbs(inclination) <= 98.5) && (orbitalPeriod>=96. && orbitalPeriod<=100.))
×
1038
                flags |= SatPSSO;
×
1039
        // definition: https://en.wikipedia.org/wiki/High_Earth_orbit
1040
        if (perigee>35786. && orbitalPeriod>1440.)
×
1041
                flags |= SatHEarthO;
×
1042
        if (qAbs(StelApp::getInstance().getCore()->getJD() - tleEpochJD) > tleEpochAge)
×
1043
                flags |= SatOutdatedTLE;
×
1044
        if (getCustomFiltersFlag())
×
1045
                flags |= SatCustomFilter;
×
1046
        if (!comms.isEmpty())
×
1047
                flags |= SatCommunication;
×
1048
        if (apogee<=100.0 || height<=100.0) // Karman line, atmosphere
×
1049
                flags |= SatReentry;
×
1050

1051
        return flags;
×
1052
}
1053

1054
void Satellite::setFlags(const SatFlags& flags)
×
1055
{
1056
        displayed = flags.testFlag(SatDisplayed);
×
1057
        orbitDisplayed = flags.testFlag(SatOrbit);
×
1058
        userDefined = flags.testFlag(SatUser);
×
1059
}
×
1060

1061
bool Satellite::getCustomFiltersFlag() const
×
1062
{
1063
        double orbitalPeriod = pSatWrapper->getOrbitalPeriod();
×
1064
        // Apogee
1065
        bool cfa = true;
×
1066
        if (flagCFApogee)
×
1067
                cfa = (apogee>=minCFApogee && apogee<=maxCFApogee);
×
1068
        // Perigee
1069
        bool cfp = true;
×
1070
        if (flagCFPerigee)
×
1071
                cfp = (perigee>=minCFPerigee && perigee<=maxCFPerigee);
×
1072
        // Eccentricity
1073
        bool cfe = true;
×
1074
        if (flagCFEccentricity)
×
1075
                cfe = (eccentricity>=minCFEccentricity && eccentricity<=maxCFEccentricity);
×
1076
        // Known standard magnitude
1077
        bool cfm = true;
×
1078
        if (flagCFKnownStdMagnitude)
×
1079
                cfm = (stdMag<99.0);
×
1080
        // Period
1081
        bool cft = true;
×
1082
        if (flagCFPeriod)
×
1083
                cft = (orbitalPeriod>=minCFPeriod && orbitalPeriod<=maxCFPeriod);
×
1084
        // Inclination
1085
        bool cfi = true;
×
1086
        if (flagCFInclination)
×
1087
                cfi = (inclination>=minCFInclination && inclination<=maxCFInclination);
×
1088
        // RCS
1089
        bool cfr = true;
×
1090
        if (flagCFRCS)
×
1091
                cfr = (RCS>=minCFRCS && RCS<=maxCFRCS);
×
1092

1093
        return (cfa && cfp && cfe && cfm && cft && cfi && cfr);
×
1094
}
1095

1096
void Satellite::parseInternationalDesignator(const QString& tle1)
×
1097
{
1098
        Q_ASSERT(!tle1.isEmpty());
×
1099
        
1100
        // The designator is encoded in chunk 3 on the first line.
1101
        QStringList tleData = tle1.split(" ");
×
1102
        QString rawString = tleData.at(2);
×
1103
        bool ok;
1104
        int year = rawString.left(2).toInt(&ok);
×
1105
        if (!rawString.isEmpty() && ok)
×
1106
        {
1107
                // Y2K bug :) I wonder what NORAD will do in 2057. :)
1108
                if (year < 57)
×
1109
                        year += 2000;
×
1110
                else
1111
                        year += 1900;
×
1112
                internationalDesignator = QString::number(year) + "-" + rawString.mid(2);
×
1113
        }
1114
        else
1115
                year = 1957;
×
1116
        
1117
        StelUtils::getJDFromDate(&jdLaunchYearJan1, year, 1, 1, 0, 0, 0);        
×
1118
}
×
1119

1120
bool Satellite::operator <(const Satellite& another) const
×
1121
{
1122
        // If interface strings are used, you'll need QString::localeAwareCompare()
1123
        int comp = name.compare(another.name);
×
1124
        if (comp < 0)
×
1125
                return true;
×
1126
        if (comp > 0)
×
1127
                return false;
×
1128
        
1129
        // If the names are the same, compare IDs, i.e. NORAD numbers.
1130
        return (id < another.id);
×
1131
}
1132

1133
void Satellite::draw(StelCore* core, StelPainter& painter)
×
1134
{
1135
        // Separated because first test should be very fast.
1136
        if (!displayed)
×
1137
                return;
×
1138

1139
        // 1) Do not show satellites before Space Era begins!
1140
        // 2) Do not show satellites when time rate is over limit (JD/sec)!
1141
        if (core->getJD()<jdLaunchYearJan1 || qAbs(core->getTimeRate())>=timeRateLimit)
×
1142
                return;
×
1143

1144
        if (flagVFAltitude)
×
1145
        {
1146
                // visual filter is activated!
1147
                // is satellite located in valid range of altitudes?
1148
                // yes, but... inverse the result and skip rendering!
1149
                if (!(minVFAltitude<=height && height<=maxVFAltitude))
×
1150
                        return;
×
1151
        }
1152

1153
        const float magSat = getVMagnitude(core);
×
1154
        if (flagVFMagnitude)
×
1155
        {
1156
                // visual filter is activated and he is applicable!
1157
                if (!(stdMag<99. || RCS>0.))
×
1158
                        return;
×
1159
                if (!(maxVFMagnitude<=magSat && magSat<=minVFMagnitude))
×
1160
                        return;
×
1161
        }
1162

1163
        Vec3d win;
×
1164
        if (painter.getProjector()->projectCheck(XYZ, win))
×
1165
        {
1166
                if (!iconicModeFlag)
×
1167
                {
1168
                        Vec3f color(1.f,1.f,1.f);
×
1169
                        // Special case: crossing of the satellite of the Moon or the Sun
1170
                        if (XYZ.angle(moon->getJ2000EquatorialPos(core))*M_180_PI <= moon->getSpheroidAngularRadius(core) || XYZ.angle(sun->getJ2000EquatorialPos(core))*M_180_PI <= sun->getSpheroidAngularRadius(core))
×
1171
                        {
1172
                                painter.setColor(transitSatelliteColor, 1.f);
×
1173
                                int screenSizeSat = static_cast<int>((getAngularRadius(core)*(2.*M_PI_180))*static_cast<double>(painter.getProjector()->getPixelPerRadAtCenter()));
×
1174
                                if (screenSizeSat>0)
×
1175
                                {
1176
                                        painter.setBlending(true, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
×
1177
                                        hintTexture->bind();
×
1178
                                        painter.drawSprite2dMode(XYZ, qMin(screenSizeSat, 15));
×
1179
                                }
1180

1181
                                if (showLabels)
×
1182
                                {
1183
                                        if (!core->isBrightDaylight()) // crossing of the Moon
×
1184
                                                painter.setColor(color, hintBrightness);
×
1185
                                        painter.drawText(XYZ, name, 0, 10, 10, false);
×
1186
                                }
1187
                        }
1188
                        else
1189
                        {
1190
                                StelSkyDrawer* sd = core->getSkyDrawer();
×
1191
                                RCMag rcMag;
1192

1193
                                // Draw the satellite
1194
                                if (magSat <= sd->getLimitMagnitude())
×
1195
                                {
1196
                                        Vec3f vf(XYZ.toVec3f());
×
1197
                                        Vec3f altAz(vf);
×
1198
                                        altAz.normalize();
×
1199
                                        core->j2000ToAltAzInPlaceNoRefraction(&altAz);
×
1200
                                        sd->preDrawPointSource(&painter);
×
1201
                                        sd->computeRCMag(magSat, &rcMag);
×
1202
                                        // allow height-dependent twinkle and suppress twinkling in higher altitudes. Keep 0.1 twinkle amount in zenith.
1203
                                        sd->drawPointSource(&painter, vf.toVec3d(), rcMag, color*hintBrightness, true, qMin(1.0f, 1.0f-0.9f*altAz[2]));
×
1204
                                        sd->postDrawPointSource(&painter);
×
1205
                                }
1206

1207
                                float txtMag = magSat;
×
1208
                                if (visibility != gSatWrapper::VISIBLE)
×
1209
                                {
1210
                                        txtMag = magSat - 10.f; // Oops... Artificial satellite is invisible, but let's make the label visible
×
1211
                                        painter.setColor(invisibleSatelliteColor, hintBrightness);
×
1212
                                }
1213
                                else
1214
                                        painter.setColor(color, hintBrightness);
×
1215

1216
                                // Draw the label of the satellite when it enabled
1217
                                if (txtMag <= sd->getLimitMagnitude() && showLabels)
×
1218
                                        painter.drawText(XYZ, name, 0, 10, 10, false);
×
1219
                        }
1220
                }
1221
                else if (!(hideInvisibleSatellitesFlag && visibility != gSatWrapper::VISIBLE))
×
1222
                {
1223
                        Vec3f drawColor = (coloredInvisibleSatellitesFlag && visibility != gSatWrapper::VISIBLE) ? invisibleSatelliteColor : hintColor; // Use hintColor for visible satellites only when coloredInvisibleSatellitesFlag is true
×
1224
                        painter.setColor(drawColor*hintBrightness, hintBrightness);
×
1225
                        if (XYZ.angle(moon->getJ2000EquatorialPos(core))*M_180_PI <= moon->getSpheroidAngularRadius(core) || XYZ.angle(sun->getJ2000EquatorialPos(core))*M_180_PI <= sun->getSpheroidAngularRadius(core))
×
1226
                                painter.setColor(transitSatelliteColor, 1.f);
×
1227

1228
                        if (showLabels)
×
1229
                                painter.drawText(XYZ, name, 0, 10, 10, false);
×
1230

1231
                        painter.setBlending(true, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
×
1232
                        hintTexture->bind();
×
1233
                        painter.drawSprite2dMode(XYZ, 11);
×
1234
                }
1235
        }
1236

1237
        if (orbitDisplayed && Satellite::orbitLinesFlag && orbitValid)
×
1238
                drawOrbit(core, painter);
×
1239
}
1240

1241
void Satellite::drawOrbit(StelCore *core, StelPainter& painter)
×
1242
{
1243
        int size = orbitPoints.size();
×
1244
        const float ppx = static_cast<float>(painter.getProjector()->getDevicePixelsPerPixel());
×
1245

1246
        if (size>0)
×
1247
        {
1248
                Vec3d position;
×
1249
                Vec3f drawColor;
×
1250
                Vec4d op;
×
1251
                QVector<Vec3d> vertexArray;
×
1252
                QVector<Vec4f> colorArray;
×
1253
                vertexArray.resize(size);
×
1254
                colorArray.resize(size);
×
1255

1256
                //Rest of points
1257
                for (int i=0; i<size; i++)
×
1258
                {
1259
                        op = orbitPoints[i];
×
1260
                        position = core->altAzToJ2000(Vec3d(op[0],op[1],op[2]), StelCore::RefractionOff);
×
1261
                        position.normalize();
×
1262
                        vertexArray[i] = position;
×
1263

1264
                        drawColor = (visibilityPoints[i] == gSatWrapper::VISIBLE) ? orbitColor : invisibleSatelliteColor;
×
1265
                        if (flagVFAltitude)
×
1266
                        {
1267
                                // visual filter is activated!
1268
                                // is satellite located in valid range of altitudes?
1269
                                if (minVFAltitude<=op[3] && op[3]<=maxVFAltitude)
×
1270
                                        colorArray[i] = Vec4f(drawColor, hintBrightness * calculateOrbitSegmentIntensity(i));
×
1271
                                else
1272
                                        colorArray[i] = Vec4f(0.f,0.f,0.f,0.f); // hide invisible part of orbit
×
1273
                        }
1274
                        else
1275
                        {
1276
                                if (hideInvisibleSatellitesFlag && visibilityPoints[i] != gSatWrapper::VISIBLE)
×
1277
                                        colorArray[i] = Vec4f(0.f,0.f,0.f,0.f); // hide invisible part of orbit
×
1278
                                else
1279
                                        colorArray[i] = Vec4f(drawColor, hintBrightness * calculateOrbitSegmentIntensity(i));
×
1280
                        }
1281
                }
1282

1283
                painter.enableClientStates(true, false, false);
×
1284
                if (orbitLineThickness>1 || ppx>1.f)
×
1285
                        painter.setLineWidth(orbitLineThickness*ppx);
×
1286

1287
                painter.drawPath(vertexArray, colorArray); // (does client state switching as needed internally)
×
1288

1289
                painter.enableClientStates(false);
×
1290
                if (orbitLineThickness>1 || ppx>1.f)
×
1291
                        painter.setLineWidth(1);
×
1292
        }
×
1293
}
×
1294

1295
float Satellite::calculateOrbitSegmentIntensity(int segNum)
×
1296
{
1297
        int endDist = (orbitLineSegments/2) - abs(segNum-1 - (orbitLineSegments/2) % orbitLineSegments);
×
1298
        if (endDist > orbitLineFadeSegments)
×
1299
                return 1.0;
×
1300
        else
1301
                return (endDist  + 1) / (orbitLineFadeSegments + 1.0);
×
1302
}
1303

1304
void Satellite::computeOrbitPoints()
×
1305
{
1306
        gTimeSpan computeInterval(0, 0, 0, orbitLineSegmentDuration);
×
1307
        gTimeSpan orbitSpan(0, 0, 0, orbitLineSegments*orbitLineSegmentDuration/2);
×
1308
        gTime epochTm;
×
1309
        gTime epoch(epochTime);
×
1310
        gTime lastEpochComp(lastEpochCompForOrbit);        
×
1311
        int diffSlots;
1312

1313
        if (orbitPoints.isEmpty())//Setup orbitPoints
×
1314
        {
1315
                epochTm  = epoch - orbitSpan;
×
1316

1317
                for (int i=0; i<=orbitLineSegments; i++)
×
1318
                {
1319
                        pSatWrapper->setEpoch(epochTm.getGmtTm());
×
1320
                        Vec3d sat = pSatWrapper->getAltAz();
×
1321
                        orbitPoints.append(Vec4d(sat[0],sat[1],sat[2],pSatWrapper->getSubPoint()[2]));
×
1322
                        visibilityPoints.append(pSatWrapper->getVisibilityPredict());
×
1323
                        epochTm    += computeInterval;
×
1324
                }
1325
                lastEpochCompForOrbit = epochTime;
×
1326
        }
1327
        else if (epochTime > lastEpochCompForOrbit)
×
1328
        {
1329
                // compute next orbit point when clock runs forward
1330
                gTimeSpan diffTime = epoch - lastEpochComp;
×
1331
                diffSlots          = static_cast<int>(diffTime.getDblSeconds()/orbitLineSegmentDuration);
×
1332

1333
                if (diffSlots > 0)
×
1334
                {
1335
                        if (diffSlots > orbitLineSegments)
×
1336
                        {
1337
                                diffSlots = orbitLineSegments + 1;
×
1338
                                epochTm  = epoch - orbitSpan;
×
1339
                        }
1340
                        else
1341
                        {
1342
                                epochTm   = lastEpochComp + orbitSpan + computeInterval;
×
1343
                        }
1344

1345
                        for (int i=0; i<diffSlots; i++)
×
1346
                        {
1347
                                //remove points at beginning of list and add points at end.
1348
                                orbitPoints.removeFirst();
×
1349
                                visibilityPoints.removeFirst();
×
1350
                                pSatWrapper->setEpoch(epochTm.getGmtTm());
×
1351
                                Vec3d sat = pSatWrapper->getAltAz();
×
1352
                                orbitPoints.append(Vec4d(sat[0],sat[1],sat[2],pSatWrapper->getSubPoint()[2]));
×
1353
                                visibilityPoints.append(pSatWrapper->getVisibilityPredict());
×
1354
                                epochTm    += computeInterval;
×
1355
                        }
1356

1357
                        lastEpochCompForOrbit = epochTime;
×
1358
                }
1359
        }
1360
        else if (epochTime < lastEpochCompForOrbit)
×
1361
        {
1362
                // compute next orbit point when clock runs backward
1363
                gTimeSpan diffTime = lastEpochComp - epoch;
×
1364
                diffSlots          = static_cast<int>(diffTime.getDblSeconds()/orbitLineSegmentDuration);
×
1365

1366
                if (diffSlots > 0)
×
1367
                {
1368
                        if (diffSlots > orbitLineSegments)
×
1369
                        {
1370
                                diffSlots = orbitLineSegments + 1;
×
1371
                                epochTm   = epoch + orbitSpan;
×
1372
                        }
1373
                        else
1374
                        {
1375
                                epochTm   = epoch - orbitSpan - computeInterval;
×
1376
                        }
1377
                        for (int i=0; i<diffSlots; i++)
×
1378
                        { //remove points at end of list and add points at beginning.
1379
                                orbitPoints.removeLast();
×
1380
                                visibilityPoints.removeLast();
×
1381
                                pSatWrapper->setEpoch(epochTm.getGmtTm());
×
1382
                                Vec3d sat = pSatWrapper->getAltAz();
×
1383
                                orbitPoints.push_front(Vec4d(sat[0],sat[1],sat[2],pSatWrapper->getSubPoint()[2]));
×
1384
                                visibilityPoints.push_front(pSatWrapper->getVisibilityPredict());
×
1385
                                epochTm -= computeInterval;
×
1386
                        }
1387
                        lastEpochCompForOrbit = epochTime;
×
1388
                }
1389
        }
1390
}
×
1391

1392
bool operator <(const SatelliteP& left, const SatelliteP& right)
×
1393
{
1394
        if (left.isNull())
×
1395
        {
1396
                if (right.isNull())
×
1397
                        return false;
×
1398
                else
1399
                        return true;
×
1400
        }
1401
        if (right.isNull())
×
1402
                return false; // No sense to check the left one now
×
1403
        
1404
        return ((*left) < (*right));
×
1405
}
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