diff --git a/doc/img/StarTracker_driftscan.png b/doc/img/StarTracker_driftscan.png new file mode 100644 index 000000000..6edce501a Binary files /dev/null and b/doc/img/StarTracker_driftscan.png differ diff --git a/doc/img/StarTracker_elevationvstime.png b/doc/img/StarTracker_elevationvstime.png index e692e9ecd..f85176188 100644 Binary files a/doc/img/StarTracker_elevationvstime.png and b/doc/img/StarTracker_elevationvstime.png differ diff --git a/doc/img/StarTracker_elevationvstime_polar.png b/doc/img/StarTracker_elevationvstime_polar.png new file mode 100644 index 000000000..fd23d8544 Binary files /dev/null and b/doc/img/StarTracker_elevationvstime_polar.png differ diff --git a/doc/img/StarTracker_solarflux.png b/doc/img/StarTracker_solarflux.png index b561e65ab..f3a49a948 100644 Binary files a/doc/img/StarTracker_solarflux.png and b/doc/img/StarTracker_solarflux.png differ diff --git a/plugins/feature/startracker/readme.md b/plugins/feature/startracker/readme.md index b1071ad7b..f563e9e13 100644 --- a/plugins/feature/startracker/readme.md +++ b/plugins/feature/startracker/readme.md @@ -4,6 +4,7 @@ The Star Tracker feature plugin is for use in radio astronomy and EME (Earth-Moon-Earth) communication. It calculates the azimuth and elevation of celestial objects and can send them to the Rotator Controller or other plugins to point an antenna at that object. +It can plot drift scan paths in both equatorial and galactic charts. The overhead position of the Sun, Moon and selected star can be displayed on the Map Feature. It can display local Sidereal time, solar flux density and sky temperature. The plugin can communicate with Stellarium, allowing Stellarium to control SDRangel as though it was a telescope and for the direction the antenna is pointing to be displayed in Stellarium. @@ -72,8 +73,8 @@ Displays the Solar flux density. The observatory where the data is sourced from,

11: Target

Select a target object to track from the list. -To manually enter RA (right ascension) and Dec (declination) of an unlisted target, select Custom. -To allow Stellarium to set the RA and Dec, select Custom, and ensure the Stellarium Server option is checked in the Star Tracker Settings dialog. +To manually enter RA (right ascension) and Dec (declination) of an unlisted target, select Custom RA/Dec. +To allow Stellarium to set the RA and Dec, select Custom RA/Dec, and ensure the Stellarium Server option is checked in the Star Tracker Settings dialog. | Target | Type | Details | Flux density (Jy) | |------------------|-------------------|------------------------------------------------|--------------------------------------------- @@ -86,7 +87,8 @@ To allow Stellarium to set the RA and Dec, select Custom, and ensure the Stellar | Cygnus A | Galaxy | First radio galaxy | 22k (50MHz), 11k (150MHz), 1579 (1.4GHz) | | Taurus A (M1) | Supernova/Pulsar | Crab Nebular | 2008 (50MHz), 1368 (150MHz), 829 (1.4GHz) | | Virgo A (M87) | Galaxy | | 2635 (50MHz), 1209 (150MHz), 212 (1.4GHz) | -| Custom | | Manually enter RA and Dec | | +| Custom RA/Dec | | Manually enter RA and Dec | | +| Custom Az/El | | Manually enter azimuth and elevation | | References: @@ -104,19 +106,27 @@ Enter the half power (-3dB) beamwidth of your antenna. This value is used for sk

14: Right Ascension

-When target is set to Custom, you can specify the right ascension in hours of the target object. This can be specified as a decimal (E.g. 12.23, from 0 to 24) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog. +When target is set to Custom RA/Dec, you can specify the right ascension in hours of the target object. This can be specified as a decimal (E.g. 12.23, from 0 to 24) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog. + +When target is set to Custom Az/El, this will display the corresponding right ascension.

15: Declination

