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

Stellarium / stellarium / 9545309360

16 Jun 2024 08:38PM UTC coverage: 12.262% (-0.01%) from 12.274%
9545309360

push

github

web-flow
Translate stellarium-remotecontrol.pot in gl

100% translated source file: 'stellarium-remotecontrol.pot'
on 'gl'.

14429 of 117670 relevant lines covered (12.26%)

18515.08 hits per line

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

0.0
/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 "VecMath.hpp"
27
#include "StelUtils.hpp"
28
#include "StelTranslator.hpp"
29
#include "StelModuleMgr.hpp"
30
#include "StelLocaleMgr.hpp"
31

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

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

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

44
#include <cmath>
45

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

48
const QString Satellite::SATELLITE_TYPE = QStringLiteral("Satellite");
49

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

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

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

100
Satellite::Satellite(const QString& identifier, const QVariantMap& map)
×
101
        : initialized(false)
×
102
        , displayed(false)
×
103
        , orbitDisplayed(false)
×
104
        , userDefined(false)
×
105
        , newlyAdded(false)
×
106
        , orbitValid(false)
×
107
        , jdLaunchYearJan1(0)
×
108
        , stdMag(99.)
×
109
        , RCS(-1.)
×
110
        , status(StatusUnknown)
×
111
        , height(0.)
×
112
        , range(0.)
×
113
        , rangeRate(0.)
×
114
        , hintColor(0.f,0.f,0.f)
×
115
        , lastUpdated()        
×
116
        , isISS(false)
×
117
        , isStarlink(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
                        c.frequency = commMap.value("frequency").toDouble(); // required!
×
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.compare("25544"));
×
214
        isStarlink = (name.startsWith("STARLINK"));
×
215
        moon = GETSTELMODULE(SolarSystem)->getMoon();
×
216
        sun = GETSTELMODULE(SolarSystem)->getSun();
×
217

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

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

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

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

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

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

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

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

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

297
        return map;
×
298
}
×
299

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

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

309
        return limit;
×
310
}
311

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

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

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

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

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

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

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

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

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

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

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

452
                if (!comms.isEmpty() && (status!=Satellite::StatusNonoperational))
×
453
                {
454
                        oss << q_("Radio communication") << ":<br/>";
×
455
                        for (const auto& c : comms)
×
456
                        {
457
                                oss << getCommLinkInfo(c);
×
458
                        }
459
                }
460
                // Umbra circle info. May not generally be interesting.
461
                //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/>";
462
                //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/>";
463
        }
×
464

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

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

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

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

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

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

504
        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")));
×
505
        return commLinkData;
×
506
}
×
507

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

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

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

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

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

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

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

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

590
        return map;
×
591
}
×
592

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

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

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

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

614
        if (visibility==gSatWrapper::VISIBLE && pSatWrapper)
×
615
        {
616
#if(SATELLITES_PLUGIN_IRIDIUM == 1)
617
                sunReflAngle = -1.;
618
#endif
619

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

649
                        double fracil = qMax(0.000001, static_cast<double>(calculateIlluminatedFraction()));
×
650

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

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

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

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

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

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

684
                                Vec3d observerECIPos;
685
                                Vec3d observerECIVel;
686
                                pSatWrapper->calcObserverECIPosition(observerECIPos, observerECIVel);
687

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

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

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

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

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

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

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

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

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

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

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

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

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

795
        tleElements.first = tle1;
×
796
        tleElements.second = tle2;
×
797

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

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

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

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

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

847
                pSatWrapper->getSlantRange(range, rangeRate);
×
848
                visibility = pSatWrapper->getVisibilityPredict();
×
849

850
                // Compute phase angle
851
                double raSun, decSun, raSat, decSat;
852
                StelUtils::rectToSphe(&raSun, &decSun, sun->getJ2000EquatorialPos(core));
×
853
                StelUtils::rectToSphe(&raSat, &decSat, XYZ);
×
854
                phaseAngle = std::acos(-1.0*(std::sin(decSun)*std::sin(decSat) + std::cos(decSun)*std::cos(decSat)*std::cos(raSun - raSat)));
×
855

856
                // Compute orbit points to draw orbit line.
857
                if (orbitDisplayed) computeOrbitPoints();
×
858
        }
859
}
860

