1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2026-05-14 05:12:09 -04:00

Plot orbits of planets.

This commit is contained in:
Jon Beniston 2026-04-07 18:21:28 +01:00
parent 3c3371cf2c
commit 80b5daa929
8 changed files with 207 additions and 100 deletions

View File

@ -266,7 +266,7 @@ To start a new animation, press ![clear animation](../../../doc/img/StarTracker_
<h3>Solar System Map</h3>
The Solar System Map shows the positions of the bodies selected in the Settings Dialog (5). Positions can be plotted on either a linear or logarithmic scale. The positions are displayed top down on the equatorial plane.
The Solar System Map shows the positions of the bodies selected in the Settings Dialog (5). Positions can be plotted on either a linear or logarithmic scale. The positions are displayed top down on the ecliptic plane.
The map will be centered at the body selected in the combo box. Select '-' to be able to pan the map with the mouse.
![Solar System Map](../../../doc/img/StarTracker_solarsystem.png)
@ -335,38 +335,30 @@ Then select the SDRangel telescope reticle and press Ocular view.
<h2>Attribution</h2>
Solar radio flux measurement at 10.7cm/2800MHz is from National Research Council Canada and Natural Resources Canada: https://www.spaceweather.gc.ca/forecast-prevision/solar-solaire/solarflux/sx-4-en.php
Solar radio flux measurements at 245, 410, 610, 1415, 2695, 4995, 8800 and 15400MHz from the Learmonth Observatory: http://www.sws.bom.gov.au/World_Data_Centre/1/10
150MHz (Landecker and Wielebinski) and 1420MHz (Stockert and Villa-Elisa) All Sky images from MPIfR's (Max-Planck-Institut Fur Radioastronomie) Survey Sampler: https://www3.mpifr-bonn.mpg.de/survey.html
408MHz (Haslam) destriped (Platania) All Sky image and spectral index (Platania) from Strasbourg astronomical Data Center: http://cdsarc.u-strasbg.fr/viz-bin/cat/J/A+A/410/847
Milky Way image from NASA/JPL-Caltech: https://photojournal.jpl.nasa.gov/catalog/PIA10748
Icons are by Adnen Kadri, iconsphere and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/
Icons are by Freepik from Flaticon https://www.flaticon.com/
Io & Ganymede Phase vs CML plots from Jupiter radio emission induced by Ganymede and consequences for the radio detection of exoplanets, Zarka et all, 2018: https://www.aanda.org/articles/aa/full_html/2018/10/aa33586-18/aa33586-18.html
Sun image from Solar Dynamics Observatory, NASA
Mercury image from Messenger, NASA/Johns Hopkins University Applied Physics Laboratory/Carnegie Institution of Washington.
Venus image by NASA/JPL-Caltech
Blue Marble by crew of Apollo 17.
Moon image by NASA.
Mars image Viking Orbiter, NASA/JPL-Caltech
Phobos image by NASA/JPL-Caltech/University of Arizona
Deimos image by NASA/JPL-Caltech/University of Arizona
Jupiter enhanced image by Kevin M. Gill (CC-BY) based on images provided courtesy of NASA/JPL-Caltech/SwRI/MSSS
Io from Galileo by NASA/JPL/USGS
Ganymede from Juno by NASA/JPL-Caltech/SwRI/MSSS/Kevin M. Gill
Calisto NASA/JPL/DLR
Saturn from Cassini, NASA/ESA
Uranus from Voyager 2, NASA/JPL-Caltech
Neptune from Voyager 2, NASA/JPL-Caltech
Pluto image from New Horizons, NASA/JHUAPL/SwRI
* Solar radio flux measurement at 10.7cm/2800MHz is from National Research Council Canada and Natural Resources Canada: https://www.spaceweather.gc.ca/forecast-prevision/solar-solaire/solarflux/sx-4-en.php
* Solar radio flux measurements at 245, 410, 610, 1415, 2695, 4995, 8800 and 15400MHz from the Learmonth Observatory: http://www.sws.bom.gov.au/World_Data_Centre/1/10
* 150MHz (Landecker and Wielebinski) and 1420MHz (Stockert and Villa-Elisa) All Sky images from MPIfR's (Max-Planck-Institut Fur Radioastronomie) Survey Sampler: https://www3.mpifr-bonn.mpg.de/survey.html
* 408MHz (Haslam) destriped (Platania) All Sky image and spectral index (Platania) from Strasbourg astronomical Data Center: http://cdsarc.u-strasbg.fr/viz-bin/cat/J/A+A/410/847
* Milky Way image from NASA/JPL-Caltech: https://photojournal.jpl.nasa.gov/catalog/PIA10748
* Icons are by Adnen Kadri, iconsphere and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/
* Icons are by Freepik from Flaticon https://www.flaticon.com/
* Io & Ganymede Phase vs CML plots from Jupiter radio emission induced by Ganymede and consequences for the radio detection of exoplanets, Zarka et all, 2018: https://www.aanda.org/articles/aa/full_html/2018/10/aa33586-18/aa33586-18.html
* Sun image from Solar Dynamics Observatory, NASA
* Mercury image from Messenger, NASA/Johns Hopkins University Applied Physics Laboratory/Carnegie Institution of Washington.
* Venus image by NASA/JPL-Caltech
* Blue Marble by crew of Apollo 17.
* Moon image by NASA.
* Mars image Viking Orbiter, NASA/JPL-Caltech
* Phobos image by NASA/JPL-Caltech/University of Arizona
* Deimos image by NASA/JPL-Caltech/University of Arizona
* Jupiter enhanced image by Kevin M. Gill (CC-BY) based on images provided courtesy of NASA/JPL-Caltech/SwRI/MSSS
* Io from Galileo by NASA/JPL/USGS
* Ganymede from Juno by NASA/JPL-Caltech/SwRI/MSSS/Kevin M. Gill
* Calisto NASA/JPL/DLR
* Saturn from Cassini, NASA/ESA
* Uranus from Voyager 2, NASA/JPL-Caltech
* Neptune from Voyager 2, NASA/JPL-Caltech
* Pluto image from New Horizons, NASA/JHUAPL/SwRI
<h2>API</h2>

View File

@ -1,4 +1,4 @@
///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2026 Jon Beniston, M7RCE <jon@beniston.com> //
// //
// This program is free software; you can redistribute it and/or modify //
@ -93,16 +93,6 @@ static SpiceDouble normalize360(SpiceDouble deg)
return d;
}
// Normalize angle to (-180, +180]
static SpiceDouble normalize180(SpiceDouble deg)
{
SpiceDouble d = fmod(deg + 180.0, 360.0);
if (d < 0.0) {
d += 360.0;
}
return d - 180.0;
}
// Compute topocentric observer position in J2000 at ET
static bool computeTopocenterJ2000(SpiceDouble latDeg, SpiceDouble lonDeg, SpiceDouble altKm, SpiceDouble et, SpiceDouble obsJ2000[3])
{
@ -214,32 +204,6 @@ QStringList getSPICETargets(const QString& file)
return targets;
}
// Get position of named body relative to Solar System Barycentre. No abberation correction
bool getSSBPositionFromSPICE(const QString& name, double et, QVector3D &positionKm)
{
SpiceDouble posJ2000[3];
SpiceDouble lightTime;
const QByteArray nameBA = name.toLatin1();
const char *nameStr = nameBA.constData();
// Get position of named body, relative to SSB.
spkpos_c(nameStr, et, "J2000", "NONE", "SOLAR SYSTEM BARYCENTER", posJ2000, &lightTime);
if (!failed_c())
{
positionKm.setX(posJ2000[0]);
positionKm.setY(posJ2000[1]);
positionKm.setZ(posJ2000[2]);
return true;
}
else
{
qDebug() << "StarTrackerWorker::calculateSolarSystemPositions: Failed to get position for" << name;
reset_c();
return false;
}
}
// Calculate azimuth and elevation of a target body from a position on Earth using SPICE
bool getAzElFromSPICE(const QString& target, double et, double latitude, double longitude, double altitudeKm, double &azimuth, double &elevation)
{
@ -314,7 +278,7 @@ bool getRADecFromSPICE(const QString& target, double et, double &ra, double &dec
}
// Calculate Jupiter's CML (Central Meridian Longitude) as seen from a location on Earth and the phase of the given Jovian moon (IO or GANYMEDE)
bool calculateJupiterMoonPhase(const QString& moon, double et, double latitude, double longitude, double altitudeMetres, double &cml, double &phase)
bool spiceJupiterMoonPhase(const QString& moon, double et, double latitude, double longitude, double altitudeMetres, double &cml, double &phase)
{
const QByteArray moonBA = moon.toUpper().toLatin1();
const char *moonName = moonBA.data();
@ -428,3 +392,109 @@ bool calculateJupiterMoonPhase(const QString& moon, double et, double latitude,
return true;
}
// Get position of named body relative to Sun, in ECLIPJ2000 reference frame. No abberation correction.
bool spicePosition(const QString& name, double et, QVector3D &positionKm)
{
SpiceDouble pos[3];
SpiceDouble lightTime;
const QByteArray nameBA = name.toLatin1();
const char *nameStr = nameBA.constData();
// Get position of named body
spkpos_c(nameStr, et, "ECLIPJ2000", "NONE", "SUN", pos, &lightTime);
if (!failed_c())
{
positionKm.setX(pos[0]);
positionKm.setY(pos[1]);
positionKm.setZ(pos[2]);
return true;
}
else
{
qDebug() << "spicePosition: Failed to get position for" << name;
reset_c();
return false;
}
}
// Convert orbital elements to perifocal (PQW) coordinate system
static void orbitalElementsToPerifocal(SpiceDouble omega, SpiceDouble inc, SpiceDouble argp,
SpiceDouble p[3], SpiceDouble q[3])
{
SpiceDouble cO = cos(omega);
SpiceDouble sO = sin(omega);
SpiceDouble ci = cos(inc);
SpiceDouble si = sin(inc);
SpiceDouble cw = cos(argp);
SpiceDouble sw = sin(argp);
// Phat (toward periapsis)
p[0] = cO * cw - sO * sw * ci;
p[1] = sO * cw + cO * sw * ci;
p[2] = sw * si;
// Qhat (90 deg ahead in orbit plane)
q[0] = -cO * sw - sO * cw * ci;
q[1] = -sO * sw + cO * cw * ci;
q[2] = cw * si;
}
// Get orbital path around the Sun, in XY plane of ECLIPJ2000 reference frame.
bool spiceOrbit(const QString& name, double et, QList<QPointF> &orbit)
{
const QByteArray nameBA = name.toUpper().toLatin1();
const char *target = nameBA.data();
// Sun relative state, expressed in ECLIPJ2000
SpiceDouble state[6], lt;
spkezr_c(target, et, "ECLIPJ2000", "NONE", "SUN", state, &lt);
// Sun GM (gravitational parameter = G * M)
SpiceInt dim;
SpiceDouble mu;
bodvrd_c("SUN", "GM", 1, &dim, &mu);
// Osculating conic elements
SpiceDouble elts[8];
oscelt_c(state, et, mu, elts);
SpiceDouble rp = elts[0]; // perifocal distance (km)
SpiceDouble e = elts[1]; // eccentricity
SpiceDouble inc = elts[2]; // inclination (rad)
SpiceDouble omega = elts[3]; // longitude of ascending node (rad)
SpiceDouble argp = elts[4]; // argument of periapsis (rad)
if (e >= 1.0)
{
qDebug() << "spiceOrbit: Not an ellipse" << name;
return false;
}
SpiceDouble a = rp / (1.0 - e); // semi-major axis in orbital plane
SpiceDouble b = a * sqrt(1.0 - e * e); // semi-minor axis in orbital plane
SpiceDouble pHat[3], qHat[3];
orbitalElementsToPerifocal(omega, inc, argp, pHat, qHat);
const int N = 1000; // Could vary this with zoom level. 1k good enough when zoomed to orbit of the Moon around Earth
for (int k = 0; k <= N; k++)
{
const double E = 2.0 * M_PI * (double) k / (double) N;
// Ellipse in PQW with Sun at focus
double xp = a * (cos(E) - e);
double yp = b * sin(E);
// Rotate into ECLIPJ2000
double x = xp * pHat[0] + yp * qHat[0];
double y = xp * pHat[1] + yp * qHat[1];
QPointF point = {x, y};
orbit.append(point);
}
return true;
}

View File

@ -28,9 +28,10 @@ void spiceUnlock();
bool dateTimeToET(const QDateTime& dateTime, double &et);
QStringList getSPICETargets(const QString& file);
bool getSSBPositionFromSPICE(const QString& name, double et, QVector3D &position);
bool getAzElFromSPICE(const QString& target, double et, double latitude, double longitude, double altitudeKm, double &azimuth, double &elevation);
bool getRADecFromSPICE(const QString& target, double et, double &ra, double &dec);
bool calculateJupiterMoonPhase(const QString& moon, double et, double latitude, double longitude, double altitudeMetres, double& cml, double &phase);
bool spiceJupiterMoonPhase(const QString& moon, double et, double latitude, double longitude, double altitudeMetres, double& cml, double &phase);
bool spicePosition(const QString& name, double et, QVector3D &position);
bool spiceOrbit(const QString& name, double et, QList<QPointF> &orbit);
#endif // STARTRACKER_SPICE_H

View File

@ -172,7 +172,7 @@ bool StarTrackerGUI::handleMessage(const Message& message)
else if (StarTrackerReport::MsgReportSolarSystemPositions::match(message))
{
StarTrackerReport::MsgReportSolarSystemPositions& report = (StarTrackerReport::MsgReportSolarSystemPositions&) message;
updateSolarSystemPositions(report.getNames(), report.getPositions());
updateSolarSystemPositions(report.getNames(), report.getPositions(), report.getOrbit());
return true;
}
else if (StarTrackerReport::MsgReportJupiter::match(message))
@ -1702,23 +1702,13 @@ static void logScale(QVector3D &p)
p.setZ(r * std::cos(el));
}
QRectF getVisibleRect( QGraphicsView * view )
{
QPointF A = view->mapToScene( QPoint(0, 0) );
QPointF B = view->mapToScene( QPoint(
view->viewport()->width(),
view->viewport()->height() ));
return QRectF( A, B );
}
void StarTrackerGUI::updateSolarSystemPositions(const QStringList &names, const QList<QVector3D> &positions)
void StarTrackerGUI::updateSolarSystemPositions(const QStringList &names, const QList<QVector3D> &positions, const QList<QList<QPointF>> &orbits)
{
const double kmToAU = 6.68459e-9; // Convert position from kM to AU
const double pixelScale = 20.0; // Mercury is 0.4AU. Neptune is 30 AU
bool scale = false;
QPen orbitPen(Qt::gray);
orbitPen.setWidth(0);
for (int i = 0; i < names.size(); i++)
{
@ -1738,9 +1728,18 @@ void StarTrackerGUI::updateSolarSystemPositions(const QStringList &names, const
QBrush brush(QColor(200, 200, 200));
item = new SolarSystemItem();
item->m_textItem = m_solarSystemScene->addText(name, m_solarSystemLabelFont);
item->m_textItem->setZValue(2);
item->m_scale = getScale(name.toLower());
item->m_pixmapItem = m_solarSystemScene->addPixmap(*pixmap);
item->m_pixmapItem->setOffset(-pixmap->width() / 2, -pixmap->height() / 2);
item->m_pixmapItem->setZValue(1);
if (orbits[i].size() > 0) {
item->m_orbitItem = m_solarSystemScene->addPolygon(QPolygonF(), orbitPen);
} else {
item->m_orbitItem = nullptr;
}
m_solarSystemItems.insert(names[i], item);
scale = true;
}
@ -1753,6 +1752,33 @@ void StarTrackerGUI::updateSolarSystemPositions(const QStringList &names, const
scaledPos = pixelScale * kmToAU * scaledPos;
}
// Draw an ellipse for planet's orbit
if (item->m_orbitItem)
{
QPolygonF polygon;
for (int j = 0; j < orbits[i].size(); j++)
{
QPointF point = orbits[i][j];
if (m_settings.m_logScale)
{
QVector3D v = {(float) point.x(), (float) point.y(), 0.0f};
logScale(v);
point.setX(v.x());
point.setY(v.y());
}
else
{
point = pixelScale * kmToAU * point;
}
polygon << point;
}
item->m_orbitItem->setPolygon(polygon);
}
// Position label to right
const int textYOffset = m_solarSystemLabelFontMetrics.height();
item->m_pixmapItem->setPos(scaledPos.x(), scaledPos.y());
@ -1774,6 +1800,7 @@ void StarTrackerGUI::updateSolarSystemPositions(const QStringList &names, const
SolarSystemItem* item = itr.value();
m_solarSystemScene->removeItem(item->m_pixmapItem);
m_solarSystemScene->removeItem(item->m_textItem);
m_solarSystemScene->removeItem(item->m_orbitItem);
delete item;
itr.remove();
}
@ -1868,7 +1895,6 @@ void StarTrackerGUI::centerOnSolarSystemBody()
if (m_solarSystemItems.contains(selectedBody))
{
SolarSystemItem *item = m_solarSystemItems.value(selectedBody);
//qDebug() << "scene rect" << m_solarSystemScene->sceneRect() << "view scene rect" << ui->image->sceneRect() << "visible rect" << getVisibleRect(ui->image) << "centre on" << selectedBody << item->m_pixmapItem->pos();
ui->image->centerOn(item->m_pixmapItem);
ui->image->setDragMode(QGraphicsView::NoDrag);
}

View File

@ -144,6 +144,7 @@ private:
QGraphicsPixmapItem *m_pixmapItem;
QGraphicsTextItem *m_textItem;
float m_scale;
QGraphicsPolygonItem *m_orbitItem;
};
QFont m_solarSystemLabelFont;
QFontMetrics m_solarSystemLabelFontMetrics;
@ -202,7 +203,8 @@ private:
void plotJupiter();
void updateJupiterMoonPosition(double cml, double ioPhase, double ganymedePhase);
void updateJupiterMoonPositions(const StarTrackerReport::MsgReportJupiterData& report);
void updateSolarSystemPositions(const QStringList &names, const QList<QVector3D> &positions);
void updateSolarSystemPositions(const QStringList &names, const QList<QVector3D> &positions,
const QList<QList<QPointF>> &orbit);
void scaleSolarSystemItems();
void createSolarSystemScene();
QPixmap *getPlanetPixmap(const QString& name);

View File

@ -145,22 +145,25 @@ public:
QDateTime getDateTime() const { return m_dateTime; }
const QStringList &getNames() const { return m_names; }
const QList<QVector3D> &getPositions() const { return m_positions; }
const QList<QList<QPointF>> &getOrbit() const { return m_orbit; }
static MsgReportSolarSystemPositions* create(const QDateTime& dateTime, const QStringList &names, const QList<QVector3D> &positions)
static MsgReportSolarSystemPositions* create(const QDateTime& dateTime, const QStringList &names, const QList<QVector3D> &positions, QList<QList<QPointF>> &orbit)
{
return new MsgReportSolarSystemPositions(dateTime, names, positions);
return new MsgReportSolarSystemPositions(dateTime, names, positions, orbit);
}
private:
QDateTime m_dateTime;
QStringList m_names;
QList<QVector3D> m_positions;
QList<QList<QPointF>> m_orbit;
MsgReportSolarSystemPositions(const QDateTime& dateTime, const QStringList &names, const QList<QVector3D> &positions) :
MsgReportSolarSystemPositions(const QDateTime& dateTime, const QStringList &names, const QList<QVector3D> &positions, const QList<QList<QPointF>> &orbit) :
Message(),
m_dateTime(dateTime),
m_names(names),
m_positions(positions)
m_positions(positions),
m_orbit(orbit)
{
}

View File

@ -27,6 +27,7 @@
const QStringList StarTrackerSettings::m_defaultSpiceEphemerides = {
"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/lsk/naif0012.tls",
"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/pck00011.tpc",
"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/gm_de440.tpc",
"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de440.bsp",
"https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/satellites/jup365.bsp",
};

View File

@ -1062,6 +1062,12 @@ void StarTrackerWorker::calculateSolarSystemPositions(const QDateTime& dateTime)
{
QStringList bodyNames;
QList<QVector3D> bodyPositions;
QList<QList<QPointF>> orbits;
static const QStringList planets = {
"MERCURY", "VENUS", "EARTH", "MARS", "JUPITER", "SATURN", "URANUS", "NEPTUNE",
"MERCURY BARYCENTER", "VENUS BARYCENTER", "EARTH BARYCENTER", "MARS BARYCENTER", "JUPITER BARYCENTER", "SATURN BARYCENTER", "URANUS BARYCENTER", "NEPTUNE BARYCENTER"
};
double et;
if (!dateTimeToET(dateTime, et)) {
@ -1072,17 +1078,23 @@ void StarTrackerWorker::calculateSolarSystemPositions(const QDateTime& dateTime)
{
QVector3D positionKm;
if (getSSBPositionFromSPICE(body, et, positionKm))
if (spicePosition(body, et, positionKm))
{
QVector3D position(positionKm[0], positionKm[1], positionKm[2]);
bodyNames.append(body);
bodyPositions.append(position);
QList<QPointF> orbit;
if (planets.contains(body)) {
spiceOrbit(body, et, orbit);
}
orbits.append(orbit);
}
}
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportSolarSystemPositions::create(dateTime, bodyNames, bodyPositions));
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportSolarSystemPositions::create(dateTime, bodyNames, bodyPositions, orbits));
}
}
@ -1111,9 +1123,9 @@ void StarTrackerWorker::calculateJupiterParameters(const QDateTime& dateTime)
if (getAzElFromSPICE("JUPITER", et, m_settings.m_latitude, m_settings.m_longitude, 0.0, az, el))
{
// Calculate Jupiter CML and phase of Moons
if (calculateJupiterMoonPhase("IO", et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, ioPhase))
if (spiceJupiterMoonPhase("IO", et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, ioPhase))
{
if (calculateJupiterMoonPhase("GANYMEDE", et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, ganymedePhase))
if (spiceJupiterMoonPhase("GANYMEDE", et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, ganymedePhase))
{
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportJupiter::create(dateTime, az, el, cml, ioPhase, ganymedePhase));
@ -1138,7 +1150,7 @@ void StarTrackerWorker::calculateJupiterParameters(const QDateTime& dateTime)
{
if (el > 0.0)
{
if (calculateJupiterMoonPhase(moon, et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, moonPhase))
if (spiceJupiterMoonPhase(moon, et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, moonPhase))
{
StarTrackerReport::JupiterData jd = {dt, az, el};
StarTrackerReport::JupiterMoonData md = {cml, moonPhase};
@ -1169,7 +1181,7 @@ void StarTrackerWorker::calculateJupiterParameters(const QDateTime& dateTime)
{
if (el > 0.0)
{
if (calculateJupiterMoonPhase(moon, et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, moonPhase))
if (spiceJupiterMoonPhase(moon, et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, moonPhase))
{
StarTrackerReport::JupiterData jd = {dt, az, el};
StarTrackerReport::JupiterMoonData id = {cml, moonPhase};
@ -1216,7 +1228,7 @@ void StarTrackerWorker::calculateJupiterParameters(const QDateTime& dateTime)
{
if (el > 0.0)
{
if (calculateJupiterMoonPhase(moon, et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, moonPhase))
if (spiceJupiterMoonPhase(moon, et, m_settings.m_latitude, m_settings.m_longitude, 0.0, cml, moonPhase))
{
StarTrackerReport::JupiterData jd = {dt, az, el};
StarTrackerReport::JupiterMoonData id = {cml, moonPhase};