-When target is set to Custom, you can specify the declination in degrees of the target object. This can be specified as a decimal (E.g. 34.6, from -90.0 to 90.0) or in degrees, minutes and seconds (E.g. 34d12m5.6s, 34d12'5.6" 34 12 5.6). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog. +When target is set to Custom RA/Dec, you can specify the declination in degrees of the target object. This can be specified as a decimal (E.g. 34.6, from -90.0 to 90.0) or in degrees, minutes and seconds (E.g. 34d12m5.6s, 34d12'5.6" 34 12 5.6). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog. + +When target is set to Custom Az/El, this will display the corresponding declination.

16: Azimuth

-Displays the calculated azimuth (angle in degrees, clockwise from North) to the object. +When target is set to Custom Az/El, you specify the azimuth in degrees of the target object. The corresponding RA/Dec will be calculated and displayed. + +For all other target settings, this displays the calculated azimuth (angle in degrees, clockwise from North) to the object.

17: Elevation

-Displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the object. +When target is set to Custom Az/El, you specify the elevation in degrees of the target object. The corresponding RA/Dec will be calculated and displayed. + +For all other target settings, this displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the object.

Plots

@@ -126,9 +136,10 @@ click on this icon ![Star Tracker Chart theme](../../../doc/img/StarTracker_char

Elevation vs time

-![Star Tracker Elevation vs Time](../../../doc/img/StarTracker_elevationvstime.png) +![Star Tracker Elevation vs Time](../../../doc/img/StarTracker_elevationvstime.png) ![Star Tracker Elevation vs Time Polar](../../../doc/img/StarTracker_elevationvstime_polar.png) In order to assist in determining whether and when observations of the target object may be possible, an elevation vs time plot is drawn for the 24 hours encompassing the selected date and time. +This can be plotted on Cartesian or polar axis. Some objects may not be visible from a particular latitude for the specified time, in which case, the graph title will indicate the object is not visible on that particular date.

Solar flux vs frequency

@@ -148,6 +159,17 @@ This temperature is therefore valid for a beamwidth of less than 1 degree. The Star Tracker plugin can also estimate a sky temperature based on the user entered observation frequency and beamwidth. To see this figure, which will be typically lower than the above, select one of the last two temperature maps from the right hand combo box. +

Drift scan path

+ +When the target (11) is set to Custom Az/El and the Sky temperature plot is displayed, a curve showing the drift scan path over a 24 hour period will be displayed. +This assumes the azimuth and elevation will be held constant and the path shows how the part of the sky the antenna will point to as the Earth rotates. + +![Drift scan path](../../../doc/img/StarTracker_driftscan.png) + +To setup a drift scan through a particular target object, first set the target (11) to that object. This will set the azimuth and elevation to point at the object. +You may want to set the Time (8) to Custom and a few hours in the future, so that the elevation is at a maximum when pointing at the target. +Then switch the target to Custom Az/El and Time back to Now, and the drift scan path that sweeps through the object will displayed. +

Map

The Star Tracker feature can send the overhead position of the Sun, Moon and target Star to the Map. These can be enabled individually in the settings dialog. The Moon should be displayed with an approximate phase. Stars (or galaxies) are displayed as an image of a pulsar. @@ -160,7 +182,7 @@ When using the Find feature in the Map GUI, you can search for "Sun", "Moon" or In Star Tracker: -* Set target to Custom +* Set target to Custom RA/Dec * Press Show settings dialog and ensure Stellarium server is checked * Press Start @@ -186,7 +208,7 @@ Then select the SDRangel telescope reticle and press Ocular view.

Attribution

-Solar radio flux measurement at 10.7cm/2800MHz is from National Research Council Canada and Natural Resources Canada: https://www.spaceweather.gc.ca/solarflux/sx-4-en.php +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 diff --git a/plugins/feature/startracker/startracker.cpp b/plugins/feature/startracker/startracker.cpp index 7714f74ec..78b3f9bea 100644 --- a/plugins/feature/startracker/startracker.cpp +++ b/plugins/feature/startracker/startracker.cpp @@ -141,6 +141,8 @@ void StarTracker::applySettings(const StarTrackerSettings& settings, bool force) << " m_target: " << settings.m_target << " m_ra: " << settings.m_ra << " m_dec: " << settings.m_dec + << " m_az: " << settings.m_az + << " m_el: " << settings.m_el << " m_latitude: " << settings.m_latitude << " m_longitude: " << settings.m_longitude << " m_serverPort: " << settings.m_serverPort diff --git a/plugins/feature/startracker/startrackergui.cpp b/plugins/feature/startracker/startrackergui.cpp index 44853ff5b..9a29ceb4e 100644 --- a/plugins/feature/startracker/startrackergui.cpp +++ b/plugins/feature/startracker/startrackergui.cpp @@ -93,18 +93,6 @@ bool StarTrackerGUI::deserialize(const QByteArray& data) } } -QString StarTrackerGUI::convertDegreesToText(double degrees) -{ - if (m_settings.m_azElUnits == StarTrackerSettings::DMS) - return Units::decimalDegreesToDegreeMinutesAndSeconds(degrees); - else if (m_settings.m_azElUnits == StarTrackerSettings::DM) - return Units::decimalDegreesToDegreesAndMinutes(degrees); - else if (m_settings.m_azElUnits == StarTrackerSettings::D) - return Units::decimalDegreesToDegrees(degrees); - else - return QString("%1").arg(degrees, 0, 'f', 2); -} - bool StarTrackerGUI::handleMessage(const Message& message) { if (StarTracker::MsgConfigureStarTracker::match(message)) @@ -121,8 +109,8 @@ bool StarTrackerGUI::handleMessage(const Message& message) else if (StarTrackerReport::MsgReportAzAl::match(message)) { StarTrackerReport::MsgReportAzAl& azAl = (StarTrackerReport::MsgReportAzAl&) message; - ui->azimuth->setText(convertDegreesToText(azAl.getAzimuth())); - ui->elevation->setText(convertDegreesToText(azAl.getElevation())); + ui->azimuth->setValue(azAl.getAzimuth()); + ui->elevation->setValue(azAl.getElevation()); return true; } else if (StarTrackerReport::MsgReportRADec::match(message)) @@ -197,6 +185,10 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, 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); + // Intialise chart m_chart.legend()->hide(); ui->chart->setChart(&m_chart); @@ -310,11 +302,18 @@ void StarTrackerGUI::displaySettings() ui->latitude->setValue(m_settings.m_latitude); ui->longitude->setValue(m_settings.m_longitude); ui->target->setCurrentIndex(ui->target->findText(m_settings.m_target)); - if (m_settings.m_target == "Custom") + ui->azimuth->setUnits(m_settings.m_azElUnits); + ui->elevation->setUnits(m_settings.m_azElUnits); + 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); + } if (m_settings.m_dateTime == "") { ui->dateTimeSelect->setCurrentIndex(0); @@ -413,6 +412,20 @@ void StarTrackerGUI::on_declination_editingFinished() 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::updateForTarget() { if (m_settings.m_target == "Sun") @@ -429,7 +442,7 @@ void StarTrackerGUI::updateForTarget() ui->rightAscension->setText(""); ui->declination->setText(""); } - else if (m_settings.m_target == "Custom") + else if (m_settings.m_target == "Custom RA/Dec") { ui->rightAscension->setReadOnly(false); ui->declination->setReadOnly(false); @@ -476,9 +489,21 @@ void StarTrackerGUI::updateForTarget() on_rightAscension_editingFinished(); on_declination_editingFinished(); } - // Clear as no longer valid when target has changed - ui->azimuth->setText(""); - ui->elevation->setText(""); + 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) @@ -572,6 +597,8 @@ void StarTrackerGUI::on_displaySettings_clicked() if (dialog.exec() == QDialog::Accepted) { applySettings(); + ui->elevation->setUnits(m_settings.m_azElUnits); + ui->azimuth->setUnits(m_settings.m_azElUnits); displaySolarFlux(); if (ui->chartSelect->currentIndex() == 1) plotChart(); @@ -691,6 +718,116 @@ void StarTrackerGUI::plotSolarFluxChart() // disconnect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF))); } +QList StarTrackerGUI::createDriftScan(bool galactic) +{ + QListlist; + 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, prevY; + // 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; + prevY = y; + } + + 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::plotSkyTemperatureChart() { bool galactic = (ui->chartSubSelect->currentIndex() & 1) == 1; @@ -698,6 +835,16 @@ void StarTrackerGUI::plotSkyTemperatureChart() m_chart.removeAllSeries(); removeAllAxes(); + // Plot drift scan path + QList 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); @@ -709,30 +856,9 @@ void StarTrackerGUI::plotSkyTemperatureChart() double degPerPixel = std::min(degPerPixelW, degPerPixelH); double markerSize; - 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; - series->append(lAxis, b); - } - else - { - // Map to category axis - double raAxis; - if (ra <= 12.0) - raAxis = 12.0 - ra; - else - raAxis = 24 - ra + 12; - series->append(raAxis, dec); - } + double x, y; + mapRaDec(ra, dec, galactic, x, y); + series->append(x, y); // Get temperature int idx = ui->chartSubSelect->currentIndex(); @@ -757,10 +883,12 @@ void StarTrackerGUI::plotSkyTemperatureChart() double degreesPerPixelV = abs(fits->degreesPerPixelV()); int numberOfCoeffsH = ceil(beamwidth/degreesPerPixelH); int numberOfCoeffsV = ceil(beamwidth/degreesPerPixelV); - if ((numberOfCoeffsH & 1) == 0) + if ((numberOfCoeffsH & 1) == 0) { numberOfCoeffsH++; - if ((numberOfCoeffsV & 1) == 0) + } + if ((numberOfCoeffsV & 1) == 0) { numberOfCoeffsV++; + } double *beam = new double[numberOfCoeffsH*numberOfCoeffsV]; double sum = 0.0; int y0 = numberOfCoeffsV/2; @@ -780,16 +908,19 @@ void StarTrackerGUI::plotSkyTemperatureChart() nonZeroCount++; } else + { beam[y*numberOfCoeffsH+x] = 0.0; + } } } // Get centre pixel coordinates double centreX; - if (ra <= 12.0) + if (ra <= 12.0) { centreX = (12.0 - ra) / 24.0; - else + } else { centreX = (24 - ra + 12) / 24.0; + } double centreY = (90.0-dec) / 180.0; int imgX = centreX * fits->width(); int imgY = centreY * fits->height(); @@ -831,12 +962,13 @@ void StarTrackerGUI::plotSkyTemperatureChart() else { // See https://arxiv.org/abs/1812.10084 fig 2 - if (m_settings.m_frequency < 200e6) + if (m_settings.m_frequency < 200e6) { spectralIndex = 2.55; - else if (m_settings.m_frequency < 20e9) + } else if (m_settings.m_frequency < 20e9) { spectralIndex = 2.695; - else + } else { spectralIndex = 3.1; + } } double galactic480 = temp408 - cmbT - iso408; double galacticT = galactic480 * pow(408e6/m_settings.m_frequency, spectralIndex); // Scale galactic contribution by frequency @@ -860,16 +992,19 @@ void StarTrackerGUI::plotSkyTemperatureChart() QImage *img = &m_images[idx]; FITS *fits = &m_temps[idx/2]; double x; - if (ra <= 12.0) + if (ra <= 12.0) { x = (12.0 - ra) / 24.0; - else + } else { x = (24 - ra + 12) / 24.0; - int imgX = x * img->width(); - if (imgX >= img->width()) - imgX = img->width(); - int imgY = (90.0-dec)/180.0 * img->height(); - if (imgY >= img->height()) - imgY = img->height(); + } + 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()) { @@ -883,8 +1018,13 @@ void StarTrackerGUI::plotSkyTemperatureChart() markerSize = 5; } series->setMarkerSize(markerSize); + series->setColor(getSeriesColor(0)); m_chart.setTitle(""); + // We want scatter to be on top of line, but same color even when no drift line + for (int i = 0; i < lineSeries.length(); i++) { + m_chart.addSeries(lineSeries[i]); + } m_chart.addSeries(series); if (galactic) { @@ -894,6 +1034,12 @@ void StarTrackerGUI::plotSkyTemperatureChart() m_skyTempYAxis.setTitleText(QString("Galactic latitude (%1)").arg(QChar(0xb0))); m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft); series->attachAxis(&m_skyTempYAxis); + + for (int i = 0; i < lineSeries.length(); i++) + { + lineSeries[i]->attachAxis(&m_skyTempGalacticLXAxis); + lineSeries[i]->attachAxis(&m_skyTempYAxis); + } } else { @@ -903,6 +1049,12 @@ void StarTrackerGUI::plotSkyTemperatureChart() m_skyTempYAxis.setTitleText(QString("Declination (%1)").arg(QChar(0xb0))); m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft); series->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()); @@ -965,7 +1117,7 @@ void StarTrackerGUI::plotElevationLineChart() QList azSeriesList; QLineSeries *azSeries = new QLineSeries(); azSeriesList.append(azSeries); - QPen pen(QColor(153, 202, 83), 2, Qt::SolidLine); + QPen pen(getSeriesColor(1), 2, Qt::SolidLine); azSeries->setPen(pen); QDateTime dt; @@ -1172,7 +1324,7 @@ void StarTrackerGUI::plotElevationPolarChart() QList series; series.append(new QLineSeries()); QLineSeries *s = series.first(); - QPen pen(QColor(32, 159, 223), 2, Qt::SolidLine); + QPen pen(getSeriesColor(0), 2, Qt::SolidLine); s->setPen(pen); qreal prevAz = polarSeries->at(0).x(); @@ -1467,7 +1619,7 @@ void StarTrackerGUI::updateSolarFlux(bool all) } if ((m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800) || all) { - m_networkRequest.setUrl(QUrl("https://www.spaceweather.gc.ca/solarflux/sx-4-en.php")); + m_networkRequest.setUrl(QUrl("https://www.spaceweather.gc.ca/forecast-prevision/solar-solaire/solarflux/sx-4-en.php")); m_networkManager->get(m_networkRequest); } } diff --git a/plugins/feature/startracker/startrackergui.h b/plugins/feature/startracker/startrackergui.h index 4830fa145..394d58e98 100644 --- a/plugins/feature/startracker/startrackergui.h +++ b/plugins/feature/startracker/startrackergui.h @@ -102,9 +102,11 @@ private: void applySettings(bool force = false); void displaySettings(); void updateForTarget(); - QString convertDegreesToText(double degrees); bool handleMessage(const Message& message); void updateLST(); + void mapRaDec(double ra, double dec, bool galactic, double& x, double& y); + QList createDriftScan(bool galactic); + QColor getSeriesColor(int series); void plotElevationLineChart(); void plotElevationPolarChart(); void plotSkyTemperatureChart(); @@ -133,6 +135,8 @@ private slots: void on_longitude_valueChanged(double value); void on_rightAscension_editingFinished(); void on_declination_editingFinished(); + void on_azimuth_valueChanged(double value); + void on_elevation_valueChanged(double value); void on_frequency_valueChanged(int value); void on_beamwidth_valueChanged(double value); void on_target_currentTextChanged(const QString &text); diff --git a/plugins/feature/startracker/startrackergui.ui b/plugins/feature/startracker/startrackergui.ui index 53dc1c121..40c0bf88c 100644 --- a/plugins/feature/startracker/startrackergui.ui +++ b/plugins/feature/startracker/startrackergui.ui @@ -67,13 +67,17 @@ - - - - Local sidereal time for selected date, time and longitude + + + + Beamwidth - - true + + + + + + Time @@ -84,86 +88,6 @@ - - - - Select time to calculate target's position at - - - - Now - - - - - Custom - - - - - - - - Frequency - - - - - - - Date and time to use when calculating target's position - - - dd/MM/yyyy HH:mm:ss - - - true - - - - - - - Declination of the target object - -This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and seconds (E.g. 34d12m10.2s, 34d12'10.2" 34 12 10.2) - - - -90d59'59.59" - - - - - - - Right Ascension of the target object. - -This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2) - - - 23h59m59.59s - - - - - - - Longitude in decimal degress (East positive) of observation point / antenna location - - - 6 - - - -180.000000000000000 - - - 180.000000000000000 - - - -180.000000000000000 - - - @@ -177,40 +101,6 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds - - - - LST - - - - - - - Longitude - - - - - - - Target - - - - - - - Computed azimuth in degrees to the target from the observation point - - - 360 - - - true - - - @@ -218,95 +108,26 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds - - + + - Solar Flux + RA - - - - - - - 110 - 0 - - - - Target object - - - 0 - - - - Sun - - - - - Moon - - - - - PSR B0329+54 - - - - - PSR B0833-45 - - - - - Sagittarius A - - - - - Cassiopeia A - - - - - Cygnus A - - - - - Taurus A (M1) - - - - - Virgo A (M87) - - - - - Custom - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - + + + + LST + + + + + + + Elevation + + @@ -396,80 +217,32 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds - - - - Latitude in decimal degrees (North positive) of observation point / antenna location - - - 6 - - - -90.000000000000000 - - - 90.000000000000000 - - - -90.000000000000000 - - - - - - - Time - - - - - - - Elevation - - - - - - - Beamwidth - - - - - - - RA - - - - + - Computed elevation in degrees to the target from the observation point + Elevation in degrees to the target from the observation point - - 90 + + + + + + Local sidereal time for selected date, time and longitude true - - + + - Solar flux density + Declination of the target object + +This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and seconds (E.g. 34d12m10.2s, 34d12'10.2" 34 12 10.2) - - true - - - - - - Latitude + -90d59'59.59" @@ -492,6 +265,226 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds + + + + + + + 110 + 0 + + + + Target object + + + 0 + + + + Sun + + + + + Moon + + + + + PSR B0329+54 + + + + + PSR B0833-45 + + + + + Sagittarius A + + + + + Cassiopeia A + + + + + Cygnus A + + + + + Taurus A (M1) + + + + + Virgo A (M87) + + + + + Custom RA/Dec + + + + + Custom Az/El + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Select time to calculate target's position at + + + + Now + + + + + Custom + + + + + + + + Latitude + + + + + + + Frequency + + + + + + + Date and time to use when calculating target's position + + + dd/MM/yyyy HH:mm:ss + + + true + + + + + + + Solar flux density + + + true + + + + + + + Target + + + + + + + Right Ascension of the target object. + +This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2) + + + 23h59m59.59s + + + + + + + Longitude + + + + + + + Longitude in decimal degress (East positive) of observation point / antenna location + + + 6 + + + -180.000000000000000 + + + 180.000000000000000 + + + -180.000000000000000 + + + + + + + Solar Flux + + + + + + + Latitude in decimal degrees (North positive) of observation point / antenna location + + + 6 + + + -90.000000000000000 + + + 90.000000000000000 + + + -90.000000000000000 + + + + + + + Azimuth in degrees to the target from the observation point + + + @@ -619,6 +612,11 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
gui/wrappingdatetimeedit.h
1 + + DMSSpinBox + QWidget +
gui/dmsspinbox.h
+
startStop @@ -637,10 +635,9 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds beamwidth rightAscension declination - azimuth - elevation chartSelect chartSubSelect + darkTheme chart diff --git a/plugins/feature/startracker/startrackerreport.h b/plugins/feature/startracker/startrackerreport.h index 0cace5aed..843feb4af 100644 --- a/plugins/feature/startracker/startrackerreport.h +++ b/plugins/feature/startracker/startrackerreport.h @@ -20,8 +20,10 @@ #define INCLUDE_FEATURE_STARTRACKERREPORT_H_ #include +#include #include "util/message.h" +#include "util/astronomy.h" class StarTrackerReport : public QObject { diff --git a/plugins/feature/startracker/startrackersettings.cpp b/plugins/feature/startracker/startrackersettings.cpp index 884f38b6d..75ec6f1a9 100644 --- a/plugins/feature/startracker/startrackersettings.cpp +++ b/plugins/feature/startracker/startrackersettings.cpp @@ -46,7 +46,7 @@ void StarTrackerSettings::resetToDefaults() m_beamwidth = 25.0; m_enableServer = true; m_serverPort = 10001; - m_azElUnits = DM; + m_azElUnits = DMSSpinBox::DM; m_solarFluxData = DRAO_2800; m_solarFluxUnits = SFU; m_updatePeriod = 1.0; @@ -62,6 +62,8 @@ void StarTrackerSettings::resetToDefaults() m_reverseAPIPort = 8888; m_reverseAPIFeatureSetIndex = 0; m_reverseAPIFeatureIndex = 0; + m_az = 0.0; + m_el = 0.0; } QByteArray StarTrackerSettings::serialize() const @@ -100,6 +102,8 @@ QByteArray StarTrackerSettings::serialize() const s.writeDouble(30, m_beamwidth); s.writeU32(31, m_solarFluxData); s.writeBool(32, m_chartsDarkTheme); + s.writeDouble(33, m_az); + s.writeDouble(34, m_el); return s.final(); } @@ -133,7 +137,7 @@ bool StarTrackerSettings::deserialize(const QByteArray& data) } else { m_serverPort = 10001; } - d.readS32(9, (qint32 *)&m_azElUnits, DM); + d.readS32(9, (qint32 *)&m_azElUnits, DMSSpinBox::DM); d.readFloat(10, &m_updatePeriod, 1.0f); d.readBool(11, &m_jnow, false); d.readString(12, &m_refraction, "Positional Astronomy Library"); @@ -169,6 +173,9 @@ bool StarTrackerSettings::deserialize(const QByteArray& data) d.readU32(31, (quint32 *)&m_solarFluxData, DRAO_2800); d.readBool(32, &m_chartsDarkTheme, true); + d.readDouble(33, &m_az, 0.0); + d.readDouble(34, &m_el, 0.0); + return true; } else diff --git a/plugins/feature/startracker/startrackersettings.h b/plugins/feature/startracker/startrackersettings.h index 777191e28..63b6c21a2 100644 --- a/plugins/feature/startracker/startrackersettings.h +++ b/plugins/feature/startracker/startrackersettings.h @@ -23,6 +23,7 @@ #include #include "util/message.h" +#include "gui/dmsspinbox.h" class Serializable; @@ -44,7 +45,7 @@ struct StarTrackerSettings double m_beamwidth; // Beamwidth in degrees uint16_t m_serverPort; bool m_enableServer; // Enable Stellarium server - enum AzElUnits {DMS, DM, D, Decimal} m_azElUnits; + enum DMSSpinBox::DisplayUnits m_azElUnits; enum SolarFluxData {DRAO_2800, L_245, L_410, L_610, L_1415, L2695, L_4995, L_8800, L_15400, TARGET_FREQ} m_solarFluxData; // What Solar flux density data to display enum SolarFluxUnits {SFU, JANSKY, WATTS_M_HZ} m_solarFluxUnits; float m_updatePeriod; @@ -53,7 +54,6 @@ struct StarTrackerSettings bool m_drawMoonOnMap; bool m_drawStarOnMap; bool m_chartsDarkTheme; // Dark theme for charts - QString m_title; quint32 m_rgbColor; bool m_useReverseAPI; @@ -61,6 +61,8 @@ struct StarTrackerSettings uint16_t m_reverseAPIPort; uint16_t m_reverseAPIFeatureSetIndex; uint16_t m_reverseAPIFeatureIndex; + double m_az; // Azimuth for Custom Az/El + double m_el; // Elevation for Custom Az/El StarTrackerSettings(); void resetToDefaults(); diff --git a/plugins/feature/startracker/startrackersettingsdialog.cpp b/plugins/feature/startracker/startrackersettingsdialog.cpp index 33bd94519..a47adad17 100644 --- a/plugins/feature/startracker/startrackersettingsdialog.cpp +++ b/plugins/feature/startracker/startrackersettingsdialog.cpp @@ -51,7 +51,7 @@ StarTrackerSettingsDialog::~StarTrackerSettingsDialog() void StarTrackerSettingsDialog::accept() { m_settings->m_jnow = ui->epoch->currentIndex() == 1; - m_settings->m_azElUnits = (StarTrackerSettings::AzElUnits)ui->azElUnits->currentIndex(); + m_settings->m_azElUnits = (DMSSpinBox::DisplayUnits)ui->azElUnits->currentIndex(); m_settings->m_updatePeriod = ui->updatePeriod->value(); m_settings->m_serverPort = (uint16_t)ui->serverPort->value(); m_settings->m_enableServer = ui->enableServer->isChecked(); diff --git a/plugins/feature/startracker/startrackerworker.cpp b/plugins/feature/startracker/startrackerworker.cpp index 631cdf5a7..333386e2b 100644 --- a/plugins/feature/startracker/startrackerworker.cpp +++ b/plugins/feature/startracker/startrackerworker.cpp @@ -463,6 +463,13 @@ void StarTrackerWorker::update() rd = moonRD; aa = moonAA; } + else if (m_settings.m_target == "Custom Az/El") + { + // Convert Alt/Az to RA/Dec + aa.alt = m_settings.m_el; + aa.az = m_settings.m_az; + rd = Astronomy::azAltToRaDec(aa, m_settings.m_latitude, m_settings.m_longitude, dt); + } else { // Convert RA/Dec to Alt/Az @@ -491,8 +498,11 @@ void StarTrackerWorker::update() // Send to GUI if (getMessageQueueToGUI()) { - StarTrackerReport::MsgReportAzAl *msg = StarTrackerReport::MsgReportAzAl::create(aa.az, aa.alt); - getMessageQueueToGUI()->push(msg); + if (m_settings.m_target == "Custom Az/El") { + getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rd.ra, rd.dec)); + } else { + getMessageQueueToGUI()->push(StarTrackerReport::MsgReportAzAl::create(aa.az, aa.alt)); + } } // Send Az/El to Rotator Controllers @@ -542,7 +552,7 @@ void StarTrackerWorker::update() { double starLongitude = Astronomy::lstAndRAToLongitude(lst, rd.ra); double starLatitude = rd.dec; - QString text = m_settings.m_target == "Custom" ? "Star" : m_settings.m_target; + QString text = m_settings.m_target.startsWith("Custom") ? "Star" : m_settings.m_target; sendToMap(mapMessageQueues, "Star", "qrc:///startracker/startracker/pulsar-32.png", text, starLatitude, starLongitude); } } diff --git a/sdrbase/util/astronomy.cpp b/sdrbase/util/astronomy.cpp index d48e0951a..ee7b941d2 100644 --- a/sdrbase/util/astronomy.cpp +++ b/sdrbase/util/astronomy.cpp @@ -216,6 +216,47 @@ AzAlt Astronomy::raDecToAzAlt(RADec rd, double latitude, double longitude, QDate return aa; } +// Convert from altitude and azimuth, for location (decimal degrees) and time, to Jnow right ascension (decimal hours) and declination (decimal degrees) +// See: http://jonvoisey.net/blog/2018/07/data-converting-alt-az-to-ra-dec-example/ +RADec Astronomy::azAltToRaDec(AzAlt aa, double latitude, double longitude, QDateTime dt) +{ + RADec rd; + double lst_deg; // Local sidereal time + double ha_rad, ha_deg; // Hour angle + double alt_rad, az_rad, lat_rad, dec_rad; // Corresponding variables as radians + + // Calculate local mean sidereal time (LMST) in degrees (see raDecToAzAlt) + lst_deg = Astronomy::localSiderealTime(dt, longitude); + + // Convert degrees to radians + alt_rad = Units::degreesToRadians(aa.alt); + az_rad = Units::degreesToRadians(aa.az); + lat_rad = Units::degreesToRadians(latitude); + + // Calculate declination + dec_rad = asin(sin(lat_rad)*sin(alt_rad)+cos(lat_rad)*cos(alt_rad)*cos(az_rad)); + + // Calculate hour angle + double quotient = (sin(alt_rad)-sin(lat_rad)*sin(dec_rad))/(cos(lat_rad)*cos(dec_rad)); + // At extreme altitudes, we seem to get small numerical errors that causes values to be out of range, so clip to [-1,1] + if (quotient < -1.0) { + ha_rad = acos(-1.0); + } else if (quotient > 1.0) { + ha_rad = acos(1.0); + } else { + ha_rad = acos(quotient); + } + + // Convert radians to degrees + rd.dec = Units::radiansToDegrees(dec_rad); + ha_deg = Units::radiansToDegrees(ha_rad); + + // Calculate right ascension in decimal hours + rd.ra = modulo((lst_deg - ha_deg) / (360.0/24.0), 24.0); + + return rd; +} + // Needs to work for negative a double Astronomy::modulo(double a, double b) { @@ -493,10 +534,11 @@ double Astronomy::lstAndRAToLongitude(double lst, double raHours) } // Return right ascension and declination of North Galactic Pole in J2000 Epoch +// http://www.lsc-group.phys.uwm.edu/lal/lsd/node1777.html void Astronomy::northGalacticPoleJ2000(double& ra, double& dec) { - ra = 12 + 51.4 / 60.0; - dec = 27.13; + ra = 192.8594813/15.0; + dec = 27.1282511; } // Convert from equatorial to Galactic coordinates, J2000 Epoch @@ -511,40 +553,24 @@ void Astronomy::equatorialToGalactic(double ra, double dec, double& l, double& b const double ngpRaRad = Units::degreesToRadians(ngpRa * 15.0); const double ngpDecRad = Units::degreesToRadians(ngpDec); - // Calculate galactic latitude for North Celestial pole, J2000 - const double ncpLRad = Units::degreesToRadians(122.93192); - // Calculate galactic longitude in radians double bRad = asin(sin(ngpDecRad)*sin(decRad)+cos(ngpDecRad)*cos(decRad)*cos(raRad - ngpRaRad)); - // Find the two possible solutions for galactic longitude in radians - double rhs1 = cos(decRad)*sin(raRad-ngpRaRad)/cos(bRad); - double rhs2 = (-cos(decRad)*sin(ngpDecRad)*cos(raRad-ngpRaRad)+sin(decRad)*cos(ngpDecRad))/cos(bRad); - double l1 = ncpLRad - asin(rhs1); - double l2 = ncpLRad - acos(rhs2); + // Calculate galactic latitiude in radians + double lRad = atan2(sin(decRad)-sin(bRad)*sin(ngpDecRad), cos(decRad)*cos(ngpDecRad)*sin(raRad - ngpRaRad)); - // Plug them back in and select solution which is valid for both equations - // (Error should be 0, but we have to allow for small numerical differences) - // There's probably a better way to solve this. - double l1lhs1 = sin(ncpLRad - l1); - double l1lhs2 = cos(ncpLRad - l1); - double l2lhs1 = sin(ncpLRad - l2); - double l2lhs2 = cos(ncpLRad - l2); - double l1Diff = abs(l1lhs1 - rhs1) + abs(l1lhs2 - rhs2); - double l2Diff = abs(l2lhs1 - rhs1) + abs(l2lhs2 - rhs2); - double lRad; - if (l1Diff < l2Diff) - lRad = l1; - else - lRad = l2; + // Ascending node of the galactic plane in degrees + double lAscend = 33.0; // Convert to degrees in range -90,90 and 0,360 b = Units::radiansToDegrees(bRad); - l = Units::radiansToDegrees(lRad); - if (l < 0.0) + l = Units::radiansToDegrees(lRad) + lAscend; + if (l < 0.0) { l += 360.0; - if (l > 360.0) + } + if (l > 360.0) { l -= 360.0; + } } // The following functions are from Starlink Positional Astronomy Library diff --git a/sdrbase/util/astronomy.h b/sdrbase/util/astronomy.h index ff14729bc..06520e308 100644 --- a/sdrbase/util/astronomy.h +++ b/sdrbase/util/astronomy.h @@ -46,6 +46,7 @@ public: static RADec precess(RADec rd_in, double jd_from, double jd_to); static AzAlt raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000=true); + static RADec azAltToRaDec(AzAlt aa, double latitude, double longitude, QDateTime dt); static double localSiderealTime(QDateTime dateTime, double longitude); diff --git a/sdrbase/util/units.h b/sdrbase/util/units.h index 50af66c51..9a17d0699 100644 --- a/sdrbase/util/units.h +++ b/sdrbase/util/units.h @@ -142,9 +142,50 @@ public: return hours + minutes * 1.0f/60.0f + seconds * 1.0f/(60.0f*60.0f); } + // Also supports decimal degrees + static bool degreeMinuteAndSecondsToDecimalDegrees(const QString& string, float& degrees) + { + QRegExp decimal("(-?[0-9]+(\\.[0-9]+)?)"); + if (decimal.exactMatch(string)) + { + degrees = decimal.capturedTexts()[1].toFloat(); + return true; + } + QRegExp dms(QString("(-)?([0-9]+)[%1d](([0-9]+)['m](([0-9]+(\\.[0-9]+)?)[\"s])?)?").arg(QChar(0xb0))); + if (dms.exactMatch(string)) + { + float d = 0.0f; + bool neg = false; + for (int i = 0; i < dms.captureCount(); i++) { + qDebug() << dms.capturedTexts()[i]; + } + if (dms.captureCount() >= 1) { + neg = dms.capturedTexts()[1] == "-"; + } + if (dms.captureCount() >= 3) { + d = dms.capturedTexts()[2].toFloat(); + } + float m = 0.0f; + if (dms.captureCount() >= 5) { + m = dms.capturedTexts()[4].toFloat(); + } + float s = 0.0f; + if (dms.captureCount() >= 7) { + s = dms.capturedTexts()[6].toFloat(); + } + qDebug() << neg << d << m << s; + degrees = d + m/60.0 + s/(60.0*60.0); + if (neg) { + degrees = -degrees; + } + return true; + } + return false; + } + static QString decimalDegreesToDegreeMinutesAndSeconds(float decimal, int secondsFieldWidth=5) { - float v, d, m, s; + double v, d, m, s; int neg; v = decimal; @@ -162,7 +203,7 @@ public: static QString decimalDegreesToDegreesAndMinutes(float decimal) { - float v, d, m; + double v, d, m; int neg; v = decimal; @@ -172,12 +213,21 @@ public: v -= d; v *= 60.0; m = round(v); + if (m == 60) + { + if (neg) { + d--; + } else { + d++; + } + m = 0; + } return QString("%1%2%3%4'").arg(neg ? "-" : "").arg((int)d).arg(QChar(0xb0)).arg((int)m, 2, 10, QChar('0')); } static QString decimalDegreesToDegrees(float decimal) { - float v, d; + double v, d; int neg; v = decimal; @@ -189,7 +239,7 @@ public: static QString decimalHoursToHoursMinutesAndSeconds(float decimal, int precision=2) { - float v, h, m, s; + double v, h, m, s; v = decimal; v = fabs(v); @@ -218,7 +268,6 @@ public: QRegExp dms(QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0))); if (dms.exactMatch(string)) { - qDebug() << "Captured: " << dms.capturedTexts(); float latD = dms.capturedTexts()[1].toFloat(); float latM = dms.capturedTexts()[2].toFloat(); float latS = dms.capturedTexts()[3].toFloat(); @@ -233,8 +282,6 @@ public: longitude = lonD + lonM/60.0 + lonS/(60.0*60.0); if (!east) longitude = -longitude; - qDebug() << "Lat " << latitude; - qDebug() << "Long " << longitude; return true; } return false; diff --git a/sdrgui/gui/dmsspinbox.cpp b/sdrgui/gui/dmsspinbox.cpp new file mode 100644 index 000000000..5a3ce4db6 --- /dev/null +++ b/sdrgui/gui/dmsspinbox.cpp @@ -0,0 +1,126 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include + +#include "util/units.h" + +#include "dmsspinbox.h" + +DMSSpinBox::DMSSpinBox(QWidget *parent) : + QAbstractSpinBox(parent), + m_value(0.0), + m_minimum(0.0), + m_maximum(360.0), + m_units(DM) +{ + setButtonSymbols(QAbstractSpinBox::UpDownArrows); + connect(lineEdit(), SIGNAL(editingFinished()), this, SLOT(on_lineEdit_editingFinished())); +} + +bool DMSSpinBox::hasValue() const +{ + return m_text.isNull(); +} + +double DMSSpinBox::value() const +{ + return m_value; +} + +void DMSSpinBox::setValue(double degrees) +{ + if (m_value != degrees) + { + m_value = degrees; + if (m_value < m_minimum) { + m_value = m_minimum; + } + if (m_value > m_maximum) { + m_value = m_maximum; + } + m_text = QString(); + emit valueChanged(m_value); + } + lineEdit()->setText(convertDegreesToText(m_value)); +} + +void DMSSpinBox::setRange(double minimum, double maximum) +{ + m_minimum = minimum; + m_maximum = maximum; +} + +void DMSSpinBox::setUnits(DisplayUnits units) +{ + m_units = units; + if (hasValue()) { + lineEdit()->setText(convertDegreesToText(m_value)); + } +} + +void DMSSpinBox::setText(QString text) +{ + m_text = text; + lineEdit()->setText(m_text); +} + +void DMSSpinBox::stepBy(int steps) +{ + if (hasValue()) { + setValue(m_value + (double)steps); + } +} + +QAbstractSpinBox::StepEnabled DMSSpinBox::stepEnabled() const +{ + QAbstractSpinBox::StepEnabled enabled = QAbstractSpinBox::StepNone; + + if (hasValue() && (m_value < m_maximum)) { + enabled |= QAbstractSpinBox::StepUpEnabled; + } + if (hasValue() && (m_value > m_minimum)) { + enabled |= QAbstractSpinBox::StepDownEnabled; + } + + return enabled; +} + +QString DMSSpinBox::convertDegreesToText(double degrees) +{ + if (m_units == DMS) { + return Units::decimalDegreesToDegreeMinutesAndSeconds(degrees); + } else if (m_units == DM) { + return Units::decimalDegreesToDegreesAndMinutes(degrees); + } else if (m_units == D) { + return Units::decimalDegreesToDegrees(degrees); + } else { + return QString("%1").arg(degrees, 0, 'f', 2); + } +} + +void DMSSpinBox::on_lineEdit_editingFinished() +{ + QString text = lineEdit()->text().trimmed(); + float decimal; + if (Units::degreeMinuteAndSecondsToDecimalDegrees(text, decimal)) { + setValue(decimal); + } else { + qDebug() << "DMSSpinBox::on_lineEdit_editingFinished: Invalid format: " << text; + } +} + diff --git a/sdrgui/gui/dmsspinbox.h b/sdrgui/gui/dmsspinbox.h new file mode 100644 index 000000000..4e82322b8 --- /dev/null +++ b/sdrgui/gui/dmsspinbox.h @@ -0,0 +1,62 @@ +/////////////////////////////////////////////////////////////////////////////////// +// Copyright (C) 2021 Jon Beniston, M7RCE // +// // +// 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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef SDRGUI_GUI_DMSSPINBOX_H +#define SDRGUI_GUI_DMSSPINBOX_H + +#include + +#include "export.h" + +// Spin box for displaying degrees in decimal or degrees, minutes and seconds format +// Can also be assigned a text string +class SDRGUI_API DMSSpinBox : public QAbstractSpinBox { + Q_OBJECT + +public: + enum DisplayUnits {DMS, DM, D, Decimal}; + + explicit DMSSpinBox(QWidget *parent = nullptr); + bool hasValue() const; + double value() const; + void setValue(double degrees); + void setRange(double minimum, double maximum); + void setUnits(DisplayUnits units); + void setText(QString text); + virtual void stepBy(int steps) override; + +protected: + + virtual QAbstractSpinBox::StepEnabled stepEnabled() const override; + QString convertDegreesToText(double degrees); + +private: + QString m_text; + double m_value; + double m_minimum; + double m_maximum; + DisplayUnits m_units; + +signals: + void valueChanged(double newValue); + +private slots: + void on_lineEdit_editingFinished(); + +}; + +#endif // SDRGUI_GUI_DMSSPINBOX_H