861
// Get radii and distances of shadow circles
862
Vec4d Satellite::getUmbraData()
×
863
{
864
        const double rhoE=position.norm(); // geocentric Satellite distance, km
×
865
        return Satellite::getUmbraData(rhoE);
×
866
}
867

868
//! Get radii and geocentric antisolar distances of shadow circles in km for a hypothetical object in geocentric distance rhoE [km].
869
//! Vec4d(umbraDistance, umbraRadius, penumbraDistance, penumbraRadius);
870
Vec4d Satellite::getUmbraData(const double rhoE)
×
871
{
872
        static PlanetP earth=GETSTELMODULE(SolarSystem)->getEarth();
×
873
        static PlanetP sun = GETSTELMODULE(SolarSystem)->getSun();
×
874

875
        // Compute locations of umbra and penumbra circles. These should show where the satellite enters/exits umbra/penumbra.
876
        // The computation follows ideas from https://celestrak.org/columns/v03n01/
877
        // These sources mention ECI coordinates (Earth Centered Inertial). Presumably TEME (True Equator Mean Equinox) are equivalent, at least for our purposes.
878
        const double rS=earth->getHeliocentricEclipticPos().norm()*AU; // distance earth...sun
×
879
        const double thetaE=asin((earth->getEquatorialRadius()*AU)/(rhoE));
×
880
        Vec3d sunEquinoxEqPos = sun->getEquinoxEquatorialPos(StelApp::getInstance().getCore());
×
881
        Vec3d sunTEME=sunEquinoxEqPos*AU;
×
882
        const double thetaS=asin((sun->getEquatorialRadius()*AU)/(sunTEME.norm()));
×
883
        Q_ASSERT(thetaE>thetaS);
×
884
        const double theta=thetaE-thetaS; // angle so that satellite dives into umbra
×
885
        // angle at Sun:
886
        const double sigma=asin(sin(theta)*rhoE/rS);
×
887
        // angle in geocenter
888
        const double eta=M_PI-sigma-theta;
×
889
        // complement
890
        const double mu=M_PI-eta;
×
891
        // geocentric distance of shadow circle towards antisun
892
        double umbraDistance=rhoE*cos(mu);
×
893
        // radius of shadow circle
894
        double umbraRadius=rhoE*sin(mu);
×
895
        // Repeat for penumbra
896
        const double thetaP=thetaE+thetaS; // angle so that satellite touches penumbra
×
897
        // angle at Sun:
898
        const double sigmaP=asin(sin(thetaP)*rhoE/rS);
×
899
        // angle in geocenter
900
        const double etaP=M_PI-sigmaP-thetaP;
×
901
        // complement
902
        const double muP=M_PI-etaP;
×
903
        // geocentric distance of shadow circle towards antisun
904
        double penumbraDistance=rhoE*cos(muP);
×
905
        // radius of shadow circle
906
        double penumbraRadius=rhoE*sin(muP);
×
907
        //// DBG OUT
908
        //StelObjectMgr *om=GETSTELMODULE(StelObjectMgr);
909
        //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/>")
910
        //                       .arg(QString::number(rhoE, 'f', 3),
911
        //                            QString::number(rS, 'f', 3),
912
        //                            QString::number(thetaE*M_180_PI, 'f', 3),
913
        //                            QString::number(thetaS*M_180_PI, 'f', 3)
914
        //                            )
915
        //                       );
916
        //om->addToExtraInfoString(StelObject::DebugAid, QString("&theta; %1°, &sigma; %2°, &eta; %3°, &mu; %4° <br/>")
917
        //                       .arg(
918
        //                            QString::number(theta*M_180_PI, 'f', 3),
919
        //                            QString::number(sigma*M_180_PI, 'f', 3),
920
        //                            QString::number(eta*M_180_PI, 'f', 3),
921
        //                            QString::number(mu*M_180_PI, 'f', 3)
922
        //                            )
923
        //                       );
924
        //om->addToExtraInfoString(StelObject::DebugAid, QString("&theta;<sub>P</sub> %1°, &sigma; %2°, &eta; %3°, &mu; %4° <br/>")
925
        //                       .arg(
926
        //                            QString::number(thetaP*M_180_PI, 'f', 3),
927
        //                            QString::number(sigmaP*M_180_PI, 'f', 3),
928
        //                            QString::number(etaP*M_180_PI, 'f', 3),
929
        //                            QString::number(muP*M_180_PI, 'f', 3)
930
        //                            )
931
        //                       );
932
        return Vec4d(umbraDistance, umbraRadius, penumbraDistance, penumbraRadius);
×
933
}
934

