1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-26 17:58:43 -05:00
sdrangel/plugins/feature/startracker/startrackergui.cpp

2089 lines
73 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <algorithm>
#include <QMessageBox>
#include <QLineEdit>
#include <QRegExp>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QGraphicsScene>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QDateTimeAxis>
#include <QtCharts/QValueAxis>
#include "SWGStarTrackerDisplaySettings.h"
#include "SWGStarTrackerDisplayLoSSettings.h"
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "gui/dmsspinbox.h"
#include "gui/graphicsviewzoom.h"
#include "mainwindow.h"
#include "device/deviceuiset.h"
#include "util/units.h"
#include "util/astronomy.h"
#include "util/interpolation.h"
#include "util/png.h"
#include "ui_startrackergui.h"
#include "startracker.h"
#include "startrackergui.h"
#include "startrackerreport.h"
#include "startrackersettingsdialog.h"
StarTrackerGUI* StarTrackerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
StarTrackerGUI* gui = new StarTrackerGUI(pluginAPI, featureUISet, feature);
return gui;
}
void StarTrackerGUI::destroy()
{
qDeleteAll(m_lineOfSightMarkers);
delete this;
}
void StarTrackerGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray StarTrackerGUI::serialize() const
{
return m_settings.serialize();
}
bool StarTrackerGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
m_feature->setWorkspaceIndex(m_settings.m_workspaceIndex);
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool StarTrackerGUI::handleMessage(const Message& message)
{
if (StarTracker::MsgConfigureStarTracker::match(message))
{
qDebug("StarTrackerGUI::handleMessage: StarTracker::MsgConfigureStarTracker");
const StarTracker::MsgConfigureStarTracker& cfg = (StarTracker::MsgConfigureStarTracker&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (StarTrackerReport::MsgReportAzAl::match(message))
{
StarTrackerReport::MsgReportAzAl& azAl = (StarTrackerReport::MsgReportAzAl&) message;
blockApplySettings(true);
ui->azimuth->setValue(azAl.getAzimuth());
ui->elevation->setValue(azAl.getElevation());
blockApplySettings(false);
return true;
}
else if (StarTrackerReport::MsgReportRADec::match(message))
{
StarTrackerReport::MsgReportRADec& raDec = (StarTrackerReport::MsgReportRADec&) message;
QString target = raDec.getTarget();
if (target == "target")
{
m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDec.getRA());
m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(raDec.getDec());
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
}
else if (target == "sun")
{
m_sunRA = raDec.getRA();
m_sunDec = raDec.getDec();
}
else if (target == "moon")
{
m_moonRA = raDec.getRA();
m_moonDec = raDec.getDec();
}
raDecChanged();
return true;
}
else if (StarTrackerReport::MsgReportGalactic::match(message))
{
StarTrackerReport::MsgReportGalactic& galactic = (StarTrackerReport::MsgReportGalactic&) message;
blockApplySettings(true);
ui->galacticLongitude->setValue(galactic.getL());
ui->galacticLatitude->setValue(galactic.getB());
blockApplySettings(false);
return true;
}
else if (MainCore::MsgStarTrackerDisplaySettings::match(message))
{
if (m_settings.m_link)
{
MainCore::MsgStarTrackerDisplaySettings& settings = (MainCore::MsgStarTrackerDisplaySettings&) message;
SWGSDRangel::SWGStarTrackerDisplaySettings *swgSettings = settings.getSWGStarTrackerDisplaySettings();
ui->dateTimeSelect->setCurrentText("Custom");
QDateTime dt = QDateTime::fromString(*swgSettings->getDateTime(), Qt::ISODateWithMs);
ui->dateTime->setDateTime(dt);
ui->target->setCurrentText("Custom Az/El");
ui->azimuth->setValue(swgSettings->getAzimuth());
ui->elevation->setValue(swgSettings->getElevation());
}
return true;
}
else if (MainCore::MsgStarTrackerDisplayLoSSettings::match(message))
{
MainCore::MsgStarTrackerDisplayLoSSettings& settings = (MainCore::MsgStarTrackerDisplayLoSSettings&) message;
SWGSDRangel::SWGStarTrackerDisplayLoSSettings *swgSettings = settings.getSWGStarTrackerDisplayLoSSettings();
bool found = false;
for (int i = 0; i < m_lineOfSightMarkers.size(); i++)
{
if (m_lineOfSightMarkers[i]->m_name == swgSettings->getName())
{
if (swgSettings->getD() == 0.0)
{
// Delete
ui->image->scene()->removeItem(m_lineOfSightMarkers[i]->m_text);
delete m_lineOfSightMarkers[i]->m_text;
delete m_lineOfSightMarkers[i];
m_lineOfSightMarkers.removeAt(i);
}
else
{
// Update
m_lineOfSightMarkers[i]->m_l = swgSettings->getL();
m_lineOfSightMarkers[i]->m_b = swgSettings->getB();
m_lineOfSightMarkers[i]->m_d = swgSettings->getD();
plotGalacticMarker(m_lineOfSightMarkers[i]);
}
found = true;
break;
}
}
if (!found && (swgSettings->getD() != 0.0))
{
// Create new
LoSMarker* marker = new LoSMarker();
marker->m_name = *swgSettings->getName();
marker->m_l = swgSettings->getL();
marker->m_b = swgSettings->getB();
marker->m_d = swgSettings->getD();
marker->m_text = ui->image->scene()->addText(marker->m_name);
m_lineOfSightMarkers.append(marker);
plotGalacticMarker(marker);
}
return true;
}
return false;
}
void StarTrackerGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void StarTrackerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
RollupContents *rollupContents = getRollupContents();
if (rollupContents->hasExpandableWidgets()) {
setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Expanding);
} else {
setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Fixed);
}
int h = rollupContents->height() + getAdditionalHeight();
int w = std::max(width(), rollupContents->minimumWidth() + gripSize() * 2);
resize(w, h);
rollupContents->saveState(m_rollupState);
applySettings();
}
StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::StarTrackerGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true),
m_lastFeatureState(0),
m_azElLineChart(nullptr),
m_azElPolarChart(nullptr),
m_solarFluxChart(nullptr),
m_networkManager(nullptr),
m_solarFlux(0.0),
m_solarFluxesValid(false),
m_images{QImage(":/startracker/startracker/150mhz_ra_dec.png"),
QImage(":/startracker/startracker/150mhz_galactic.png"),
QImage(":/startracker/startracker/408mhz_ra_dec.png"),
QImage(":/startracker/startracker/408mhz_galactic.png"),
QImage(":/startracker/startracker/1420mhz_ra_dec.png"),
QImage(":/startracker/startracker/1420mhz_galactic.png")},
m_milkyWayImages{QPixmap(":/startracker/startracker/milkyway.png"),
QPixmap(":/startracker/startracker/milkywayannotated.png")},
m_sunRA(0.0),
m_sunDec(0.0),
m_moonRA(0.0),
m_moonDec(0.0)
{
m_feature = feature;
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/feature/startracker/readme.md";
RollupContents *rollupContents = getRollupContents();
ui->setupUi(rollupContents);
setSizePolicy(rollupContents->sizePolicy());
rollupContents->arrangeRollups();
connect(rollupContents, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_starTracker = reinterpret_cast<StarTracker*>(feature);
m_starTracker->setMessageQueueToGUI(&m_inputMessageQueue);
m_settings.setRollupState(&m_rollupState);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &StarTrackerGUI::downloadFinished);
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(1000);
connect(ui->azimuth, SIGNAL(valueChanged(double)), this, SLOT(on_azimuth_valueChanged(double)));
ui->azimuth->setRange(0, 360.0);
ui->elevation->setRange(-90.0, 90.0);
ui->galacticLongitude->setRange(0, 360.0);
ui->galacticLatitude->setRange(-90.0, 90.0);
ui->galacticLatitude->setText("");
ui->galacticLongitude->setText("");
// Intialise chart
m_chart.legend()->hide();
ui->chart->setChart(&m_chart);
ui->chart->setRenderHint(QPainter::Antialiasing);
m_chart.addAxis(&m_chartXAxis, Qt::AlignBottom);
m_chart.addAxis(&m_chartYAxis, Qt::AlignLeft);
m_chart.layout()->setContentsMargins(0, 0, 0, 0);
m_chart.setMargins(QMargins(1, 1, 1, 1));
// Create axes that are static
m_skyTempGalacticLXAxis.setTitleText(QString("Galactic longitude (%1)").arg(QChar(0xb0)));
m_skyTempGalacticLXAxis.setMin(0);
m_skyTempGalacticLXAxis.setMax(360);
m_skyTempGalacticLXAxis.append("180", 0);
m_skyTempGalacticLXAxis.append("90", 90);
m_skyTempGalacticLXAxis.append("0/360", 180);
m_skyTempGalacticLXAxis.append("270", 270);
//m_skyTempGalacticLXAxis.append("180", 360); // Note - labels need to be unique, so can't have 180 at start and end
m_skyTempGalacticLXAxis.setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
m_skyTempGalacticLXAxis.setGridLineVisible(false);
m_skyTempRAXAxis.setTitleText(QString("Right ascension (hours)"));
m_skyTempRAXAxis.setMin(0);
m_skyTempRAXAxis.setMax(24);
m_skyTempRAXAxis.append("12", 0);
m_skyTempRAXAxis.append("9", 3);
m_skyTempRAXAxis.append("6", 6);
m_skyTempRAXAxis.append("3", 9);
m_skyTempRAXAxis.append("0", 12);
m_skyTempRAXAxis.append("21", 15);
m_skyTempRAXAxis.append("18", 18);
m_skyTempRAXAxis.append("15", 21);
//m_skyTempRAXAxis.append("12", 24); // Note - labels need to be unique, so can't have 12 at start and end
m_skyTempRAXAxis.setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
m_skyTempRAXAxis.setGridLineVisible(false);
m_skyTempYAxis.setGridLineVisible(false);
m_skyTempYAxis.setRange(-90.0, 90.0);
m_skyTempYAxis.setGridLineVisible(false);
ui->dateTime->setDateTime(QDateTime::currentDateTime());
displaySettings();
applySettings(true);
makeUIConnections();
// Populate subchart menu
on_chartSelect_currentIndexChanged(0);
connect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
// Use My Position from preferences, if none set
if ((m_settings.m_latitude == 0.0) && (m_settings.m_longitude == 0.0))
on_useMyPosition_clicked();
/*
printf("saemundsson=[");
for (int i = 0; i <= 90; i+= 5)
printf("%f ", Astronomy::refractionSaemundsson(i, m_settings.m_pressure, m_settings.m_temperature));
printf("];\n");
printf("palRadio=[");
for (int i = 0; i <= 90; i+= 5)
printf("%f ", Astronomy::refractionPAL(i, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
100000000, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate));
printf("];\n");
printf("palLight=[");
for (int i = 0; i <= 90; i+= 5)
printf("%f ",Astronomy::refractionPAL(i, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
7.5e14, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate));
printf("];\n");
*/
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&StarTrackerGUI::networkManagerFinished
);
readSolarFlux();
connect(&m_solarFluxTimer, SIGNAL(timeout()), this, SLOT(autoUpdateSolarFlux()));
m_solarFluxTimer.start(1000*60*60*24); // Update every 24hours
autoUpdateSolarFlux();
createGalacticLineOfSightScene();
plotChart();
}
StarTrackerGUI::~StarTrackerGUI()
{
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&StarTrackerGUI::networkManagerFinished
);
delete m_networkManager;
delete ui;
}
void StarTrackerGUI::setWorkspaceIndex(int index)
{
m_settings.m_workspaceIndex = index;
m_feature->setWorkspaceIndex(index);
}
void StarTrackerGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void StarTrackerGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
setTitle(m_settings.m_title);
blockApplySettings(true);
ui->darkTheme->setChecked(m_settings.m_chartsDarkTheme);
if (m_solarFluxChart) {
m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
}
m_chart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
ui->drawSun->setChecked(m_settings.m_drawSunOnSkyTempChart);
ui->drawMoon->setChecked(m_settings.m_drawMoonOnSkyTempChart);
ui->link->setChecked(m_settings.m_link);
ui->latitude->setValue(m_settings.m_latitude);
ui->longitude->setValue(m_settings.m_longitude);
ui->target->setCurrentIndex(ui->target->findText(m_settings.m_target));
ui->azimuth->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->elevation->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->galacticLatitude->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->galacticLongitude->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->azimuthOffset->setValue(m_settings.m_azOffset);
ui->elevationOffset->setValue(m_settings.m_elOffset);
if (m_settings.m_target == "Custom RA/Dec")
{
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
}
else if (m_settings.m_target == "Custom Az/El")
{
ui->azimuth->setValue(m_settings.m_az);
ui->elevation->setValue(m_settings.m_el);
}
else if ( (m_settings.m_target == "Custom l/b")
|| (m_settings.m_target == "S7")
|| (m_settings.m_target == "S8")
|| (m_settings.m_target == "S9")
)
{
ui->galacticLatitude->setValue(m_settings.m_b);
ui->galacticLongitude->setValue(m_settings.m_l);
}
if (m_settings.m_dateTime == "")
{
ui->dateTimeSelect->setCurrentIndex(0);
ui->dateTime->setVisible(false);
}
else
{
ui->dateTime->setDateTime(QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs));
ui->dateTime->setVisible(true);
ui->dateTimeSelect->setCurrentIndex(1);
}
if ((m_settings.m_solarFluxData != StarTrackerSettings::DRAO_2800) && !m_solarFluxesValid)
autoUpdateSolarFlux();
ui->frequency->setValue(m_settings.m_frequency/1000000.0);
ui->beamwidth->setValue(m_settings.m_beamwidth);
updateForTarget();
getRollupContents()->restoreState(m_rollupState);
plotChart();
blockApplySettings(false);
}
void StarTrackerGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.setDefaultTitle(m_displayedName);
dialog.move(p);
dialog.exec();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void StarTrackerGUI::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
StarTracker::MsgStartStop *message = StarTracker::MsgStartStop::create(checked);
m_starTracker->getInputMessageQueue()->push(message);
}
}
void StarTrackerGUI::on_latitude_valueChanged(double value)
{
m_settings.m_latitude = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_longitude_valueChanged(double value)
{
m_settings.m_longitude = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_rightAscension_editingFinished()
{
m_settings.m_ra = ui->rightAscension->text();
applySettings();
plotChart();
}
void StarTrackerGUI::on_declination_editingFinished()
{
m_settings.m_dec = ui->declination->text();
applySettings();
plotChart();
}
void StarTrackerGUI::on_azimuth_valueChanged(double value)
{
m_settings.m_az = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_elevation_valueChanged(double value)
{
m_settings.m_el = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_azimuthOffset_valueChanged(double value)
{
m_settings.m_azOffset = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_elevationOffset_valueChanged(double value)
{
m_settings.m_elOffset = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_galacticLatitude_valueChanged(double value)
{
m_settings.m_b = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_galacticLongitude_valueChanged(double value)
{
m_settings.m_l = value;
applySettings();
plotChart();
}
void StarTrackerGUI::updateForTarget()
{
if (m_settings.m_target == "Sun")
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
ui->rightAscension->setText("");
ui->declination->setText("");
}
else if (m_settings.m_target == "Moon")
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
ui->rightAscension->setText("");
ui->declination->setText("");
}
else if (m_settings.m_target == "Custom RA/Dec")
{
ui->rightAscension->setReadOnly(false);
ui->declination->setReadOnly(false);
}
else if (m_settings.m_target == "S7")
{
ui->galacticLatitude->setValue(-1.0);
ui->galacticLongitude->setValue(132.0);
}
else if (m_settings.m_target == "S8")
{
ui->galacticLatitude->setValue(-15.0);
ui->galacticLongitude->setValue(207.0);
}
else if (m_settings.m_target == "S9")
{
ui->galacticLatitude->setValue(-4.0);
ui->galacticLongitude->setValue(356.0);
}
else
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
if (m_settings.m_target == "PSR B0329+54")
{
ui->rightAscension->setText("03h32m59.35s");
ui->declination->setText(QString("54%0134'45.05\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "PSR B0833-45")
{
ui->rightAscension->setText("08h35m20.66s");
ui->declination->setText(QString("-45%0110'35.15\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Sagittarius A")
{
ui->rightAscension->setText("17h45m40.04s");
ui->declination->setText(QString("-29%0100'28.17\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Cassiopeia A")
{
ui->rightAscension->setText("23h23m24s");
ui->declination->setText(QString("58%0148'54\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Cygnus A")
{
ui->rightAscension->setText("19h59m28.36s");
ui->declination->setText(QString("40%0144'02.1\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Taurus A (M1)")
{
ui->rightAscension->setText("05h34m31.94s");
ui->declination->setText(QString("22%0100'52.2\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Virgo A (M87)")
{
ui->rightAscension->setText("12h30m49.42s");
ui->declination->setText(QString("12%0123'28.04\"").arg(QChar(0xb0)));
}
on_rightAscension_editingFinished();
on_declination_editingFinished();
}
if (m_settings.m_target != "Custom Az/El")
{
ui->azimuth->setReadOnly(true);
ui->elevation->setReadOnly(true);
// Clear as no longer valid when target has changed
ui->azimuth->setText("");
ui->elevation->setText("");
}
else
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
ui->azimuth->setReadOnly(false);
ui->elevation->setReadOnly(false);
}
}
void StarTrackerGUI::on_target_currentTextChanged(const QString &text)
{
m_settings.m_target = text;
applySettings();
updateForTarget();
plotChart();
}
void StarTrackerGUI::updateLST()
{
QDateTime dt;
if (m_settings.m_dateTime.isEmpty())
dt = QDateTime::currentDateTime();
else
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
double lst = Astronomy::localSiderealTime(dt, m_settings.m_longitude);
ui->lst->setText(Units::decimalHoursToHoursMinutesAndSeconds(lst/15.0, 0));
}
void StarTrackerGUI::updateStatus()
{
int state = m_starTracker->getState();
if (m_lastFeatureState != state)
{
// We set checked state of start/stop button, in case it was changed via API
bool oldState;
switch (state)
{
case Feature::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case Feature::StIdle:
oldState = ui->startStop->blockSignals(true);
ui->startStop->setChecked(false);
ui->startStop->blockSignals(oldState);
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case Feature::StRunning:
oldState = ui->startStop->blockSignals(true);
ui->startStop->setChecked(true);
ui->startStop->blockSignals(oldState);
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case Feature::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_starTracker->getErrorMessage());
break;
default:
break;
}
m_lastFeatureState = state;
}
updateLST();
}
void StarTrackerGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
StarTracker::MsgConfigureStarTracker* message = StarTracker::MsgConfigureStarTracker::create(m_settings, force);
m_starTracker->getInputMessageQueue()->push(message);
}
}
void StarTrackerGUI::on_link_clicked(bool checked)
{
m_settings.m_link = checked;
applySettings();
}
void StarTrackerGUI::on_useMyPosition_clicked(bool checked)
{
(void) checked;
double stationLatitude = MainCore::instance()->getSettings().getLatitude();
double stationLongitude = MainCore::instance()->getSettings().getLongitude();
double stationAltitude = MainCore::instance()->getSettings().getAltitude();
ui->latitude->setValue(stationLatitude);
ui->longitude->setValue(stationLongitude);
m_settings.m_heightAboveSeaLevel = stationAltitude;
applySettings();
plotChart();
}
// Show settings dialog
void StarTrackerGUI::on_displaySettings_clicked()
{
StarTrackerSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
{
applySettings();
ui->elevation->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->azimuth->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->galacticLatitude->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->galacticLongitude->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
displaySolarFlux();
if (ui->chartSelect->currentIndex() == 1)
plotChart();
}
}
void StarTrackerGUI::on_dateTimeSelect_currentTextChanged(const QString &text)
{
if (text == "Now")
{
m_settings.m_dateTime = "";
ui->dateTime->setVisible(false);
}
else
{
m_settings.m_dateTime = ui->dateTime->dateTime().toString(Qt::ISODateWithMs);
ui->dateTime->setVisible(true);
}
applySettings();
plotChart();
}
void StarTrackerGUI::on_dateTime_dateTimeChanged(const QDateTime &datetime)
{
(void) datetime;
if (ui->dateTimeSelect->currentIndex() == 1)
{
m_settings.m_dateTime = ui->dateTime->dateTime().toString(Qt::ISODateWithMs);
applySettings();
plotChart();
}
}
void StarTrackerGUI::plotChart()
{
if (ui->chartSelect->currentIndex() == 0)
{
if (ui->chartSubSelect->currentIndex() == 0) {
plotElevationLineChart();
} else {
plotElevationPolarChart();
}
}
else if (ui->chartSelect->currentIndex() == 1)
{
plotSolarFluxChart();
}
else if (ui->chartSelect->currentIndex() == 2)
{
plotSkyTemperatureChart();
}
else if (ui->chartSelect->currentIndex() == 3)
{
plotGalacticLineOfSight();
}
}
void StarTrackerGUI::raDecChanged()
{
if (ui->chartSelect->currentIndex() == 2) {
plotSkyTemperatureChart();
} else if (ui->chartSelect->currentIndex() == 3) {
plotGalacticLineOfSight();
}
}
void StarTrackerGUI::on_frequency_valueChanged(int value)
{
m_settings.m_frequency = value*1000000.0;
applySettings();
if (ui->chartSelect->currentIndex() != 0)
{
updateChartSubSelect();
plotChart();
}
displaySolarFlux();
}
void StarTrackerGUI::on_beamwidth_valueChanged(double value)
{
m_settings.m_beamwidth = value;
applySettings();
updateChartSubSelect();
if (ui->chartSelect->currentIndex() == 2)
plotChart();
}
void StarTrackerGUI::plotSolarFluxChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(true);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
QChart *oldChart = m_solarFluxChart;
m_solarFluxChart = new QChart();
if (m_solarFluxesValid)
{
m_solarFluxChart->setTitle("");
m_solarFluxChart->legend()->hide();
m_solarFluxChart->layout()->setContentsMargins(0, 0, 0, 0);
m_solarFluxChart->setMargins(QMargins(1, 1, 1, 1));
m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
double maxValue = -std::numeric_limits<double>::infinity();
double minValue = std::numeric_limits<double>::infinity();
QLineSeries *series = new QLineSeries();
for (int i = 0; i < 8; i++)
{
double value = convertSolarFluxUnits(m_solarFluxes[i]);
series->append(m_solarFluxFrequencies[i], value);
maxValue = std::max(value, maxValue);
minValue = std::min(value, minValue);
}
series->setPointLabelsVisible(true);
series->setPointLabelsFormat("@yPoint");
series->setPointLabelsClipping(false);
m_solarFluxChart->addSeries(series);
QLogValueAxis *chartSolarFluxXAxis = new QLogValueAxis();
QValueAxis *chartSolarFluxYAxis = new QValueAxis();
chartSolarFluxXAxis->setTitleText(QString("Frequency (MHz)"));
chartSolarFluxXAxis->setMinorTickCount(-1);
chartSolarFluxYAxis->setTitleText(QString("Solar flux density (%1)").arg(solarFluxUnit()));
chartSolarFluxYAxis->setMinorTickCount(-1);
if (m_settings.m_solarFluxUnits == StarTrackerSettings::SFU)
{
chartSolarFluxYAxis->setLabelFormat("%d");
chartSolarFluxYAxis->setRange(0.0, ((((int)maxValue)+99)/100)*100);
}
else if (m_settings.m_solarFluxUnits == StarTrackerSettings::JANSKY)
{
chartSolarFluxYAxis->setLabelFormat("%.2g");
chartSolarFluxYAxis->setRange(0, ((((int)maxValue)+999999)/100000)*100000);
}
else
{
chartSolarFluxYAxis->setLabelFormat("%.2g");
// Bug in QtCharts for values < ~1e-12 https://bugreports.qt.io/browse/QTBUG-95304
// Set range to 0-1 here, then real range after axis have been attached
chartSolarFluxYAxis->setRange(0.0, 1.0);
}
m_solarFluxChart->addAxis(chartSolarFluxXAxis, Qt::AlignBottom);
m_solarFluxChart->addAxis(chartSolarFluxYAxis, Qt::AlignLeft);
series->attachAxis(chartSolarFluxXAxis);
series->attachAxis(chartSolarFluxYAxis);
if (m_settings.m_solarFluxUnits == StarTrackerSettings::WATTS_M_HZ) {
chartSolarFluxYAxis->setRange(minValue, maxValue);
}
}
else
m_solarFluxChart->setTitle("Press download Solar flux density data to view");
ui->chart->setChart(m_solarFluxChart);
delete oldChart;
}
QList<QLineSeries*> StarTrackerGUI::createDriftScan(bool galactic)
{
QList<QLineSeries *>list;
QLineSeries *series = new QLineSeries();
list.append(series);
QDateTime dt;
// Get date and time to calculate position at
if (m_settings.m_dateTime == "") {
dt = QDateTime::currentDateTime();
} else {
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
}
// Create a list of RA/Dec points of drift scan path
AzAlt aa;
aa.alt = m_settings.m_el;
aa.az = m_settings.m_az;
double prevX;
// Plot every 30min over a day
for (int i = 0; i <= 24*2; i++)
{
dt = dt.addSecs(30*60);
RADec rd = Astronomy::azAltToRaDec(aa, m_settings.m_latitude, m_settings.m_longitude, dt);
double x, y;
mapRaDec(rd.ra, rd.dec, galactic, x, y);
if (i == 0)
{
series->append(x, y);
}
else
{
// Check for crossing edge of chart
if (galactic)
{
if (((prevX < 90.0) && (x > 270.0)) || ((prevX > 270.0) && (x < 90.0)))
{
// Start new series, so we don't have lines crossing across the chart
series = new QLineSeries();
list.append(series);
}
}
series->append(x, y);
}
prevX = x;
}
return list;
}
void StarTrackerGUI::mapRaDec(double ra, double dec, bool galactic, double& x, double& y)
{
if (galactic)
{
// Convert to category coordinates
double l, b;
Astronomy::equatorialToGalactic(ra, dec, l, b);
// Map to linear axis
double lAxis;
if (l < 180.0) {
lAxis = 180.0 - l;
} else {
lAxis = 360.0 - l + 180.0;
}
x = lAxis;
y = b;
}
else
{
// Map to category axis
double raAxis;
if (ra <= 12.0) {
raAxis = 12.0 - ra;
} else {
raAxis = 24 - ra + 12;
}
x = raAxis;
y = dec;
}
}
// Is there a way to get this from the theme? Got these values from the source
QColor StarTrackerGUI::getSeriesColor(int series)
{
if (m_settings.m_chartsDarkTheme)
{
if (series == 0) {
return QColor(0x38ad6b);
} else if (series == 1) {
return QColor(0x3c84a7);
} else {
return QColor(0xeb8817);
}
}
else
{
if (series == 0) {
return QColor(0x209fdf);
} else if (series == 1) {
return QColor(0x99ca53);
} else {
return QColor(0xf6a625);
}
}
}
void StarTrackerGUI::createGalacticLineOfSightScene()
{
m_zoom = new GraphicsViewZoom(ui->image); // Deleted automatically when view is deleted
QGraphicsScene *scene = new QGraphicsScene(ui->image);
scene->setBackgroundBrush(QBrush(Qt::black));
// Milkyway images
for (int i = 0; i < m_milkyWayImages.size(); i++)
{
m_milkyWayItems.append(scene->addPixmap(m_milkyWayImages[i]));
m_milkyWayItems[i]->setPos(0, 0);
m_milkyWayItems[i]->setVisible(i == 0);
}
// Line of sight
QPen pen(QColor(255, 0, 0), 4, Qt::SolidLine);
m_lineOfSight = scene->addLine(511, 708, 511, 708, pen);
ui->image->setScene(scene);
ui->image->show();
}
void StarTrackerGUI::plotGalacticLineOfSight()
{
if (!ui->image->isVisible())
{
// Start zoomed out
ui->image->fitInView(m_milkyWayItems[0], Qt::KeepAspectRatio);
}
// Draw top-down image of Milky Way
ui->chart->setVisible(false);
ui->image->setVisible(true);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(false);
ui->zoomIn->setVisible(true);
ui->zoomOut->setVisible(true);
ui->addAnimationFrame->setVisible(true);
ui->clearAnimation->setVisible(true);
ui->saveAnimation->setVisible(true);
// Select which Milky Way image to show
int imageIdx = ui->chartSubSelect->currentIndex();
for (int i = 0; i < m_milkyWayItems.size(); i++) {
m_milkyWayItems[i]->setVisible(i == imageIdx);
}
// Calculate Galactic longitude we're observing
float ra = Astronomy::raToDecimal(m_settings.m_ra);
float dec = Astronomy::decToDecimal(m_settings.m_dec);
double l, b;
Astronomy::equatorialToGalactic(ra, dec, l, b);
//l = ui->azimuth->value(); // For testing, just use azimuth
// Calculate length of line, as Sun is not at centre
// we assume end point lies on an ellipse, with Sun at foci
// See https://en.wikipedia.org/wiki/Ellipse Polar form relative to focus
QPointF sun(511, 708); // Location of Sun on Milky Way image
float a = sun.x() - 112; // semi-major axis
float c = sun.y() - sun.x(); // linear eccentricity
float e = c / a; // eccentricity
float r = a * (1.0f - e*e) / (1.0f - e * cos(Units::degreesToRadians(l)));
// Draw line from Sun along observation galactic longitude
QTransform rotation = QTransform().translate(sun.x(), -sun.y()).rotate(l).translate(-sun.x(), sun.y()); // Flip Y
QPointF point = rotation.map(QPointF(511, -sun.y() + r));
m_lineOfSight->setLine(sun.x(), sun.y(), point.x(), -point.y());
}
void StarTrackerGUI::plotGalacticMarker(LoSMarker* marker)
{
QPointF sun(511, 708); // Location of Sun on Milky Way image
double pixelsPerKPC = 564.0/22.995; // 75,000ly = 23kpc
QTransform rotation = QTransform().translate(sun.x(), -sun.y()).rotate(marker->m_l).translate(-sun.x(), sun.y()); // Flip Y
QPointF point = rotation.map(QPointF(511, -sun.y() + pixelsPerKPC*marker->m_d));
marker->m_text->setPos(point.x(), -point.y());
}
void StarTrackerGUI::on_zoomIn_clicked()
{
m_zoom->gentleZoom(1.25);
}
void StarTrackerGUI::on_zoomOut_clicked()
{
m_zoom->gentleZoom(0.75);
}
void StarTrackerGUI::on_addAnimationFrame_clicked()
{
QImage image(ui->image->size(), QImage::Format_ARGB32);
image.fill(Qt::black);
QPainter painter(&image);
ui->image->render(&painter);
m_animationImages.append(image);
ui->saveAnimation->setEnabled(true);
ui->clearAnimation->setEnabled(true);
}
void StarTrackerGUI::on_clearAnimation_clicked()
{
m_animationImages.clear();
ui->saveAnimation->setEnabled(false);
ui->clearAnimation->setEnabled(false);
}
void StarTrackerGUI::on_saveAnimation_clicked()
{
// Get filename of animation file
QFileDialog fileDialog(nullptr, "Select file to save animation to", "", "*.png");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
APNG apng(m_animationImages.size());
for (int i = 0; i < m_animationImages.size(); i++) {
apng.addImage(m_animationImages[i]);
}
if (!apng.save(fileNames[0])) {
QMessageBox::critical(this, "Star Tracker", QString("Failed to write to file %1").arg(fileNames[0]));
}
}
}
}
void StarTrackerGUI::plotSkyTemperatureChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(true);
ui->drawMoon->setVisible(true);
ui->darkTheme->setVisible(false);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
bool galactic = (ui->chartSubSelect->currentIndex() & 1) == 1;
m_chart.removeAllSeries();
removeAllAxes();
// Plot drift scan path
QList<QLineSeries*> lineSeries;
if (m_settings.m_target == "Custom Az/El") {
lineSeries = createDriftScan(galactic);
QPen pen(getSeriesColor(1), 2, Qt::SolidLine);
for (int i = 0; i < lineSeries.length(); i++) {
lineSeries[i]->setPen(pen);
}
}
QScatterSeries *series = new QScatterSeries();
float ra = Astronomy::raToDecimal(m_settings.m_ra);
float dec = Astronomy::decToDecimal(m_settings.m_dec);
double beamWidth = m_settings.m_beamwidth;
// Ellipse not supported, so draw circle on shorter axis
double degPerPixelW = 360.0/m_chart.plotArea().width();
double degPerPixelH = 180.0/m_chart.plotArea().height();
double degPerPixel = std::min(degPerPixelW, degPerPixelH);
double markerSize;
double x, y;
mapRaDec(ra, dec, galactic, x, y);
series->append(x, y);
// Get temperature
int idx = ui->chartSubSelect->currentIndex();
if ((idx == 6) || (idx == 7))
{
double temp;
if (m_starTracker->calcSkyTemperature(m_settings.m_frequency, m_settings.m_beamwidth, ra, dec, temp))
{
series->setPointLabelsVisible(true);
series->setPointLabelsColor(Qt::red);
series->setPointLabelsFormat(QString("%1 K").arg(std::round(temp)));
// Scale marker size by beamwidth
markerSize = std::max((int)round(beamWidth * degPerPixel), 5);
}
}
else
{
// Read temperature from selected FITS file at target RA/Dec
QImage *img = &m_images[idx];
const FITS *fits = m_starTracker->getTempFITS(idx/2);
double x;
if (ra <= 12.0) {
x = (12.0 - ra) / 24.0;
} else {
x = (24 - ra + 12) / 24.0;
}
int imgX = x * (img->width() - 1);
if (imgX >= img->width()) {
imgX = img->width() - 1;
}
int imgY = (90.0-dec)/180.0 * (img->height() - 1);
if (imgY >= img->height()) {
imgY = img->height() - 1;
}
if (fits->valid())
{
double temp = fits->scaledValue(imgX, imgY);
series->setPointLabelsVisible(true);
series->setPointLabelsColor(Qt::red);
series->setPointLabelsFormat(QString("%1 K").arg(std::round(temp)));
}
else
{
qDebug() << "FITS not valid";
}
// Temperature from just one pixel, but need to make marker visbile
markerSize = 5;
}
series->setMarkerSize(markerSize);
series->setColor(getSeriesColor(0));
m_chart.setTitle("");
// We want scatter (for the beam) to be on top of line, but same color even when other series
for (int i = 0; i < lineSeries.length(); i++) {
m_chart.addSeries(lineSeries[i]);
}
// Draw Sun on chart if requested
QScatterSeries *sunSeries = nullptr;
if (m_settings.m_drawSunOnSkyTempChart)
{
sunSeries = new QScatterSeries();
mapRaDec(m_sunRA, m_sunDec, galactic, x, y);
sunSeries->append(x, y);
sunSeries->setMarkerSize((int)std::max(std::round(0.53 * degPerPixel), 5.0));
sunSeries->setColor(Qt::yellow);
sunSeries->setBorderColor(Qt::yellow);
if (m_settings.m_target != "Sun") // Avoid labels on top of each other
{
sunSeries->setPointLabelsVisible(true);
sunSeries->setPointLabelsColor(Qt::red);
sunSeries->setPointLabelsFormat("Sun");
}
m_chart.addSeries(sunSeries);
}
// Draw moon on chart if requested
QScatterSeries *moonSeries = nullptr;
if (m_settings.m_drawMoonOnSkyTempChart)
{
moonSeries = new QScatterSeries();
mapRaDec(m_moonRA, m_moonDec, galactic, x, y);
moonSeries->append(x, y);
moonSeries->setMarkerSize((int)std::max(std::round(0.53 * degPerPixel), 5.0));
moonSeries->setColor(qRgb(150, 150, 150));
moonSeries->setBorderColor(qRgb(150, 150, 150));
if (m_settings.m_target != "Moon") // Avoid labels on top of each other
{
moonSeries->setPointLabelsVisible(true);
moonSeries->setPointLabelsColor(Qt::red);
moonSeries->setPointLabelsFormat("Moon");
}
m_chart.addSeries(moonSeries);
}
m_chart.addSeries(series);
if (galactic)
{
m_chart.addAxis(&m_skyTempGalacticLXAxis, Qt::AlignBottom);
series->attachAxis(&m_skyTempGalacticLXAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempGalacticLXAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempGalacticLXAxis);
}
m_skyTempYAxis.setTitleText(QString("Galactic latitude (%1)").arg(QChar(0xb0)));
m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft);
series->attachAxis(&m_skyTempYAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempYAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempYAxis);
}
for (int i = 0; i < lineSeries.length(); i++)
{
lineSeries[i]->attachAxis(&m_skyTempGalacticLXAxis);
lineSeries[i]->attachAxis(&m_skyTempYAxis);
}
}
else
{
m_chart.addAxis(&m_skyTempRAXAxis, Qt::AlignBottom);
series->attachAxis(&m_skyTempRAXAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempRAXAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempRAXAxis);
}
m_skyTempYAxis.setTitleText(QString("Declination (%1)").arg(QChar(0xb0)));
m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft);
series->attachAxis(&m_skyTempYAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempYAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempYAxis);
}
for (int i = 0; i < lineSeries.length(); i++)
{
lineSeries[i]->attachAxis(&m_skyTempRAXAxis);
lineSeries[i]->attachAxis(&m_skyTempYAxis);
}
}
ui->chart->setChart(&m_chart);
plotAreaChanged(m_chart.plotArea());
}
void StarTrackerGUI::plotAreaChanged(const QRectF &plotArea)
{
int width = static_cast<int>(plotArea.width());
int height = static_cast<int>(plotArea.height());
int viewW = static_cast<int>(ui->chart->width());
int viewH = static_cast<int>(ui->chart->height());
// Scale the image to fit plot area
int imageIdx = ui->chartSubSelect->currentIndex();
if (imageIdx == -1) {
return;
} else if (imageIdx == 6) {
imageIdx = 2;
} else if (imageIdx == 7) {
imageIdx = 3;
}
QImage image = m_images[imageIdx].scaled(QSize(width, height), Qt::IgnoreAspectRatio);
QImage translated(viewW, viewH, QImage::Format_ARGB32);
translated.fill(Qt::white);
QPainter painter(&translated);
painter.drawImage(plotArea.topLeft(), image);
m_chart.setPlotAreaBackgroundBrush(translated);
m_chart.setPlotAreaBackgroundVisible(true);
}
void StarTrackerGUI::removeAllAxes()
{
QList<QAbstractAxis *> axes;
axes = m_chart.axes(Qt::Horizontal);
for (QAbstractAxis *axis : axes)
m_chart.removeAxis(axis);
axes = m_chart.axes(Qt::Vertical);
for (QAbstractAxis *axis : axes)
m_chart.removeAxis(axis);
}
// Plot target elevation angle over the day
void StarTrackerGUI::plotElevationLineChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(true);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
QChart *oldChart = m_azElLineChart;
m_azElLineChart = new QChart();
m_azElLineChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
QDateTimeAxis *xAxis = new QDateTimeAxis();
QValueAxis *yLeftAxis = new QValueAxis();
QValueAxis *yRightAxis = new QValueAxis();
m_azElLineChart->legend()->hide();
m_azElLineChart->layout()->setContentsMargins(0, 0, 0, 0);
m_azElLineChart->setMargins(QMargins(1, 1, 1, 1));
double maxElevation = -90.0;
QLineSeries *elSeries = new QLineSeries();
QList<QLineSeries *> azSeriesList;
QLineSeries *azSeries = new QLineSeries();
azSeriesList.append(azSeries);
QPen pen(getSeriesColor(1), 2, Qt::SolidLine);
azSeries->setPen(pen);
QDateTime dt;
if (m_settings.m_dateTime.isEmpty())
dt = QDateTime::currentDateTime();
else
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
dt.setTime(QTime(0,0));
QDateTime startTime = dt;
QDateTime endTime = dt;
double prevAz;
int timestep = 10*60;
for (int step = 0; step <= 24*60*60/timestep; step++)
{
AzAlt aa;
RADec rd;
// Calculate elevation of desired object
if (m_settings.m_target == "Sun")
Astronomy::sunPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt);
else if (m_settings.m_target == "Moon")
Astronomy::moonPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt);
else
{
rd.ra = Astronomy::raToDecimal(m_settings.m_ra);
rd.dec = Astronomy::decToDecimal(m_settings.m_dec);
aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow);
}
if (aa.alt > maxElevation)
maxElevation = aa.alt;
// Adjust for refraction
if (m_settings.m_refraction == "Positional Astronomy Library")
{
aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
else if (m_settings.m_refraction == "Saemundsson")
{
aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
if (step == 0)
prevAz = aa.az;
if (((prevAz >= 270) && (aa.az < 90)) || ((prevAz < 90) && (aa.az >= 270)))
{
azSeries = new QLineSeries();
azSeriesList.append(azSeries);
azSeries->setPen(pen);
}
elSeries->append(dt.toMSecsSinceEpoch(), aa.alt);
azSeries->append(dt.toMSecsSinceEpoch(), aa.az);
endTime = dt;
prevAz = aa.az;
dt = dt.addSecs(timestep); // addSecs accounts for daylight savings jumps
}
m_azElLineChart->addAxis(xAxis, Qt::AlignBottom);
m_azElLineChart->addAxis(yLeftAxis, Qt::AlignLeft);
m_azElLineChart->addAxis(yRightAxis, Qt::AlignRight);
m_azElLineChart->addSeries(elSeries);
for (int i = 0; i < azSeriesList.size(); i++)
{
m_azElLineChart->addSeries(azSeriesList[i]);
azSeriesList[i]->attachAxis(xAxis);
azSeriesList[i]->attachAxis(yRightAxis);
}
elSeries->attachAxis(xAxis);
elSeries->attachAxis(yLeftAxis);
xAxis->setTitleText(QString("%1 %2").arg(startTime.date().toString()).arg(startTime.timeZoneAbbreviation()));
xAxis->setFormat("hh");
xAxis->setTickCount(7);
xAxis->setRange(startTime, endTime);
yLeftAxis->setRange(0.0, 90.0);
yLeftAxis->setTitleText(QString("Elevation (%1)").arg(QChar(0xb0)));
yRightAxis->setRange(0.0, 360.0);
yRightAxis->setTitleText(QString("Azimuth (%1)").arg(QChar(0xb0)));
if (maxElevation < 0)
m_azElLineChart->setTitle("Not visible from this latitude");
else
m_azElLineChart->setTitle("");
ui->chart->setChart(m_azElLineChart);
delete oldChart;
}
// Plot target elevation angle over the day
void StarTrackerGUI::plotElevationPolarChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(true);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
QChart *oldChart = m_azElPolarChart;
m_azElPolarChart = new QPolarChart();
m_azElPolarChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
QValueAxis *angularAxis = new QValueAxis();
QCategoryAxis *radialAxis = new QCategoryAxis();
angularAxis->setTickCount(9);
angularAxis->setMinorTickCount(1);
angularAxis->setLabelFormat("%d");
angularAxis->setRange(0, 360);
radialAxis->setMin(0);
radialAxis->setMax(90);
radialAxis->append("90", 0);
radialAxis->append("60", 30);
radialAxis->append("30", 60);
radialAxis->append("0", 90);
radialAxis->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
m_azElPolarChart->addAxis(angularAxis, QPolarChart::PolarOrientationAngular);
m_azElPolarChart->addAxis(radialAxis, QPolarChart::PolarOrientationRadial);
m_azElPolarChart->legend()->hide();
m_azElPolarChart->layout()->setContentsMargins(0, 0, 0, 0);
m_azElPolarChart->setMargins(QMargins(1, 1, 1, 1));
double maxElevation = -90.0;
QLineSeries *polarSeries = new QLineSeries();
QDateTime dt;
if (m_settings.m_dateTime.isEmpty())
dt = QDateTime::currentDateTime();
else
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
dt.setTime(QTime(0,0));
QDateTime startTime = dt;
QDateTime endTime = dt;
QDateTime riseTime;
QDateTime setTime;
int riseIdx = -1;
int setIdx = -1;
int idx = 0;
int timestep = 10*60; // Rise/set times accurate to nearest 10 minutes
double prevAlt;
for (int step = 0; step <= 24*60*60/timestep; step++)
{
AzAlt aa;
RADec rd;
// Calculate elevation of desired object
if (m_settings.m_target == "Sun")
Astronomy::sunPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt);
else if (m_settings.m_target == "Moon")
Astronomy::moonPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt);
else
{
rd.ra = Astronomy::raToDecimal(m_settings.m_ra);
rd.dec = Astronomy::decToDecimal(m_settings.m_dec);
aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow);
}
if (aa.alt > maxElevation)
maxElevation = aa.alt;
// Adjust for refraction
if (m_settings.m_refraction == "Positional Astronomy Library")
{
aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
else if (m_settings.m_refraction == "Saemundsson")
{
aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
if (idx == 0)
prevAlt = aa.alt;
// We can have set before rise in a day, if the object starts > 0
if ((aa.alt >= 0.0) && (prevAlt < 0.0))
{
riseTime = dt;
riseIdx = idx;
}
if ((aa.alt < 0.0) && (prevAlt >= 0.0))
{
setTime = endTime;
setIdx = idx;
}
polarSeries->append(aa.az, 90 - aa.alt);
idx++;
endTime = dt;
prevAlt = aa.alt;
dt = dt.addSecs(timestep); // addSecs accounts for daylight savings jumps
}
// Polar charts can't handle points that are more than 180 degrees apart, so
// we need to split passes that cross from 359 -> 0 degrees (or the reverse)
QList<QLineSeries *> series;
series.append(new QLineSeries());
QLineSeries *s = series.first();
QPen pen(getSeriesColor(0), 2, Qt::SolidLine);
s->setPen(pen);
qreal prevAz = polarSeries->at(0).x();
qreal prevEl = polarSeries->at(0).y();
for (int i = 1; i < polarSeries->count(); i++)
{
qreal az = polarSeries->at(i).x();
qreal el = polarSeries->at(i).y();
if ((prevAz > 270.0) && (az <= 90.0))
{
double elMid = Interpolation::interpolate(prevAz, prevEl, az+360.0, el, 360.0);
s->append(360.0, elMid);
series.append(new QLineSeries());
s = series.last();
s->setPen(pen);
s->append(0.0, elMid);
s->append(az, el);
}
else if ((prevAz <= 90.0) && (az > 270.0))
{
double elMid = Interpolation::interpolate(prevAz, prevEl, az-360.0, el, 0.0);
s->append(0.0, elMid);
series.append(new QLineSeries());
s = series.last();
s->setPen(pen);
s->append(360.0, elMid);
s->append(az, el);
}
else
s->append(polarSeries->at(i));
prevAz = az;
prevEl = el;
}
for (int i = 0; i < series.length(); i++)
{
m_azElPolarChart->addSeries(series[i]);
series[i]->attachAxis(angularAxis);
series[i]->attachAxis(radialAxis);
}
// Create series with single point, so we can plot time of rising
if (riseTime.isValid())
{
QLineSeries *riseSeries = new QLineSeries();
riseSeries->append(polarSeries->at(riseIdx));
riseSeries->setPointLabelsFormat(QString("Rise %1").arg(riseTime.time().toString("hh:mm")));
riseSeries->setPointLabelsVisible(true);
riseSeries->setPointLabelsClipping(false);
m_azElPolarChart->addSeries(riseSeries);
riseSeries->attachAxis(angularAxis);
riseSeries->attachAxis(radialAxis);
}
// Create series with single point, so we can plot time of setting
if (setTime.isValid())
{
QLineSeries *setSeries = new QLineSeries();
setSeries->append(polarSeries->at(setIdx));
setSeries->setPointLabelsFormat(QString("Set %1").arg(setTime.time().toString("hh:mm")));
setSeries->setPointLabelsVisible(true);
setSeries->setPointLabelsClipping(false);
m_azElPolarChart->addSeries(setSeries);
setSeries->attachAxis(angularAxis);
setSeries->attachAxis(radialAxis);
}
if (maxElevation < 0)
m_azElPolarChart->setTitle("Not visible from this latitude");
else
m_azElPolarChart->setTitle("");
ui->chart->setChart(m_azElPolarChart);
delete polarSeries;
delete oldChart;
}
// Find target on the Map
void StarTrackerGUI::on_viewOnMap_clicked()
{
QString target = m_settings.m_target == "Sun" || m_settings.m_target == "Moon" ? m_settings.m_target : "Star";
FeatureWebAPIUtils::mapFind(target);
}
void StarTrackerGUI::updateChartSubSelect()
{
if (ui->chartSelect->currentIndex() == 2)
{
ui->chartSubSelect->setItemText(6, QString("%1 MHz %2%3 Equatorial")
.arg((int)std::round(m_settings.m_frequency/1e6))
.arg((int)std::round(m_settings.m_beamwidth))
.arg(QChar(0xb0)));
ui->chartSubSelect->setItemText(7, QString("%1 MHz %2%3 Galactic")
.arg((int)std::round(m_settings.m_frequency/1e6))
.arg((int)std::round(m_settings.m_beamwidth))
.arg(QChar(0xb0)));
}
}
void StarTrackerGUI::on_chartSelect_currentIndexChanged(int index)
{
bool oldState = ui->chartSubSelect->blockSignals(true);
ui->chartSubSelect->clear();
if (index == 0)
{
ui->chartSubSelect->addItem("Az/El vs time");
ui->chartSubSelect->addItem("Polar");
}
else if (index == 2)
{
ui->chartSubSelect->addItem(QString("150 MHz 5%1 Equatorial").arg(QChar(0xb0)));
ui->chartSubSelect->addItem(QString("150 MHz 5%1 Galactic").arg(QChar(0xb0)));
ui->chartSubSelect->addItem("408 MHz 51' Equatorial");
ui->chartSubSelect->addItem("408 MHz 51' Galactic");
ui->chartSubSelect->addItem("1420 MHz 35' Equatorial");
ui->chartSubSelect->addItem("1420 MHz 35' Galactic");
ui->chartSubSelect->addItem("Custom Equatorial");
ui->chartSubSelect->addItem("Custom Galactic");
ui->chartSubSelect->setCurrentIndex(2);
updateChartSubSelect();
}
else if (index == 3)
{
ui->chartSubSelect->addItem("Milky Way");
ui->chartSubSelect->addItem("Milky Way annotated");
}
ui->chartSubSelect->blockSignals(oldState);
plotChart();
}
void StarTrackerGUI::on_chartSubSelect_currentIndexChanged(int index)
{
(void) index;
plotChart();
}
double StarTrackerGUI::convertSolarFluxUnits(double sfu)
{
switch (m_settings.m_solarFluxUnits)
{
case StarTrackerSettings::SFU:
return sfu;
case StarTrackerSettings::JANSKY:
return Units::solarFluxUnitsToJansky(sfu);
case StarTrackerSettings::WATTS_M_HZ:
return Units::solarFluxUnitsToWattsPerMetrePerHertz(sfu);
}
return 0.0;
}
QString StarTrackerGUI::solarFluxUnit()
{
switch (m_settings.m_solarFluxUnits)
{
case StarTrackerSettings::SFU:
return "sfu";
case StarTrackerSettings::JANSKY:
return "Jy";
case StarTrackerSettings::WATTS_M_HZ:
return "Wm^-2Hz^-1";
}
return "";
}
// Calculate solar flux at given frequency in SFU
double StarTrackerGUI::calcSolarFlux(double freqMhz)
{
if (m_solarFluxesValid)
{
double solarFlux;
const int fluxes = sizeof(m_solarFluxFrequencies)/sizeof(*m_solarFluxFrequencies);
int i;
for (i = 0; i < fluxes; i++)
{
if (freqMhz < m_solarFluxFrequencies[i])
break;
}
if (i == 0)
{
solarFlux = Interpolation::extrapolate((double)m_solarFluxFrequencies[0], (double)m_solarFluxes[0],
(double)m_solarFluxFrequencies[1], (double)m_solarFluxes[1],
freqMhz
);
}
else if (i == fluxes)
{
solarFlux = Interpolation::extrapolate((double)m_solarFluxFrequencies[fluxes-2], (double)m_solarFluxes[fluxes-2],
(double)m_solarFluxFrequencies[fluxes-1], (double)m_solarFluxes[fluxes-1],
freqMhz
);
}
else
{
solarFlux = Interpolation::interpolate((double)m_solarFluxFrequencies[i-1], (double)m_solarFluxes[i-1],
(double)m_solarFluxFrequencies[i], (double)m_solarFluxes[i],
freqMhz
);
}
return solarFlux;
}
else
{
return 0.0;
}
}
void StarTrackerGUI::displaySolarFlux()
{
if (((m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800) && (m_solarFlux == 0.0))
|| ((m_settings.m_solarFluxData != StarTrackerSettings::DRAO_2800) && !m_solarFluxesValid))
{
ui->solarFlux->setText("");
}
else
{
double solarFlux;
double freqMhz = m_settings.m_frequency/1000000.0;
if (m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800)
{
solarFlux = m_solarFlux;
ui->solarFlux->setToolTip(QString("Solar flux density at 2800 MHz"));
}
else if (m_settings.m_solarFluxData == StarTrackerSettings::TARGET_FREQ)
{
solarFlux = calcSolarFlux(freqMhz);
ui->solarFlux->setToolTip(QString("Solar flux density interpolated to %1 MHz").arg(freqMhz));
}
else
{
int idx = m_settings.m_solarFluxData-StarTrackerSettings::L_245;
solarFlux = m_solarFluxes[idx];
ui->solarFlux->setToolTip(QString("Solar flux density at %1 MHz").arg(m_solarFluxFrequencies[idx]));
}
ui->solarFlux->setText(QString("%1 %2").arg(convertSolarFluxUnits(solarFlux)).arg(solarFluxUnit()));
ui->solarFlux->setCursorPosition(0);
// Calculate value at target frequency in Jy for Radio Astronomy plugin
m_starTracker->getInputMessageQueue()->push(StarTracker::MsgSetSolarFlux::create(Units::solarFluxUnitsToJansky(calcSolarFlux(freqMhz))));
}
}
bool StarTrackerGUI::readSolarFlux()
{
QFile file(getSolarFluxFilename());
QDateTime lastModified = file.fileTime(QFileDevice::FileModificationTime);
if (QDateTime::currentDateTime().secsTo(lastModified) >= -(60*60*24))
{
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QByteArray bytes = file.readLine();
QString string(bytes);
// HHMMSS 245 410 610 1415 2695 4995 8800 15400 Mhz
// 000000 000019 000027 000037 000056 000073 000116 000202 000514 sfu
// Occasionally, file will contain ////// in a column, presumably to indicate no data
QRegExp re("([0-9]{2})([0-9]{2})([0-9]{2}) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+)");
if (re.indexIn(string) != -1)
{
for (int i = 0; i < 8; i++)
m_solarFluxes[i] = re.capturedTexts()[i+4].toInt();
m_solarFluxesValid = true;
displaySolarFlux();
plotChart();
return true;
}
else
qDebug() << "StarTrackerGUI::readSolarFlux: No match for " << string;
}
}
else
qDebug() << "StarTrackerGUI::readSolarFlux: Solar flux data is more than 1 day old";
return false;
}
void StarTrackerGUI::networkManagerFinished(QNetworkReply *reply)
{
ui->solarFlux->setText(""); // Don't show obsolete data
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "StarTrackerGUI::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
QRegExp re("\\<th\\>Observed Flux Density\\<\\/th\\>\\<td\\>([0-9]+(\\.[0-9]+)?)\\<\\/td\\>");
if (re.indexIn(answer) != -1)
{
m_solarFlux = re.capturedTexts()[1].toDouble();
displaySolarFlux();
}
else
qDebug() << "StarTrackerGUI::networkManagerFinished - No Solar flux found: " << answer;
}
reply->deleteLater();
}
QString StarTrackerGUI::getSolarFluxFilename()
{
return HttpDownloadManager::downloadDir() + "/solar_flux.srd";
}
void StarTrackerGUI::updateSolarFlux(bool all)
{
qDebug() << "StarTrackerGUI: Updating Solar flux data";
if ((m_settings.m_solarFluxData != StarTrackerSettings::DRAO_2800) || all)
{
QDate today = QDateTime::currentDateTimeUtc().date();
QString solarFluxFile = getSolarFluxFilename();
if (m_dlm.confirmDownload(solarFluxFile, nullptr, 0))
{
QString urlString = QString("https://www.sws.bom.gov.au/Category/World Data Centre/Data Display and Download/Solar Radio/station/learmonth/SRD/%1/L%2.SRD")
.arg(today.year()).arg(today.toString("yyMMdd"));
m_dlm.download(QUrl(urlString), solarFluxFile, this);
}
}
if ((m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800) || all)
{
m_networkRequest.setUrl(QUrl("https://www.spaceweather.gc.ca/forecast-prevision/solar-solaire/solarflux/sx-4-en.php"));
m_networkManager->get(m_networkRequest);
}
}
void StarTrackerGUI::autoUpdateSolarFlux()
{
updateSolarFlux(false);
}
void StarTrackerGUI::on_downloadSolarFlux_clicked()
{
updateSolarFlux(true);
}
void StarTrackerGUI::on_darkTheme_clicked(bool checked)
{
m_settings.m_chartsDarkTheme = checked;
if (m_solarFluxChart) {
m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
}
m_chart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
plotChart();
applySettings();
}
void StarTrackerGUI::on_drawSun_clicked(bool checked)
{
m_settings.m_drawSunOnSkyTempChart = checked;
plotChart();
applySettings();
}
void StarTrackerGUI::on_drawMoon_clicked(bool checked)
{
m_settings.m_drawMoonOnSkyTempChart = checked;
plotChart();
applySettings();
}
void StarTrackerGUI::downloadFinished(const QString& filename, bool success)
{
(void) filename;
if (success)
readSolarFlux();
}
void StarTrackerGUI::makeUIConnections()
{
QObject::connect(ui->startStop, &ButtonSwitch::toggled, this, &StarTrackerGUI::on_startStop_toggled);
QObject::connect(ui->link, &ButtonSwitch::clicked, this, &StarTrackerGUI::on_link_clicked);
QObject::connect(ui->useMyPosition, &QToolButton::clicked, this, &StarTrackerGUI::on_useMyPosition_clicked);
QObject::connect(ui->latitude, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &StarTrackerGUI::on_latitude_valueChanged);
QObject::connect(ui->longitude, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &StarTrackerGUI::on_longitude_valueChanged);
QObject::connect(ui->rightAscension, &QLineEdit::editingFinished, this, &StarTrackerGUI::on_rightAscension_editingFinished);
QObject::connect(ui->declination, &QLineEdit::editingFinished, this, &StarTrackerGUI::on_declination_editingFinished);
QObject::connect(ui->azimuth, &DMSSpinBox::valueChanged, this, &StarTrackerGUI::on_azimuth_valueChanged);
QObject::connect(ui->elevation, &DMSSpinBox::valueChanged, this, &StarTrackerGUI::on_elevation_valueChanged);
QObject::connect(ui->azimuthOffset, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &StarTrackerGUI::on_azimuthOffset_valueChanged);
QObject::connect(ui->elevationOffset, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &StarTrackerGUI::on_elevationOffset_valueChanged);
QObject::connect(ui->galacticLatitude, &DMSSpinBox::valueChanged, this, &StarTrackerGUI::on_galacticLatitude_valueChanged);
QObject::connect(ui->galacticLongitude, &DMSSpinBox::valueChanged, this, &StarTrackerGUI::on_galacticLongitude_valueChanged);
QObject::connect(ui->frequency, qOverload<int>(&QSpinBox::valueChanged), this, &StarTrackerGUI::on_frequency_valueChanged);
QObject::connect(ui->beamwidth, qOverload<double>(&QDoubleSpinBox::valueChanged), this, &StarTrackerGUI::on_beamwidth_valueChanged);
QObject::connect(ui->target, &QComboBox::currentTextChanged, this, &StarTrackerGUI::on_target_currentTextChanged);
QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &StarTrackerGUI::on_displaySettings_clicked);
QObject::connect(ui->dateTimeSelect, &QComboBox::currentTextChanged, this, &StarTrackerGUI::on_dateTimeSelect_currentTextChanged);
QObject::connect(ui->dateTime, &WrappingDateTimeEdit::dateTimeChanged, this, &StarTrackerGUI::on_dateTime_dateTimeChanged);
QObject::connect(ui->viewOnMap, &QToolButton::clicked, this, &StarTrackerGUI::on_viewOnMap_clicked);
QObject::connect(ui->chartSelect, qOverload<int>(&QComboBox::currentIndexChanged), this, &StarTrackerGUI::on_chartSelect_currentIndexChanged);
QObject::connect(ui->chartSubSelect, qOverload<int>(&QComboBox::currentIndexChanged), this, &StarTrackerGUI::on_chartSubSelect_currentIndexChanged);
QObject::connect(ui->downloadSolarFlux, &QToolButton::clicked, this, &StarTrackerGUI::on_downloadSolarFlux_clicked);
QObject::connect(ui->darkTheme, &QToolButton::clicked, this, &StarTrackerGUI::on_darkTheme_clicked);
QObject::connect(ui->zoomIn, &QToolButton::clicked, this, &StarTrackerGUI::on_zoomIn_clicked);
QObject::connect(ui->zoomOut, &QToolButton::clicked, this, &StarTrackerGUI::on_zoomOut_clicked);
QObject::connect(ui->addAnimationFrame, &QToolButton::clicked, this, &StarTrackerGUI::on_addAnimationFrame_clicked);
QObject::connect(ui->clearAnimation, &QToolButton::clicked, this, &StarTrackerGUI::on_clearAnimation_clicked);
QObject::connect(ui->saveAnimation, &QToolButton::clicked, this, &StarTrackerGUI::on_saveAnimation_clicked);
QObject::connect(ui->drawSun, &QToolButton::clicked, this, &StarTrackerGUI::on_drawSun_clicked);
QObject::connect(ui->drawMoon, &QToolButton::clicked, this, &StarTrackerGUI::on_drawMoon_clicked);
}