935
double Satellite::getDoppler(double freq) const
×
936
{
937
        return  -freq*((rangeRate*1000.0)/SPEED_OF_LIGHT);
×
938
}
939

940
void Satellite::recalculateOrbitLines(void)
×
941
{
942
        orbitPoints.clear();
×
943
        visibilityPoints.clear();
×
944
}
×
945

946
SatFlags Satellite::getFlags() const
×
947
{
948
        // There's also a faster, but less readable way: treating them as uint.
949
        SatFlags flags;
×
950
        double orbitalPeriod = pSatWrapper->getOrbitalPeriod();
×
951
        if (displayed)
×
952
                flags |= SatDisplayed;
×
953
        else
954
                flags |= SatNotDisplayed;
×
955
        if (orbitDisplayed)
×
956
                flags |= SatOrbit;
×
957
        if (userDefined)
×
958
                flags |= SatUser;
×
959
        if (newlyAdded)
×
960
                flags |= SatNew;
×
961
        if (!orbitValid)
×
962
                flags |= SatError;
×
963
        if (RCS>0. && RCS <= 0.1)
×
964
                flags |= SatSmallSize;
×
965
        if (RCS>0.1 && RCS <= 1.0)
×
966
                flags |= SatMediumSize;
×
967
        if (RCS>1.0)
×
968
                flags |= SatLargeSize;
×
969
        if (eccentricity < 0.25 && (inclination>=0. && inclination<=180.) && apogee<4400.)
×
970
                flags |= SatLEO;
×
971
        if (eccentricity < 0.25 && inclination<25. && (orbitalPeriod>=1100. && orbitalPeriod<=2000.))
×
972
                flags |= SatGSO;
×
973
        if (eccentricity < 0.25 && (inclination>=0. && inclination<=180.) && apogee>=4400. && orbitalPeriod<1100.)
×
974
                flags |= SatMEO;
×
975
        if (eccentricity >= 0.25 && (inclination>=0. && inclination<=180.) && perigee<=70000. && orbitalPeriod<=14000.)
×
976
                flags |= SatHEO;
×
977
        if (eccentricity < 0.25 && (inclination>=25. && inclination<=180.) && (orbitalPeriod>=1100. && orbitalPeriod<=2000.))
×
978
                flags |= SatHGSO;
×
979
        if (qAbs(inclination) >= 89.5 && qAbs(inclination) <= 90.5)
×
980
                flags |= SatPolarOrbit;
×
981
        if (qAbs(inclination) <= 0.5)
×
982
                flags |= SatEquatOrbit;
×
983
        if ((qAbs(inclination) >= 97.5 && qAbs(inclination) <= 98.5) && (orbitalPeriod>=96. && orbitalPeriod<=100.))
×
984
                flags |= SatPSSO;
×
985
        // definition: https://en.wikipedia.org/wiki/High_Earth_orbit
986
        if (perigee>35786. && orbitalPeriod>1440.)
×
987
                flags |= SatHEarthO;
×
988
        if (qAbs(StelApp::getInstance().getCore()->getJD() - tleEpochJD) > tleEpochAge)
×
989
                flags |= SatOutdatedTLE;
×
990
        if (getCustomFiltersFlag())
×
991
                flags |= SatCustomFilter;
×
992
        if (!comms.isEmpty())
×
993
                flags |= SatCommunication;
×
994
        if (apogee<=100.0 || height<=100.0) // Karman line, atmosphere: thermosphere
×
995
                flags |= SatReentry;
×
996
        if (status == StatusOperational)
×
997
        {
998
                flags |= SatOperationalOS;
×
999
                flags |= SatActiveOS;
×
1000
        }
1001
        if (status == StatusNonoperational)
×
1002
                flags |= SatNonoperationalOS;
×
1003
        if (status == StatusPartiallyOperational)
×
1004
        {
1005
                flags |= SatPartiallyOperationalOS;
×
1006
                flags |= SatActiveOS;
×
1007
        }
1008
        if (status == StatusStandby)
×
1009
        {
1010
                flags |= SatStandbyOS;
×
1011
                flags |= SatActiveOS;
×
1012
        }
1013
        if (status == StatusSpare)
×
1014
        {
1015
                flags |= SatSpareOS;
×
1016
                flags |= SatActiveOS;
×
1017
        }
1018
        if (status == StatusExtendedMission)
×
1019
        {
1020
                flags |= SatExtendedMissionOS;
×
1021
                flags |= SatActiveOS;
×
1022
        }
1023
        if (status == StatusDecayed)
×
1024
                flags |= SatDecayedOS;
×
1025

1026
        return flags;
×
1027
}
1028

1029
void Satellite::setFlags(const SatFlags& flags)
×
1030
{
1031
        displayed = flags.testFlag(SatDisplayed);
×
1032
        orbitDisplayed = flags.testFlag(SatOrbit);
×
1033
        userDefined = flags.testFlag(SatUser);
×
1034
}
×
1035

1036
bool Satellite::getCustomFiltersFlag() const
×
1037
{
1038
        double orbitalPeriod = pSatWrapper->getOrbitalPeriod();
×
1039
        // Apogee
1040
        bool cfa = true;
×
1041
        if (flagCFApogee)
×
1042
                cfa = (apogee>=minCFApogee && apogee<=maxCFApogee);
×
1043
        // Perigee
1044
        bool cfp = true;
×
1045
        if (flagCFPerigee)
×
1046
                cfp = (perigee>=minCFPerigee && perigee<=maxCFPerigee);
×
1047
        // Eccentricity
1048
        bool cfe = true;
×
1049
        if (flagCFEccentricity)
×
1050
                cfe = (eccentricity>=minCFEccentricity && eccentricity<=maxCFEccentricity);
×
1051
        // Known standard magnitude
1052
        bool cfm = true;
×
1053
        if (flagCFKnownStdMagnitude)
×
1054
                cfm = (stdMag<99.0);
×
1055
        // Period
1056
        bool cft = true;
×
1057
        if (flagCFPeriod)
×
1058
                cft = (orbitalPeriod>=minCFPeriod && orbitalPeriod<=maxCFPeriod);
×
1059
        // Inclination
1060
        bool cfi = true;
×
1061
        if (flagCFInclination)
×
1062
                cfi = (inclination>=minCFInclination && inclination<=maxCFInclination);
×
1063
        // RCS
1064
        bool cfr = true;
×
1065
        if (flagCFRCS)
×
1066
                cfr = (RCS>=minCFRCS && RCS<=maxCFRCS);
×
1067

1068
        return (cfa && cfp && cfe && cfm && cft && cfi && cfr);
×
1069
}
1070

1071
void Satellite::parseInternationalDesignator(const QString& tle1)
×
1072
{
1073
        Q_ASSERT(!tle1.isEmpty());
×
1074
        
1075
        // The designator is encoded in chunk 3 on the first line.
1076
        QStringList tleData = tle1.split(" ");
×
1077
        QString rawString = tleData.at(2);
×
1078
        bool ok;
1079
        int year = rawString.left(2).toInt(&ok);
×
1080
        if (!rawString.isEmpty() && ok)
×
1081
        {
1082
                // Y2K bug :) I wonder what NORAD will do in 2057. :)
1083
                if (year < 57)
×
1084
                        year += 2000;
×
1085
                else
1086
                        year += 1900;
×
1087
                internationalDesignator = QString::number(year) + "-" + rawString.mid(2);
×
1088
        }
1089
        else
1090
                year = 1957;
×
1091
        
1092
        StelUtils::getJDFromDate(&jdLaunchYearJan1, year, 1, 1, 0, 0, 0);        
×
1093
}
×
1094

1095
bool Satellite::operator <(const Satellite& another) const
×
1096
{
1097
        // If interface strings are used, you'll need QString::localeAwareCompare()
1098
        int comp = name.compare(another.name);
×
1099
        if (comp < 0)
×
1100
                return true;
×
1101
        if (comp > 0)
×
1102
                return false;
×
1103
        
1104
        // If the names are the same, compare IDs, i.e. NORAD numbers.
1105
        return (id < another.id);
×
1106
}
1107

1108
void Satellite::draw(StelCore* core, StelPainter& painter)
×
1109
{
1110
        // Separated because first test should be very fast.
1111
        if (!displayed)
×
1112
                return;
×
1113

1114
        // 1) Do not show satellites before Space Era begins!
1115
        // 2) Do not show satellites when time rate is over limit (JD/sec)!
1116
        if (core->getJD()<jdLaunchYearJan1 || qAbs(core->getTimeRate())>=timeRateLimit)
×
1117
                return;
×
1118

1119
        if (flagVFAltitude)
×
1120
        {
1121
                // visual filter is activated!
1122
                // is satellite located in valid range of altitudes?
1123
                // yes, but... inverse the result and skip rendering!
1124
                if (!(minVFAltitude<=height && height<=maxVFAltitude))
×
1125
                        return;
×
1126
        }
1127

1128
        const float magSat = getVMagnitude(core);
×
1129
        if (flagVFMagnitude)
×
1130
        {
1131
                // visual filter is activated and he is applicable!
1132
                if (!(stdMag<99. || RCS>0.))
×
1133
                        return;
×
1134
                if (!(maxVFMagnitude<=magSat && magSat<=minVFMagnitude))
×
1135
                        return;
×
1136
        }
1137

1138
        Vec3d win;
×
1139
        if (painter.getProjector()->projectCheck(XYZ, win))
×
1140
        {
1141
                if (!iconicModeFlag)
×
1142
                {
1143
                        Vec3f color(1.f,1.f,1.f);
×
1144
                        // Special case: crossing of the satellite of the Moon or the Sun
1145
                        if (XYZ.angle(moon->getJ2000EquatorialPos(core))*M_180_PI <= moon->getSpheroidAngularRadius(core) || XYZ.angle(sun->getJ2000EquatorialPos(core))*M_180_PI <= sun->getSpheroidAngularRadius(core))
×
1146
                        {
1147
                                painter.setColor(transitSatelliteColor, 1.f);
×
1148
                                int screenSizeSat = static_cast<int>((getAngularRadius(core)*(2.*M_PI_180))*static_cast<double>(painter.getProjector()->getPixelPerRadAtCenter()));
×
1149
                                if (screenSizeSat>0)
×
1150
                                        painter.drawSprite2dMode(XYZ, qMin(screenSizeSat, 15));
×
1151

1152
                                if (showLabels)
×
1153
                                {
1154
                                        if (!core->isBrightDaylight()) // crossing of the Moon
×
1155
                                                painter.setColor(color, hintBrightness);
×
1156
                                        painter.drawText(XYZ, name, 0, 10, 10, false);
×
1157
                                }
1158
                        }
1159
                        else
1160
                        {
1161
                                StelSkyDrawer* sd = core->getSkyDrawer();
×
1162
                                RCMag rcMag;
1163

1164
                                // Draw the satellite
1165
                                if (magSat <= sd->getLimitMagnitude())
×
1166
                                {
1167
                                        Vec3f vf(XYZ.toVec3f());
×
1168
                                        Vec3f altAz(vf);
×
1169
                                        altAz.normalize();
×
1170
                                        core->j2000ToAltAzInPlaceNoRefraction(&altAz);
×
1171
                                        sd->preDrawPointSource(&painter);
×
1172
                                        sd->computeRCMag(magSat, &rcMag);
×
1173
                                        // allow height-dependent twinkle and suppress twinkling in higher altitudes. Keep 0.1 twinkle amount in zenith.
1174
                                        sd->drawPointSource(&painter, vf.toVec3d(), rcMag, color*hintBrightness, true, qMin(1.0f, 1.0f-0.9f*altAz[2]));
×
1175
                                        sd->postDrawPointSource(&painter);
×
1176
                                }
1177

1178
                                float txtMag = magSat;
×
1179
                                if (visibility != gSatWrapper::VISIBLE)
×
1180
                                {
1181
                                        txtMag = magSat - 10.f; // Oops... Artificial satellite is invisible, but let's make the label visible
×
1182
                                        painter.setColor(invisibleSatelliteColor, hintBrightness);
×
1183
                                }
1184
                                else
1185
                                        painter.setColor(color, hintBrightness);
×
1186

1187
                                // Draw the label of the satellite when it enabled
1188
                                if (txtMag <= sd->getLimitMagnitude() && showLabels)
×
1189
                                        painter.drawText(XYZ, name, 0, 10, 10, false);
×
1190
                        }
1191
                }
1192
                else if (!(hideInvisibleSatellitesFlag && visibility != gSatWrapper::VISIBLE))
×
1193
                {
1194
                        Vec3f drawColor = (coloredInvisibleSatellitesFlag && visibility != gSatWrapper::VISIBLE) ? invisibleSatelliteColor : hintColor; // Use hintColor for visible satellites only when coloredInvisibleSatellitesFlag is true
×
1195
                        painter.setColor(drawColor*hintBrightness, hintBrightness);
×
1196
                        if (XYZ.angle(moon->getJ2000EquatorialPos(core))*M_180_PI <= moon->getSpheroidAngularRadius(core) || XYZ.angle(sun->getJ2000EquatorialPos(core))*M_180_PI <= sun->getSpheroidAngularRadius(core))
×
1197
                                painter.setColor(transitSatelliteColor, 1.f);
×
1198

1199
                        if (showLabels)
×
1200
                                painter.drawText(XYZ, name, 0, 10, 10, false);
×
1201

1202
                        painter.drawSprite2dMode(XYZ, 11);
×
1203
                }
1204
        }
1205

1206
        if (orbitDisplayed && Satellite::orbitLinesFlag && orbitValid)
×
1207
                drawOrbit(core, painter);
×
1208
}
1209

1210
void Satellite::drawOrbit(StelCore *core, StelPainter& painter)
×
1211
{
1212
        int size = orbitPoints.size();
×
1213
        const float ppx = static_cast<float>(painter.getProjector()->getDevicePixelsPerPixel());
×
1214

1215
        if (size>0)
×
1216
        {
1217
                Vec3d position;
×
1218
                Vec3f drawColor;
×
1219
                Vec4d op;
×
1220
                QVector<Vec3d> vertexArray;
×
1221
                QVector<Vec4f> colorArray;
×
1222
                vertexArray.resize(size);
×
1223
                colorArray.resize(size);
×
1224

1225
                //Rest of points
1226
                for (int i=0; i<size; i++)
×
1227
                {
1228
                        op = orbitPoints[i];
×
1229
                        position = core->altAzToJ2000(Vec3d(op[0],op[1],op[2]), StelCore::RefractionOff);
×
1230
                        position.normalize();
×
1231
                        vertexArray[i] = position;
×
1232

1233
                        drawColor = (visibilityPoints[i] == gSatWrapper::VISIBLE) ? orbitColor : invisibleSatelliteColor;
×
1234
                        if (flagVFAltitude)
×
1235
                        {
1236
                                // visual filter is activated!
1237
                                // is satellite located in valid range of altitudes?
1238
                                if (minVFAltitude<=op[3] && op[3]<=maxVFAltitude)
×
1239
                                        colorArray[i] = Vec4f(drawColor, hintBrightness * calculateOrbitSegmentIntensity(i));
×
1240
                                else
1241
                                        colorArray[i] = Vec4f(0.f,0.f,0.f,0.f); // hide invisible part of orbit
×
1242
                        }
1243
                        else
1244
                        {
1245
                                if (hideInvisibleSatellitesFlag && visibilityPoints[i] != gSatWrapper::VISIBLE)
×
1246
                                        colorArray[i] = Vec4f(0.f,0.f,0.f,0.f); // hide invisible part of orbit
×
1247
                                else
1248
                                        colorArray[i] = Vec4f(drawColor, hintBrightness * calculateOrbitSegmentIntensity(i));
×
1249
                        }
1250
                }
1251

1252
                painter.enableClientStates(true, false, false);
×
1253
                if (orbitLineThickness>1 || ppx>1.f)
×
1254
                        painter.setLineWidth(orbitLineThickness*ppx);
×
1255

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

1258
                painter.enableClientStates(false);
×
1259
                if (orbitLineThickness>1 || ppx>1.f)
×
1260
                        painter.setLineWidth(1);
×
1261
        }
×
1262
}
×
1263

1264
float Satellite::calculateOrbitSegmentIntensity(int segNum)
×
1265
{
1266
        int endDist = (orbitLineSegments/2) - abs(segNum-1 - (orbitLineSegments/2) % orbitLineSegments);
×
1267
        if (endDist > orbitLineFadeSegments)
×
1268
                return 1.0;
×
1269
        else
1270
                return (endDist  + 1) / (orbitLineFadeSegments + 1.0);
×
1271
}
1272

1273
void Satellite::computeOrbitPoints()
×
1274
{
1275
        gTimeSpan computeInterval(0, 0, 0, orbitLineSegmentDuration);
×
1276
        gTimeSpan orbitSpan(0, 0, 0, orbitLineSegments*orbitLineSegmentDuration/2);
×
1277
        gTime epochTm;
×
1278
        gTime epoch(epochTime);
×
1279
        gTime lastEpochComp(lastEpochCompForOrbit);        
×
1280
        int diffSlots;
1281

1282
        if (orbitPoints.isEmpty())//Setup orbitPoints
×
1283
        {
1284
                epochTm  = epoch - orbitSpan;
×
1285

1286
                for (int i=0; i<=orbitLineSegments; i++)
×
1287
                {
1288
                        pSatWrapper->setEpoch(epochTm.getGmtTm());
×
1289
                        Vec3d sat = pSatWrapper->getAltAz();
×
1290
                        orbitPoints.append(Vec4d(sat[0],sat[1],sat[2],pSatWrapper->getSubPoint()[2]));
×
1291
                        visibilityPoints.append(pSatWrapper->getVisibilityPredict());
×
1292
                        epochTm    += computeInterval;
×
1293
                }
1294
                lastEpochCompForOrbit = epochTime;
×
1295
        }
1296
        else if (epochTime > lastEpochCompForOrbit)
×
1297
        {
1298
                // compute next orbit point when clock runs forward
1299
                gTimeSpan diffTime = epoch - lastEpochComp;
×
1300
                diffSlots          = static_cast<int>(diffTime.getDblSeconds()/orbitLineSegmentDuration);
×
1301

1302
                if (diffSlots > 0)
×
1303
                {
1304
                        if (diffSlots > orbitLineSegments)
×
1305
                        {
1306
                                diffSlots = orbitLineSegments + 1;
×
1307
                                epochTm  = epoch - orbitSpan;
×
1308
                        }
1309
                        else
1310
                        {
1311
                                epochTm   = lastEpochComp + orbitSpan + computeInterval;
×
1312
                        }
1313

1314
                        for (int i=0; i<diffSlots; i++)
×
1315
                        {
1316
                                //remove points at beginning of list and add points at end.
1317
                                orbitPoints.removeFirst();
×
1318
                                visibilityPoints.removeFirst();
×
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

1326
                        lastEpochCompForOrbit = epochTime;
×
1327
                }
1328
        }
1329
        else if (epochTime < lastEpochCompForOrbit)
×
1330
        {
1331
                // compute next orbit point when clock runs backward
1332
                gTimeSpan diffTime = lastEpochComp - epoch;
×
1333
                diffSlots          = static_cast<int>(diffTime.getDblSeconds()/orbitLineSegmentDuration);
×
1334

1335
                if (diffSlots > 0)
×
1336
                {
1337
                        if (diffSlots > orbitLineSegments)
×
1338
                        {
1339
                                diffSlots = orbitLineSegments + 1;
×
1340
                                epochTm   = epoch + orbitSpan;
×
1341
                        }
1342
                        else
1343
                        {
1344
                                epochTm   = epoch - orbitSpan - computeInterval;
×
1345
                        }
1346
                        for (int i=0; i<diffSlots; i++)
×
1347
                        { //remove points at end of list and add points at beginning.
1348
                                orbitPoints.removeLast();
×
1349
                                visibilityPoints.removeLast();
×
1350
                                pSatWrapper->setEpoch(epochTm.getGmtTm());
×
1351
                                Vec3d sat = pSatWrapper->getAltAz();
×
1352
                                orbitPoints.push_front(Vec4d(sat[0],sat[1],sat[2],pSatWrapper->getSubPoint()[2]));
×
1353
                                visibilityPoints.push_front(pSatWrapper->getVisibilityPredict());
×
1354
                                epochTm -= computeInterval;
×
1355
                        }
1356
                        lastEpochCompForOrbit = epochTime;
×
1357
                }
1358
        }
1359
}
×
1360

1361
bool operator <(const SatelliteP& left, const SatelliteP& right)
×
1362
{
1363
        if (left.isNull())
×
1364
        {
1365
                if (right.isNull())
×
1366
                        return false;
×
1367
                else
1368
                        return true;
×
1369
        }
1370
        if (right.isNull())
×
1371
                return false; // No sense to check the left one now
×
1372
        
1373
        return ((*left) < (*right));
×
1374
}
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