Star Tracker updates.

Add sky temperature calculation.
Add Solar flux plot.
Add .gitattributes so .fits files are treated as binary.
This commit is contained in:
Jon Beniston 2021-01-29 12:57:58 +00:00
parent 52b51feded
commit d80087b974
36 changed files with 11995 additions and 465 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.fits binary

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -5,9 +5,10 @@
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.
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.
<h2>Interface</h2>
<h2>Settings</h2>
![Star Tracker feature plugin GUI](../../../doc/img/StarTracker_plugin.png)
@ -19,11 +20,15 @@ This button starts or stops the plugin. The plugin will only calculate azimuth a
Pressing this button centres the Map Feature (if open) on the current target.
<h3>3: Set latitude and longitude from My Position</h3>
<h3>3:Download Solar flux density data</h3>
Pressing this button downloads the Solar flux density data from the DRAO and Learmonth Observatories.
<h3>4: Set latitude and longitude from My Position</h3>
When clicked, it sets the latitude, longitude and height fields to the values from SDRangel's My Position preferences.
<h3>4: Show settings dialog</h3>
<h3>5: Show settings dialog</h3>
Pressing this button displays a settings dialog, that allows you to set:
@ -35,8 +40,8 @@ Pressing this button displays a settings dialog, that allows you to set:
* Relative humidity in % for use in refraction correction.
* Height above sea level in metres for use in refraction correction.
* Temperature lapse rate in Kelvin per kilometer for use in refraction correction.
* Radio frequency being observed in MHz for use in refraction correction.
* The units to display the solar flux in.
* What data to display for the Solar flux measurement. Data can be selected from 2800 from DRAO or a number of different frequencies from Learmonth. Also, the Learnmonth data can be linearly interpolated to the observation frequency set in the main window.
* The units to display the solar flux in, either Solar Flux Units, Jansky or Wm^-2Hz-1. 1 sfu equals 10,000 Jansky or 10^-22 Wm^-2Hz-1.
* The update period in seconds, which controls how frequently azimuth and elevation are re-calculated.
* The IP port number the Stellarium server listens on.
* Whether to start a Stellarium telescope server.
@ -44,29 +49,27 @@ Pressing this button displays a settings dialog, that allows you to set:
* Whether to draw the Moon on the map.
* Whether to draw the target star (or galaxy) on the map.
<h3>5: Latitude</h3>
<h3>6: Latitude</h3>
Specifies the latitude in decimal degrees of the observation point (antenna location).
Specifies the latitude in decimal degrees (North positive) of the observation point (antenna location).
<h3>6: Longitude</h3>
<h3>7: Longitude</h3>
Specifies the longitude in decimal degrees of the observation point (antenna location).
Specifies the longitude in decimal degrees (East positive) of the observation point (antenna location).
<h3>7: Time</h3>
<h3>8: Time</h3>
Select the date and time at which the position of the target should be calculated. Select either Now, for the current time, or Custom to manually enter a date and time.
<h3>8: LST - Local Sidereal Time</h3>
<h3>9: LST - Local Sidereal Time</h3>
The LST field displays the local sidereal time at the specified location (5&6) and Solar time (7).
The LST field displays the local sidereal time at the specified location (6&7) and Solar time (8).
<h3>9: Solar Flux</h3>
<h3>10: Solar Flux</h3>
The Canadian Solar Radio Monitoring Program measures the Solar flux at 10.7cm three times a day, and is reported in Solar Flux Units (sfu), where 1 SFU equals 10,000 Jansky or 10^-22 Wm^-2Hz-1.
Displays the Solar flux density. The observatory where the data is sourced from, frequency and units can be set in the Settings dialog (5). The field is updated every 24 hours, or can be manually by pressing the download Solar flux density data button (3).
The Solar flux field displays this flux value, using units that can be set in the settings dialog. The field is updated every 24 hours.
<h3>10: Target</h3>
<h3>11: Target</h3>
Select a target object to track from the list.
To manually enter RA (right ascension) and Dec (declination) of an unlisted target, select Custom.
@ -91,27 +94,53 @@ References:
* Cassiopeia A, Cygnus A, Taurus A, and Virgo A at ultra-low radio frequencies - https://research.chalmers.se/publication/516438/file/516438_Fulltext.pdf
* Repeating Jansky - https://www.gb.nrao.edu/~fghigo/JanskyAntenna/RepeatingJansky_memo10.pdf
<h3>11: Right Ascension</h3>
<h3>12: Frequency</h3>
Enter the frequency of observation in MHz. This value is used for sky temperature and refraction calculations.
<h3>13: Beamwidth</h3>
Enter the halfpower (-3dB) beamwidth of your antenna. This value is used for sky temperature calculation.
<h3>14: Right Ascension</h3>
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.
<h3>12: Declination</h3>
<h3>15: Declination</h3>
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.
<h3>13: Azimuth</h3>
<h3>16: Azimuth</h3>
Displays the calculated azimuth (angle in degrees, clockwise from North) to the object.
<h3>14: Elevation</h3>
<h3>17: Elevation</h3>
Displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the object.
<h3>13: Elevation vs Time Plot</h3>
<h2>Plots</h2>
<h3>Elevation vs time</h3>
![Star Tracker Elevation vs Time](../../../doc/img/StarTracker_elevationvstime.png)
In order to assit 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.
Some objects may not be visible from a particular latitude for the specified time, in which case, the grahp title will indicate the object is not visible on that particular date.
<h3>Solar flux vs frequency</h3>
![Star Tracker Solar Flux](../../../doc/img/StarTracker_solarflux.png)
The Solar flux vs frequency plot, shows the solar flux data from the Learmonth observator as a function of frequency.
<h3>Sky temperature</h3>
![Star Tracker sky temperature](../../../doc/img/StarTracker_skytemp.png)
Sky temperature maps are available for display at 150MHz, 408MHz and 1420MHz, in both equatorial and galactic coordinates.
The Star Tracker plugin can also estimate a sky temperature based on the observation frequency and beamwidth entered.
<h2>Map</h2>
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.
@ -150,7 +179,13 @@ Then select the SDRangel telescope reticle and press Ocular view.
<h2>Attribution</h2>
Solar radio flux measurement at 10.7cm 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/solarflux/sx-4-en.php
Salar radio flux mesaurements at 245, 410, 610, 1415, 2695, 4995, 8800 and 15400MHz from the Learmonth Observatory: http://www.sws.bom.gov.au/World_Data_Centre/1/10
150MHz (Landecker and Wielebinski) and 1420MHz (Stockert and Villa-Elisa) All Sky images from MPIfR's (Max-Planck-Institut Fur Radioastronomie) Survey Sampler: https://www3.mpifr-bonn.mpg.de/survey.html
408MHz (Haslam) destriped (Platania) All Sky image and spectral index (Platania) from Strasbourg astronomical Data Center: http://cdsarc.u-strasbg.fr/viz-bin/cat/J/A+A/410/847
Icons are by Adnen Kadri and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/
@ -169,3 +204,26 @@ Or to a custom RA and declination on a given date and time:
To start tracking:
curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/run"
<h2>Devloper Notes</h2>
To convert FITS images between projections, use Montage:
sudo apg-get install montage wcslib-tools
Create header for desired output image. E.g. For galatic coordinates, 0.3deg per pixel, covering 360/180 degrees:
mHdr -c ga -p 1200 -h 180.0 "0.0 +0.0" 360 header.hdr
This uses a gnomonic projection. To change to cylindrical/plate carree, with the centre of the galaxy at the centre of the image, edit header.hdr to have:
CTYPE1 = 'GLON-CAR'
CTYPE2 = 'GLAT-CAR'
CRVAL1 = 0
CRVAL2 = 0
Then convert with:
mProjectQL source.fits dest.fits header.hdr
FITS files can be scaled (Scale > ZScale) and exported to .png with SAOImageDS9: https://sites.google.com/cfa.harvard.edu/saoimageds9

View File

@ -13,5 +13,16 @@
<file>startracker/moon-young-32.png</file>
<file>startracker/pulsar-32.png</file>
<file>startracker/sun-40.png</file>
<file>startracker/sun-button-24.png</file>
<file>startracker/150mhz_ra_dec.png</file>
<file>startracker/408mhz_ra_dec.png</file>
<file>startracker/1420mhz_ra_dec.png</file>
<file>startracker/150mhz_galactic.png</file>
<file>startracker/408mhz_galactic.png</file>
<file>startracker/1420mhz_galactic.png</file>
<file>startracker/150mhz_ra_dec.fits</file>
<file>startracker/408mhz_ra_dec.fits</file>
<file>startracker/1420mhz_ra_dec.fits</file>
<file>startracker/408mhz_ra_dec_spectral_index.fits</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 341 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

View File

@ -17,6 +17,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <algorithm>
#include <QMessageBox>
#include <QLineEdit>
#include <QRegExp>
@ -42,6 +43,7 @@
#include "startrackerreport.h"
#include "startrackersettingsdialog.h"
StarTrackerGUI* StarTrackerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
StarTrackerGUI* gui = new StarTrackerGUI(pluginAPI, featureUISet, feature);
@ -119,6 +121,7 @@ bool StarTrackerGUI::handleMessage(const Message& message)
m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(raDec.getDec());
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
raDecChanged();
return true;
}
@ -151,7 +154,19 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
m_doApplySettings(true),
m_lastFeatureState(0),
m_networkManager(nullptr),
m_solarFlux(0.0)
m_progressDialog(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_temps{FITS(":/startracker/startracker/150mhz_ra_dec.fits"),
FITS(":/startracker/startracker/408mhz_ra_dec.fits"),
FITS(":/startracker/startracker/1420mhz_ra_dec.fits")},
m_spectralIndex(":/startracker/startracker/408mhz_ra_dec_spectral_index.fits")
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
@ -165,15 +180,51 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
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);
// Intialise chart
m_chart.legend()->hide();
ui->elevationChart->setChart(&m_chart);
ui->elevationChart->setRenderHint(QPainter::Antialiasing);
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();
@ -200,14 +251,15 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
7.5e14, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate));
printf("];\n");
*/
*/
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
connect(&m_solarFluxTimer, SIGNAL(timeout()), this, SLOT(updateSolarFlux()));
readSolarFlux();
connect(&m_solarFluxTimer, SIGNAL(timeout()), this, SLOT(autoUpdateSolarFlux()));
m_solarFluxTimer.start(1000*60*60*24); // Update every 24hours
updateSolarFlux();
autoUpdateSolarFlux();
}
StarTrackerGUI::~StarTrackerGUI()
@ -246,6 +298,10 @@ void StarTrackerGUI::displaySettings()
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();
plotChart();
blockApplySettings(false);
@ -489,6 +545,8 @@ void StarTrackerGUI::on_displaySettings_clicked()
{
applySettings();
displaySolarFlux();
if (ui->chartSelect->currentIndex() == 1)
plotChart();
}
}
@ -519,10 +577,350 @@ void StarTrackerGUI::on_dateTime_dateTimeChanged(const QDateTime &datetime)
}
}
// Plot target elevation angle over the day
void StarTrackerGUI::plotChart()
{
if (ui->chartSelect->currentIndex() == 0)
plotElevationChart();
else if (ui->chartSelect->currentIndex() == 1)
plotSolarFluxChart();
else if (ui->chartSelect->currentIndex() == 2)
plotSkyTemperatureChart();
}
void StarTrackerGUI::raDecChanged()
{
if (ui->chartSelect->currentIndex() == 2)
plotSkyTemperatureChart();
}
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()
{
m_chart.removeAllSeries();
removeAllAxes();
if (m_solarFluxesValid)
{
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_chart.setTitle("");
m_chart.addAxis(&m_chartSolarFluxXAxis, Qt::AlignBottom);
m_chart.addAxis(&m_chartYAxis, Qt::AlignLeft);
m_chart.addSeries(series);
series->attachAxis(&m_chartSolarFluxXAxis);
series->attachAxis(&m_chartYAxis);
m_chartSolarFluxXAxis.setTitleText(QString("Frequency (MHz)"));
m_chartSolarFluxXAxis.setMinorTickCount(-1);
if (m_settings.m_solarFluxUnits == StarTrackerSettings::SFU)
{
m_chartYAxis.setLabelFormat("%d");
m_chartYAxis.setRange(0.0, ((((int)maxValue)+99)/100)*100);
}
else if (m_settings.m_solarFluxUnits == StarTrackerSettings::JANSKY)
{
m_chartYAxis.setLabelFormat("%.2g");
m_chartYAxis.setRange(0, ((((int)maxValue)+999999)/100000)*100000);
}
else
{
m_chartYAxis.setLabelFormat("%.2g");
m_chartYAxis.setRange(minValue, maxValue);
}
m_chartYAxis.setTitleText(QString("Solar flux density (%1)").arg(solarFluxUnit()));
}
else
m_chart.setTitle("Press download Solar flux density data to view");
m_chart.setPlotAreaBackgroundVisible(false);
disconnect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
}
void StarTrackerGUI::plotSkyTemperatureChart()
{
bool galactic = (ui->chartSubSelect->currentIndex() & 1) == 1;
m_chart.removeAllSeries();
removeAllAxes();
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;
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);
}
// Get temperature
int idx = ui->chartSubSelect->currentIndex();
if ((idx == 6) || (idx == 7))
{
// Adjust temperature from 408MHz FITS file, taking in to account
// observation frequency and beamwidth
FITS *fits = &m_temps[1];
if (fits->valid())
{
const double beamwidth = m_settings.m_beamwidth;
const double halfBeamwidth = beamwidth/2.0;
// Use cos^p(x) for approximation of radiation pattern
// (Essentially the same as Gaussian of exp(-4*ln(theta^2/beamwidth^2))
// (See a2 in https://arxiv.org/pdf/1812.10084.pdf for Elliptical equivalent))
// We have gain of 0dB (1) at 0 degrees, and -3dB (~0.5) at half-beamwidth degrees
// Find exponent that correponds to -3dB at that angle
double minus3dBLinear = pow(10.0, -3.0/10.0);
double p = log(minus3dBLinear)/log(cos(Units::degreesToRadians(halfBeamwidth)));
// Create an matrix with gain as a function of angle
double degreesPerPixelH = abs(fits->degreesPerPixelH());
double degreesPerPixelV = abs(fits->degreesPerPixelV());
int numberOfCoeffsH = ceil(beamwidth/degreesPerPixelH);
int numberOfCoeffsV = ceil(beamwidth/degreesPerPixelV);
if ((numberOfCoeffsH & 1) == 0)
numberOfCoeffsH++;
if ((numberOfCoeffsV & 1) == 0)
numberOfCoeffsV++;
double *beam = new double[numberOfCoeffsH*numberOfCoeffsV];
double sum = 0.0;
int y0 = numberOfCoeffsV/2;
int x0 = numberOfCoeffsH/2;
int nonZeroCount = 0;
for (int y = 0; y < numberOfCoeffsV; y++)
{
for (int x = 0; x < numberOfCoeffsH; x++)
{
double xp = (x - x0) * degreesPerPixelH;
double yp = (y - y0) * degreesPerPixelV;
double r = sqrt(xp*xp+yp*yp);
if (r < halfBeamwidth)
{
beam[y*numberOfCoeffsH+x] = pow(cos(Units::degreesToRadians(r)), p);
sum += beam[y*numberOfCoeffsH+x];
nonZeroCount++;
}
else
beam[y*numberOfCoeffsH+x] = 0.0;
}
}
// Get centre pixel coordinates
double centreX;
if (ra <= 12.0)
centreX = (12.0 - ra) / 24.0;
else
centreX = (24 - ra + 12) / 24.0;
double centreY = (90.0-dec) / 180.0;
int imgX = centreX * fits->width();
int imgY = centreY * fits->height();
// Apply weighting to temperature data
double weightedSum = 0.0;
for (int y = 0; y < numberOfCoeffsV; y++)
{
for (int x = 0; x < numberOfCoeffsH; x++)
{
weightedSum += beam[y*numberOfCoeffsH+x] * fits->scaledWrappedValue(imgX + (x-x0), imgY + (y-y0));
}
}
// From: https://www.cv.nrao.edu/~sransom/web/Ch3.html
// The antenna temperature equals the source brightness temperature multiplied by the fraction of the beam solid angle filled by the source
// So we scale the sum by the total number of non-zero pixels (i.e. beam area)
// If we compare to some maps with different beamwidths here: https://www.cv.nrao.edu/~demerson/radiosky/sky_jun96.pdf
// The values we've computed are a bit higher..
double temp408 = weightedSum/nonZeroCount;
// Scale according to frequency - CMB contribution constant
// Power law at low frequencies, with slight variation in spectral index
// See:
// Global Sky Model: https://ascl.net/1011.010
// An improved Model of Diffuse Galactic Radio Emission: https://arxiv.org/pdf/1605.04920.pdf
// A high-resolution self-consistent whole sky foreground model: https://arxiv.org/abs/1812.10084
// (De-striping:) Full sky study of diffuse Galactic emission at decimeter wavelength https://www.aanda.org/articles/aa/pdf/2003/42/aah4363.pdf
// Data here: http://cdsarc.u-strasbg.fr/viz-bin/cat/J/A+A/410/847
// LFmap: https://www.faculty.ece.vt.edu/swe/lwa/memo/lwa0111.pdf
double iso408 = 50 * pow(150e6/408e6, 2.75); // Extra-galactic isotropic in reference map at 408MHz
double isoT = 50 * pow(150e6/m_settings.m_frequency, 2.75); // Extra-galactic isotropic at target frequency
double cmbT = 2.725; // Cosmic microwave backgroud;
double spectralIndex;
if (m_spectralIndex.valid())
{
// See https://www.aanda.org/articles/aa/pdf/2003/42/aah4363.pdf
spectralIndex = m_spectralIndex.scaledValue(imgX, imgY);
}
else
{
// See https://arxiv.org/abs/1812.10084 fig 2
if (m_settings.m_frequency < 200e6)
spectralIndex = 2.55;
else if (m_settings.m_frequency < 20e9)
spectralIndex = 2.695;
else
spectralIndex = 3.1;
}
double galactic480 = temp408 - cmbT - iso408;
double galacticT = galactic480 * pow(408e6/m_settings.m_frequency, spectralIndex); // Scale galactic contribution by frequency
double temp = galacticT + cmbT + isoT; // Final temperature
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
qDebug() << "StarTrackerGUI::plotSkyTemperatureChart: FITS temperature file not valid";
}
else
{
// Read temperature from selected FITS file at target RA/Dec
QImage *img = &m_images[idx];
FITS *fits = &m_temps[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();
if (imgX >= img->width())
imgX = img->width();
int imgY = (90.0-dec)/180.0 * img->height();
if (imgY >= img->height())
imgY = img->height();
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)));
}
// Temperature from just one pixel, but need to make marker visbile
markerSize = 5;
}
series->setMarkerSize(markerSize);
m_chart.setTitle("");
m_chart.addSeries(series);
if (galactic)
{
m_chart.addAxis(&m_skyTempGalacticLXAxis, Qt::AlignBottom);
series->attachAxis(&m_skyTempGalacticLXAxis);
m_skyTempYAxis.setTitleText(QString("Galactic latitude (%1)").arg(QChar(0xb0)));
m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft);
series->attachAxis(&m_skyTempYAxis);
}
else
{
m_chart.addAxis(&m_skyTempRAXAxis, Qt::AlignBottom);
series->attachAxis(&m_skyTempRAXAxis);
m_skyTempYAxis.setTitleText(QString("Declination (%1)").arg(QChar(0xb0)));
m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft);
series->attachAxis(&m_skyTempYAxis);
}
plotAreaChanged(m_chart.plotArea());
connect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
}
void StarTrackerGUI::plotAreaChanged(const QRectF &plotArea)
{
int width = static_cast<int>(m_chart.plotArea().width());
int height = static_cast<int>(m_chart.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 == 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);
QPointF topLeft = m_chart.plotArea().topLeft();
painter.drawImage(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::plotElevationChart()
{
m_chart.removeAllSeries();
removeAllAxes();
double maxElevation = -90.0;
QLineSeries *series = new QLineSeries();
@ -579,6 +977,8 @@ void StarTrackerGUI::plotChart()
m_chart.setTitle("Not visible from this latitude");
else
m_chart.setTitle("");
m_chart.addAxis(&m_chartXAxis, Qt::AlignBottom);
m_chart.addAxis(&m_chartYAxis, Qt::AlignLeft);
m_chart.addSeries(series);
series->attachAxis(&m_chartXAxis);
series->attachAxis(&m_chartYAxis);
@ -588,6 +988,8 @@ void StarTrackerGUI::plotChart()
m_chartXAxis.setRange(startTime, endTime);
m_chartYAxis.setRange(0.0, 90.0);
m_chartYAxis.setTitleText(QString("Elevation (%1)").arg(QChar(0xb0)));
m_chart.setPlotAreaBackgroundVisible(false);
disconnect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
}
// Find target on the Map
@ -597,35 +999,174 @@ void StarTrackerGUI::on_viewOnMap_clicked()
FeatureWebAPIUtils::mapFind(target);
}
void StarTrackerGUI::updateSolarFlux()
void StarTrackerGUI::updateChartSubSelect()
{
qDebug() << "StarTrackerGUI: Updating flux";
m_networkRequest.setUrl(QUrl("https://www.spaceweather.gc.ca/solarflux/sx-4-en.php"));
m_networkManager->get(m_networkRequest);
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 (ui->chartSelect->currentIndex() == 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();
}
ui->chartSubSelect->blockSignals(oldState);
plotChart();
}
void StarTrackerGUI::on_chartSubSelect_currentIndexChanged(int 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 "";
}
// Linear extrapolation
static double extrapolate(double x0, double y0, double x1, double y1, double x)
{
return y0 + ((x-x0)/(x1-x0)) * (y1-y0);
}
// Linear interpolation
static double interpolate(double x0, double y0, double x1, double y1, double x)
{
return (y0*(x1-x) + y1*(x-x0)) / (x1-x0);
}
void StarTrackerGUI::displaySolarFlux()
{
if (m_solarFlux <= 0.0)
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
{
switch (m_settings.m_solarFluxUnits)
double solarFlux;
if (m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800)
{
case StarTrackerSettings::SFU:
ui->solarFlux->setText(QString("%1 sfu").arg(m_solarFlux));
break;
case StarTrackerSettings::JANSKY:
ui->solarFlux->setText(QString("%1 Jy").arg(Units::solarFluxUnitsToJansky(m_solarFlux)));
break;
case StarTrackerSettings::WATTS_M_HZ:
ui->solarFlux->setText(QString("%1 Wm^-2Hz^-1").arg(Units::solarFluxUnitsToWattsPerMetrePerHertz(m_solarFlux)));
break;
solarFlux = m_solarFlux;
ui->solarFlux->setToolTip(QString("Solar flux density at 2800 MHz"));
}
else if (m_settings.m_solarFluxData == StarTrackerSettings::TARGET_FREQ)
{
double freqMhz = m_settings.m_frequency/1000000.0;
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 = extrapolate(m_solarFluxFrequencies[0], m_solarFluxes[0],
m_solarFluxFrequencies[1], m_solarFluxes[1],
freqMhz
);
}
else if (i == fluxes)
{
solarFlux = extrapolate(m_solarFluxFrequencies[fluxes-2], m_solarFluxes[fluxes-2],
m_solarFluxFrequencies[fluxes-1], m_solarFluxes[fluxes-1],
freqMhz
);
}
else
{
solarFlux = interpolate(m_solarFluxFrequencies[i-1], m_solarFluxes[i-1],
m_solarFluxFrequencies[i], m_solarFluxes[i],
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);
}
}
bool StarTrackerGUI::readSolarFlux()
{
QDate today = QDateTime::currentDateTimeUtc().date();
QFile file(getSolarFluxFilename(today));
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
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: Solar flux data is more than 1 day old";
return false;
}
void StarTrackerGUI::networkManagerFinished(QNetworkReply *reply)
{
ui->solarFlux->setText(""); // Don't show obsolete data
@ -648,8 +1189,50 @@ void StarTrackerGUI::networkManagerFinished(QNetworkReply *reply)
displaySolarFlux();
}
else
qDebug() << "No Solar flux found: " << answer;
qDebug() << "StarTrackerGUI::networkManagerFinished - No Solar flux found: " << answer;
}
reply->deleteLater();
}
QString StarTrackerGUI::getSolarFluxFilename(QDate date)
{
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(today);
if (m_dlm.confirmDownload(solarFluxFile))
{
QString urlString = QString("http://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/solarflux/sx-4-en.php"));
m_networkManager->get(m_networkRequest);
}
}
void StarTrackerGUI::autoUpdateSolarFlux()
{
updateSolarFlux(false);
}
void StarTrackerGUI::on_downloadSolarFlux_clicked()
{
updateSolarFlux(true);
}
void StarTrackerGUI::downloadFinished(const QString& filename, bool success)
{
if (success)
readSolarFlux();
}

View File

@ -22,9 +22,13 @@
#include <QTimer>
#include <QtCharts>
#include <QNetworkRequest>
#include <QImage>
#include <QProgressDialog>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "util/fits.h"
#include "gui/httpdownloadmanagergui.h"
#include "startrackersettings.h"
class PluginAPI;
@ -67,9 +71,25 @@ private:
QDateTimeAxis m_chartXAxis;
QValueAxis m_chartYAxis;
QCategoryAxis m_skyTempGalacticLXAxis;
QCategoryAxis m_skyTempRAXAxis;
QValueAxis m_skyTempYAxis;
QLogValueAxis m_chartSolarFluxXAxis;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
double m_solarFlux;
HttpDownloadManagerGUI m_dlm;
QProgressDialog *m_progressDialog;
double m_solarFlux; // 10.7cm/2800MHz
bool m_solarFluxesValid;
int m_solarFluxes[8]; // Frequency (MHz), flux density (sfu)
const int m_solarFluxFrequencies[8] = {245, 410, 610, 1415, 2695, 4995, 8800, 15400};
QList<QImage> m_images;
QList<FITS> m_temps;
FITS m_spectralIndex;
explicit StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~StarTrackerGUI();
@ -81,8 +101,19 @@ private:
QString convertDegreesToText(double degrees);
bool handleMessage(const Message& message);
void updateLST();
void plotElevationChart();
void plotSkyTemperatureChart();
void plotSolarFluxChart();
void plotChart();
void removeAllAxes();
double convertSolarFluxUnits(double sfu);
QString solarFluxUnit();
void displaySolarFlux();
QString getSolarFluxFilename(QDate date);
bool readSolarFlux();
void raDecChanged();
void updateChartSubSelect();
void updateSolarFlux(bool all);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -97,14 +128,21 @@ private slots:
void on_longitude_valueChanged(double value);
void on_rightAscension_editingFinished();
void on_declination_editingFinished();
void on_frequency_valueChanged(int value);
void on_beamwidth_valueChanged(double value);
void on_target_currentTextChanged(const QString &text);
void on_displaySettings_clicked();
void on_dateTimeSelect_currentTextChanged(const QString &text);
void on_dateTime_dateTimeChanged(const QDateTime &datetime);
void updateStatus();
void on_viewOnMap_clicked();
void updateSolarFlux();
void on_chartSelect_currentIndexChanged(int index);
void on_chartSubSelect_currentIndexChanged(int index);
void plotAreaChanged(const QRectF &plotArea);
void autoUpdateSolarFlux();
void on_downloadSolarFlux_clicked();
void networkManagerFinished(QNetworkReply *reply);
void downloadFinished(const QString& filename, bool success);
};

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>337</width>
<width>339</width>
<height>568</height>
</rect>
</property>
@ -42,7 +42,7 @@
<rect>
<x>10</x>
<y>10</y>
<width>301</width>
<width>321</width>
<height>201</height>
</rect>
</property>
@ -67,73 +67,44 @@
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="latitude">
<item row="4" column="1">
<widget class="QLineEdit" name="lst">
<property name="toolTip">
<string>Latitude in decimal degrees (North positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>-90.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="rightAscension">
<property name="toolTip">
<string>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)</string>
</property>
<property name="text">
<string>23h59m59.59s</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="azimuth">
<property name="toolTip">
<string>Computed azimuth in degrees to the target from the observation point</string>
</property>
<property name="text">
<string>360</string>
<string>Local sidereal time for selected date, time and longitude</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeLabel">
<item row="7" column="2">
<widget class="QLabel" name="declinationLabel">
<property name="text">
<string>Time</string>
<string>Dec</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QLineEdit" name="declination">
<item row="3" column="1">
<widget class="QComboBox" name="dateTimeSelect">
<property name="toolTip">
<string>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&quot; 34 12 10.2)</string>
</property>
<property name="text">
<string>-90d59'59.59&quot;</string>
<string>Select time to calculate target's position at</string>
</property>
<item>
<property name="text">
<string>Now</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="targetLabel">
<item row="6" column="0">
<widget class="QLabel" name="frequencyLabel">
<property name="text">
<string>Target</string>
<string>Frequency</string>
</property>
</widget>
</item>
@ -150,114 +121,109 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="latitudeLabel">
<item row="7" column="3">
<widget class="QLineEdit" name="declination">
<property name="toolTip">
<string>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&quot; 34 12 10.2)</string>
</property>
<property name="text">
<string>Latitude</string>
<string>-90d59'59.59&quot;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="dateTimeSelect">
<item>
<property name="text">
<string>Now</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="7" column="2">
<widget class="QLabel" name="elevationLabel">
<item row="7" column="1">
<widget class="QLineEdit" name="rightAscension">
<property name="toolTip">
<string>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)</string>
</property>
<property name="text">
<string>Elevation</string>
<string>23h59m59.59s</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="rightAscensionLabel">
<property name="text">
<string>RA</string>
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="longitude">
<property name="toolTip">
<string>Longitude in decimal degress (East positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="value">
<double>-180.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0" colspan="4">
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>start/stop acquisition</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="viewOnMap">
<property name="toolTip">
<string>Find target on the map</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/gridpolar.png</normaloff>:/gridpolar.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="useMyPosition">
<property name="toolTip">
<string>Set latitude, longitude and height from My Position in SDRangel preferences</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/import.png</normaloff>:/import.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="displaySettings">
<property name="toolTip">
<string>Show settings dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
</layout>
<item row="6" column="1">
<widget class="QSpinBox" name="frequency">
<property name="toolTip">
<string>Observation frequency (MHz)</string>
</property>
<property name="minimum">
<number>50</number>
</property>
<property name="maximum">
<number>100000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lstLabel">
<property name="text">
<string>LST</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="longitudeLabel">
<property name="text">
<string>Longitude</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="targetLabel">
<property name="text">
<string>Target</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="azimuth">
<property name="toolTip">
<string>Computed azimuth in degrees to the target from the observation point</string>
</property>
<property name="text">
<string>360</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="azimuthtLabel">
<property name="text">
<string>Azimuth</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="solarFluxLabel">
<property name="text">
<string>Solar Flux</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<layout class="QHBoxLayout" name="targetLayout">
@ -269,6 +235,9 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Target object</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
@ -339,47 +308,142 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
</item>
</layout>
</item>
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="longitude">
<item row="0" column="0" colspan="4">
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>start/stop acquisition</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="viewOnMap">
<property name="toolTip">
<string>Find target on the map</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/gridpolar.png</normaloff>:/gridpolar.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="downloadSolarFlux">
<property name="toolTip">
<string>Download Solar flux density data</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="startracker.qrc">
<normaloff>:/startracker/startracker/sun-button-24.png</normaloff>:/startracker/startracker/sun-button-24.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="useMyPosition">
<property name="toolTip">
<string>Set latitude, longitude and height from My Position in SDRangel preferences</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/import.png</normaloff>:/import.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="displaySettings">
<property name="toolTip">
<string>Show settings dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="latitude">
<property name="toolTip">
<string>Longitude in decimal degress (East positive) of observation point / antenna location</string>
<string>Latitude in decimal degrees (North positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
<double>90.000000000000000</double>
</property>
<property name="value">
<double>-180.000000000000000</double>
<double>-90.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLabel" name="elevationLabel">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="declinationLabel">
<widget class="QLabel" name="beamwidthLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="longitudeLabel">
<property name="text">
<string>Longitude</string>
<string>Beamwidth</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="azimuthtLabel">
<widget class="QLabel" name="rightAscensionLabel">
<property name="text">
<string>Azimuth</string>
<string>RA</string>
</property>
</widget>
</item>
<item row="7" column="3">
<item row="8" column="3">
<widget class="QLineEdit" name="elevation">
<property name="toolTip">
<string>Computed elevation in degrees to the target from the observation point</string>
@ -392,40 +456,42 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lstLabel">
<property name="text">
<string>LST</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lst">
<property name="toolTip">
<string>Local sidereal time for selected date, time and longitude</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="solarFluxLabel">
<property name="text">
<string>Solar Flux</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLineEdit" name="solarFlux">
<property name="toolTip">
<string>Displays the solar flux at 10.7cm</string>
<string>Solar flux density</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="latitudeLabel">
<property name="text">
<string>Latitude</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QDoubleSpinBox" name="beamwidth">
<property name="toolTip">
<string>Antenna half power (-3dB) beamwidth (degrees)</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>25.000000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -452,9 +518,9 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
</size>
</property>
<property name="windowTitle">
<string>Elevation vs Time</string>
<string>Plots</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0">
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0">
<property name="spacing">
<number>2</number>
</property>
@ -471,7 +537,36 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
<number>3</number>
</property>
<item>
<widget class="QChartView" name="elevationChart">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="chartSelect">
<property name="toolTip">
<string>Select chart to view</string>
</property>
<item>
<property name="text">
<string>Elevation vs time</string>
</property>
</item>
<item>
<property name="text">
<string>Solar flux vs frequency</string>
</property>
</item>
<item>
<property name="text">
<string>Sky temperature</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QComboBox" name="chartSubSelect"/>
</item>
</layout>
</item>
<item>
<widget class="QChartView" name="chart">
<property name="minimumSize">
<size>
<width>300</width>
@ -517,10 +612,11 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
<tabstop>declination</tabstop>
<tabstop>azimuth</tabstop>
<tabstop>elevation</tabstop>
<tabstop>elevationChart</tabstop>
<tabstop>chart</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="startracker.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -42,10 +42,12 @@ void StarTrackerSettings::resetToDefaults()
m_humidity = 80.0;
m_heightAboveSeaLevel = 0.0;
m_temperatureLapseRate = 6.49;
m_frequency = 435000000;
m_frequency = 432000000;
m_beamwidth = 25.0;
m_enableServer = true;
m_serverPort = 10001;
m_azElUnits = DM;
m_solarFluxData = DRAO_2800;
m_solarFluxUnits = SFU;
m_updatePeriod = 1.0;
m_jnow = false;
@ -94,6 +96,8 @@ QByteArray StarTrackerSettings::serialize() const
s.writeU32(27, m_reverseAPIFeatureSetIndex);
s.writeU32(28, m_reverseAPIFeatureIndex);
s.writeU32(29, m_solarFluxUnits);
s.writeDouble(30, m_beamwidth);
s.writeU32(31, m_solarFluxData);
return s.final();
}
@ -159,6 +163,8 @@ bool StarTrackerSettings::deserialize(const QByteArray& data)
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
d.readU32(29, (quint32 *)&m_solarFluxUnits, SFU);
d.readDouble(30, &m_beamwidth, 25.0);
d.readU32(31, (quint32 *)&m_solarFluxData, DRAO_2800);
return true;
}

View File

@ -41,9 +41,11 @@ struct StarTrackerSettings
double m_heightAboveSeaLevel; // In metres
double m_temperatureLapseRate; // In K/km
double m_frequency; // Observation frequency in Hz
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 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;
bool m_jnow; // Use JNOW epoch rather than J2000

View File

@ -36,7 +36,7 @@ StarTrackerSettingsDialog::StarTrackerSettingsDialog(StarTrackerSettings *settin
ui->humidity->setValue(settings->m_humidity);
ui->height->setValue(settings->m_heightAboveSeaLevel);
ui->temperatureLapseRate->setValue(settings->m_temperatureLapseRate);
ui->frequency->setValue(settings->m_frequency/1000000.0);
ui->solarFluxData->setCurrentIndex((int)settings->m_solarFluxData);
ui->solarFluxUnits->setCurrentIndex((int)settings->m_solarFluxUnits);
ui->drawSunOnMap->setChecked(settings->m_drawSunOnMap);
ui->drawMoonOnMap->setChecked(settings->m_drawMoonOnMap);
@ -61,7 +61,7 @@ void StarTrackerSettingsDialog::accept()
m_settings->m_humidity = ui->humidity->value();
m_settings->m_heightAboveSeaLevel = ui->height->value();
m_settings->m_temperatureLapseRate = ui->temperatureLapseRate->value();
m_settings->m_frequency = ui->frequency->value() * 1000000.0;
m_settings->m_solarFluxData = (StarTrackerSettings::SolarFluxData)ui->solarFluxData->currentIndex();
m_settings->m_solarFluxUnits = (StarTrackerSettings::SolarFluxUnits)ui->solarFluxUnits->currentIndex();
m_settings->m_drawSunOnMap = ui->drawSunOnMap->isChecked();
m_settings->m_drawMoonOnMap = ui->drawMoonOnMap->isChecked();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>393</width>
<height>485</height>
<width>496</width>
<height>491</height>
</rect>
</property>
<property name="font">
@ -23,17 +23,29 @@
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="10" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="text">
<string>Update period (s)</string>
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="updatePeriod">
<property name="toolTip">
<string>Enter the time in seconds between each calculation of the target's position</string>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="pressureLabel">
<property name="text">
<string>Air pressure (mb)</string>
<item row="4" column="1">
<widget class="QSpinBox" name="temperature">
<property name="toolTip">
<string>Air temperature in degrees Celsius, for use in atmospheric refraction correction</string>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
@ -44,52 +56,69 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="refractionLabel">
<item row="16" column="0">
<widget class="QCheckBox" name="drawStarOnMap">
<property name="text">
<string>Refraction correction</string>
<string>Draw target star on map</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="frequencyLabel">
<property name="text">
<string>Frequency (MHz)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="refraction">
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="pressure">
<property name="toolTip">
<string>Atmospheric refraction correction</string>
<string>Air pressure in millibars, for use in atmospheric refraction correction</string>
</property>
<property name="currentIndex">
<number>0</number>
<property name="maximum">
<double>2000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1010.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="heightLabel">
<property name="text">
<string>Height above sea level (m)</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QCheckBox" name="drawMoonOnMap">
<property name="text">
<string>Draw Moon on map</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QComboBox" name="solarFluxUnits">
<property name="toolTip">
<string>Units to use for the display of the Solar flux density</string>
</property>
<item>
<property name="text">
<string>None</string>
<string>Solar flux units (sfu)</string>
</property>
</item>
<item>
<property name="text">
<string>Saemundsson</string>
<string>Jansky (Jy)</string>
</property>
</item>
<item>
<property name="text">
<string>Positional Astronomy Library</string>
<string>Watts per square metre per hertz (W m^-2 Hz-1)</string>
</property>
</item>
</widget>
</item>
<item row="13" column="0">
<widget class="QCheckBox" name="enableServer">
<property name="toolTip">
<string>Enable Stellarium server which allows RA and Dec to be sent to and from Stellarium</string>
</property>
<item row="10" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="text">
<string>Stellarium server</string>
<string>Update period (s)</string>
</property>
</widget>
</item>
@ -106,16 +135,66 @@
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="frequency">
<item row="5" column="1">
<widget class="QSpinBox" name="humidity">
<property name="toolTip">
<string>Radio frequency being observed</string>
<string>Relative humidity in %</string>
</property>
<property name="maximum">
<number>1000000000</number>
<number>100</number>
</property>
<property name="value">
<number>435</number>
<number>80</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="solarFluxUnitsLabel">
<property name="text">
<string>Solar flux density units</string>
</property>
</widget>
</item>
<item row="17" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="14" column="0">
<widget class="QCheckBox" name="drawSunOnMap">
<property name="text">
<string>Draw Sun on map</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="toolTip">
<string>Stellarium telescope server IP port number</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>10001</number>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="serverPortLabel">
<property name="text">
<string>Stellarium server port</string>
</property>
</widget>
</item>
@ -126,6 +205,63 @@
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="temperatureLapseRateLabel">
<property name="toolTip">
<string>Temperature lapse rate (K/m)</string>
</property>
<property name="text">
<string>Temperature lapse rate (K/km)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="pressureLabel">
<property name="text">
<string>Air pressure (mb)</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QCheckBox" name="enableServer">
<property name="toolTip">
<string>Enable Stellarium server which allows RA and Dec to be sent to and from Stellarium</string>
</property>
<property name="text">
<string>Stellarium server</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="epoch">
<property name="toolTip">
<string>Epoch for custom right ascension and declination</string>
</property>
<item>
<property name="text">
<string>J2000</string>
</property>
</item>
<item>
<property name="text">
<string>JNOW</string>
</property>
</item>
</widget>
</item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="temperatureLapseRate">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>6.490000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="temperatureLabel">
<property name="text">
@ -133,6 +269,13 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="humidityLabel">
<property name="text">
<string>Humidity (%)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="azElUnits">
<property name="toolTip">
@ -163,197 +306,98 @@
</item>
</widget>
</item>
<item row="14" column="0">
<widget class="QCheckBox" name="drawSunOnMap">
<item row="2" column="0">
<widget class="QLabel" name="refractionLabel">
<property name="text">
<string>Draw Sun on map</string>
<string>Refraction correction</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="heightLabel">
<property name="text">
<string>Height above sea level (m)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="epoch">
<item row="2" column="1">
<widget class="QComboBox" name="refraction">
<property name="toolTip">
<string>Epoch for custom right ascension and declination</string>
<string>Atmospheric refraction correction</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>J2000</string>
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>JNOW</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="humidityLabel">
<property name="text">
<string>Humidity (%)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="temperatureLapseRate">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>6.490000000000000</double>
</property>
</widget>
</item>
<item row="17" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="15" column="0">
<widget class="QCheckBox" name="drawMoonOnMap">
<property name="text">
<string>Draw Moon on map</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="pressure">
<property name="toolTip">
<string>Air pressure in millibars, for use in atmospheric refraction correction</string>
</property>
<property name="maximum">
<double>2000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1010.000000000000000</double>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="temperatureLapseRateLabel">
<property name="toolTip">
<string>Temperature lapse rate (K/m)</string>
</property>
<property name="text">
<string>Temperature lapse rate (K/km)</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="humidity">
<property name="toolTip">
<string>Relative humidity in %</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>80</number>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="toolTip">
<string>Stellarium telescope server IP port number</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>10001</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="temperature">
<property name="toolTip">
<string>Air temperature in degrees Celsius, for use in atmospheric refraction correction</string>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="updatePeriod">
<property name="toolTip">
<string>Enter the time in seconds between each calculation of the target's position</string>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="serverPortLabel">
<property name="text">
<string>Stellarium server port</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QCheckBox" name="drawStarOnMap">
<property name="text">
<string>Draw target star on map</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="solarFluxUnitsLabel">
<property name="text">
<string>Solar flux units</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QComboBox" name="solarFluxUnits">
<property name="toolTip">
<string>Units to use for the display of the Solar flux</string>
</property>
<item>
<property name="text">
<string>Solar flux units (sfu)</string>
<string>Saemundsson</string>
</property>
</item>
<item>
<property name="text">
<string>Jansky (Jy)</string>
<string>Positional Astronomy Library</string>
</property>
</item>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="solarFluxDataLabel">
<property name="text">
<string>Solar flux density data</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QComboBox" name="solarFluxData">
<property name="toolTip">
<string>Select frequency at which to display Solar flux density data for</string>
</property>
<item>
<property name="text">
<string>DRAO (2800MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Watts per square metre per hertz (W m^-2 Hz-1)</string>
<string>Learmonth (245MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (410MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (610MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (1415MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (2695MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (4995MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (8800MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Learmonth (15400MHz)</string>
</property>
</item>
<item>
<property name="text">
<string>Observation frequency</string>
</property>
</item>
</widget>
@ -382,7 +426,6 @@
<tabstop>humidity</tabstop>
<tabstop>height</tabstop>
<tabstop>temperatureLapseRate</tabstop>
<tabstop>frequency</tabstop>
<tabstop>solarFluxUnits</tabstop>
<tabstop>updatePeriod</tabstop>
<tabstop>serverPort</tabstop>

View File

@ -184,6 +184,7 @@ set(sdrbase_SOURCES
util/csv.cpp
util/db.cpp
util/fixedtraits.cpp
util/fits.cpp
util/httpdownloadmanager.cpp
util/lfsr.cpp
util/maidenhead.cpp
@ -377,6 +378,7 @@ set(sdrbase_HEADERS
util/doublebuffer.h
util/doublebufferfifo.h
util/fixedtraits.h
util/fits.h
util/httpdownloadmanager.h
util/incrementalarray.h
util/incrementalvector.h

View File

@ -492,6 +492,61 @@ double Astronomy::lstAndRAToLongitude(double lst, double raHours)
return -longitude; // East positive
}
// Return right ascension and declination of North Galactic Pole in J2000 Epoch
void Astronomy::northGalacticPoleJ2000(double& ra, double& dec)
{
ra = 12 + 51.4 / 60.0;
dec = 27.13;
}
// Convert from equatorial to Galactic coordinates, J2000 Epoch
void Astronomy::equatorialToGalactic(double ra, double dec, double& l, double& b)
{
const double raRad = Units::degreesToRadians(ra * 15.0);
const double decRad = Units::degreesToRadians(dec);
// Calculate RA and dec for North Galactic pole, J2000
double ngpRa, ngpDec;
northGalacticPoleJ2000(ngpRa, ngpDec);
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);
// 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;
// Convert to degrees in range -90,90 and 0,360
b = Units::radiansToDegrees(bRad);
l = Units::radiansToDegrees(lRad);
if (l < 0.0)
l += 360.0;
if (l > 360.0)
l -= 360.0;
}
// The following functions are from Starlink Positional Astronomy Library
// https://github.com/Starlink/pal

View File

@ -61,6 +61,9 @@ public:
static double lstAndRAToLongitude(double lst, double raHours);
static void equatorialToGalactic(double ra, double dec, double& l, double& b);
static void northGalacticPoleJ2000(double& ra, double& dec);
protected:
static double modulo(double a, double b);
};

149
sdrbase/util/fits.cpp Normal file
View File

@ -0,0 +1,149 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <algorithm>
#include <QRegExp>
#include <QDebug>
#include <QResource>
#include "fits.h"
FITS::FITS(QString resourceName) :
m_valid(false)
{
QResource m_res(resourceName);
int m_headerSize = 2880;
m_data = m_res.uncompressedData();
int hLen = std::min((qint64)m_headerSize * 3, m_res.uncompressedSize()); // Could possibly be bigger
QByteArray headerBytes = m_data.left(hLen);
QString header = QString::fromLatin1(headerBytes);
QRegExp widthRE("NAXIS1 *= *([0-9]+)");
QRegExp heightRE("NAXIS2 *= *([0-9]+)");
QRegExp bitsPerPixelRE("BITPIX *= *(-?[0-9]+)");
QRegExp bzeroRE("BZERO *= *([0-9]+)");
QRegExp bscaleRE("BSCALE *= *(-?[0-9]+(.[0-9]+)?)");
QRegExp buintRE("BUNIT *= *\\'([A-Z ]+)\\'");
QRegExp cdelt1RE("CDELT1 *= *(-?[0-9]+(.[0-9]+)?)");
QRegExp cdelt2RE("CDELT2 *= *(-?[0-9]+(.[0-9]+)?)");
QRegExp endRE("END {77}");
if (widthRE.indexIn(header) != -1)
m_width = widthRE.capturedTexts()[1].toInt();
else
{
qWarning() << "FITS: NAXIS1 missing";
return;
}
if (heightRE.indexIn(header) != -1)
m_height = heightRE.capturedTexts()[1].toInt();
else
{
qWarning() << "FITS: NAXIS2 missing";
return;
}
if (bitsPerPixelRE.indexIn(header) != -1)
m_bitsPerPixel = bitsPerPixelRE.capturedTexts()[1].toInt();
else
{
qWarning() << "FITS: BITPIX missing";
return;
}
m_bytesPerPixel = abs(m_bitsPerPixel)/8;
if (bzeroRE.indexIn(header) != -1)
m_bzero = bzeroRE.capturedTexts()[1].toInt();
else
m_bzero = 0;
if (bscaleRE.indexIn(header) != -1)
m_bscale = bscaleRE.capturedTexts()[1].toDouble();
else
m_bscale = 1.0;
if (cdelt1RE.indexIn(header) != -1)
m_cdelta1 = cdelt1RE.capturedTexts()[1].toDouble();
else
m_cdelta1 = 0.0;
if (cdelt2RE.indexIn(header) != -1)
m_cdelta2 = cdelt2RE.capturedTexts()[1].toDouble();
else
m_cdelta2 = 0.0;
if (buintRE.indexIn(header) != -1)
{
m_buint = buintRE.capturedTexts()[1].trimmed();
if (m_buint.contains("MILLI"))
m_uintScale = 0.001f;
else
m_uintScale = 1.0f;
}
else
m_uintScale = 1.0f;
int endIdx = endRE.indexIn(header);
if (endIdx == -1)
{
qWarning() << "FITS: END missing";
return;
}
m_dataStart = ((endIdx + m_headerSize) / m_headerSize) * m_headerSize;
m_valid = true;
}
float FITS::value(int x, int y)
{
int offset = m_dataStart + (m_height-1-y) * m_width * m_bytesPerPixel + x * m_bytesPerPixel;
const uchar *data = (const uchar *)m_data.data();
// Big-endian
int v = 0;
for (int i = m_bytesPerPixel - 1; i >= 0; i--)
v += data[offset++] << (i*8);
if (m_bitsPerPixel > 0)
{
// Sign-extend
switch (m_bytesPerPixel)
{
case 1:
v = (char)v;
break;
case 2:
v = (qint16)v;
break;
case 3:
v = (qint32)v;
break;
}
return v * m_bscale + m_bzero;
}
else
return *reinterpret_cast<float*>(&v);
}
float FITS::scaledValue(int x, int y)
{
float v = value(x, y);
return v * m_uintScale;
}
int FITS::mod(int a, int b)
{
return a - b * floor(a/(double)b);
}
float FITS::scaledWrappedValue(int x, int y)
{
float v = value(mod(x, m_width), mod(y, m_height));
return v * m_uintScale;
}

66
sdrbase/util/fits.h Normal file
View File

@ -0,0 +1,66 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_UTIL_FITS_H
#define INCLUDE_UTIL_FITS_H
#include <QString>
#include <QByteArray>
#include "export.h"
// Flexible Image Transport System
// Specification: https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf
// This just implements the minimum functionality used by the files
// used by the StarTracker plugin
class SDRBASE_API FITS {
bool m_valid;
int m_width; // In pixels
int m_height; // In pixels
int m_bitsPerPixel; // BITPIX=-32 means single precision floating point
int m_bytesPerPixel;
int m_bzero;
double m_bscale;
QString m_buint; // "K TB" "MILLIK TB" "K"
float m_uintScale;
double m_cdelta1; // How many degrees RA per horizontal pixel
double m_cdelta2; // How many degrees Declination per vertical pixel
int m_dataStart;
QByteArray m_data;
public:
FITS(QString resourceName);
float value(int x, int y);
float scaledValue(int x, int y);
float scaledWrappedValue(int x, int y);
double degreesPerPixelH() { return m_cdelta1; }
double degreesPerPixelV() { return m_cdelta2; }
int width() { return m_width; }
int height() { return m_height; }
bool valid() { return m_valid; }
protected:
int mod(int a, int b);
};
#endif // INCLUDE_UTIL_FITS_H

View File

@ -18,6 +18,8 @@
#include "httpdownloadmanager.h"
#include <QDebug>
#include <QFile>
#include <QDateTime>
HttpDownloadManager::HttpDownloadManager()
{
@ -34,10 +36,32 @@ QNetworkReply *HttpDownloadManager::download(const QUrl &url, const QString &fil
qDebug() << "HttpDownloadManager: Downloading from " << url << " to " << filename;
downloads.append(reply);
filenames.append(filename);
return reply;
}
qint64 HttpDownloadManager::fileAgeInDays(const QString& filename)
{
QFile file(filename);
if (file.exists())
{
QDateTime modified = file.fileTime(QFileDevice::FileModificationTime);
if (modified.isValid())
return modified.daysTo(QDateTime::currentDateTime());
else
return -1;
}
return -1;
}
// Get default directory to write downloads to
QString HttpDownloadManager::downloadDir()
{
// Get directory to store app data in
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
// First dir is writable
return locations[0];
}
void HttpDownloadManager::sslErrors(const QList<QSslError> &sslErrors)
{
for (const QSslError &error : sslErrors)

View File

@ -33,6 +33,11 @@ public:
HttpDownloadManager();
QNetworkReply *download(const QUrl &url, const QString &filename);
static QString downloadDir();
protected:
static qint64 fileAgeInDays(const QString& filename);
private:
QNetworkAccessManager manager;
QVector<QNetworkReply *> downloads;

View File

@ -71,14 +71,16 @@ public:
return (int)std::round(feetPerMinToMetresPerSecond(fpm));
}
static float degreesToRadians(float degrees)
template <class T>
static T degreesToRadians(T degrees)
{
return degrees * ((float)M_PI) / 180.0f;
return degrees * ((T)M_PI) / 180.0f;
}
static float radiansToDegrees(float radians)
template <class T>
static T radiansToDegrees(T radians)
{
return radians * 180.0f / ((float)M_PI);
return radians * 180.0f / ((T)M_PI);
}
static float fahrenheitToCelsius(float fahrenheit)
@ -218,7 +220,8 @@ public:
return sfu * 10000.0f;
}
static float solarFluxUnitsToWattsPerMetrePerHertz(float sfu)
template <class T>
static T solarFluxUnitsToWattsPerMetrePerHertz(T sfu)
{
return sfu * 1e-22f;
}

View File

@ -44,6 +44,7 @@ set(sdrgui_SOURCES
gui/glshadertvarray.cpp
gui/glspectrum.cpp
gui/glspectrumgui.cpp
gui/httpdownloadmanagergui.cpp
gui/indicator.cpp
gui/levelmeter.cpp
gui/loggingdialog.cpp
@ -128,6 +129,7 @@ set(sdrgui_HEADERS
gui/glshadertextured.h
gui/glspectrum.h
gui/glspectrumgui.h
gui/httpdownloadmanagergui.h
gui/indicator.h
gui/levelmeter.h
gui/loggingdialog.h

View File

@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "httpdownloadmanagergui.h"
#include <QDebug>
#include <QProgressDialog>
#include <QMessageBox>
HttpDownloadManagerGUI::HttpDownloadManagerGUI()
{
connect(this, &HttpDownloadManager::downloadComplete, this, &HttpDownloadManagerGUI::downloadCompleteGUI);
}
QNetworkReply *HttpDownloadManagerGUI::download(const QUrl &url, const QString &filename, QWidget *parent)
{
QNetworkReply *reply = HttpDownloadManager::download(url, filename);
if (parent != nullptr)
{
QProgressDialog *progressDialog = new QProgressDialog(parent);
progressDialog->setAttribute(Qt::WA_DeleteOnClose);
progressDialog->setCancelButton(nullptr);
progressDialog->setMinimumDuration(500);
progressDialog->setLabelText(QString("Downloading %1.").arg(url.toString()));
m_progressDialogs.append(progressDialog);
connect(reply, &QNetworkReply::downloadProgress, this, [progressDialog](qint64 bytesRead, qint64 totalBytes) {
progressDialog->setMaximum(totalBytes);
progressDialog->setValue(bytesRead);
});
}
else
m_progressDialogs.append(nullptr);
return reply;
}
// Confirm if user wants to re-download an existing file
bool HttpDownloadManagerGUI::confirmDownload(const QString& filename, QWidget *parent, int maxAge)
{
qint64 age = HttpDownloadManager::fileAgeInDays(filename);
if ((age == -1) || (age > maxAge))
return true;
else
{
QMessageBox::StandardButton reply;
if (age == 0)
reply = QMessageBox::question(parent, "Confirm download", "This file was last downloaded today. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
else if (age == 1)
reply = QMessageBox::question(parent, "Confirm download", "This file was last downloaded yesterday. Are you sure you wish to redownload it?", QMessageBox::Yes|QMessageBox::No);
else
reply = QMessageBox::question(parent, "Confirm download", QString("This file was last downloaded %1 days ago. Are you sure you wish to redownload this file?").arg(age), QMessageBox::Yes|QMessageBox::No);
return reply == QMessageBox::Yes;
}
}
void HttpDownloadManagerGUI::downloadCompleteGUI(const QString& filename, bool success)
{
int idx = m_filenames.indexOf(filename);
if (idx >= 0)
{
QProgressDialog *progressDialog = m_progressDialogs[idx];
if (progressDialog != nullptr)
progressDialog->close();
m_filenames.remove(idx);
m_progressDialogs.remove(idx);
}
}

View File

@ -0,0 +1,45 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_HTTPDOWNLOADMANAGERGUI_H
#define INCLUDE_HTTPDOWNLOADMANAGERGUI_H
#include "util/httpdownloadmanager.h"
#include "export.h"
class QWidget;
class QProgressDialog;
// Class to download files via http and write them to disk with progress dialog
class SDRGUI_API HttpDownloadManagerGUI : public HttpDownloadManager
{
public:
HttpDownloadManagerGUI();
QNetworkReply *download(const QUrl &url, const QString &filename, QWidget *parent = nullptr);
static bool confirmDownload(const QString& filename, QWidget *parent = nullptr, int maxAge = 100);
private:
QVector<QString> m_filenames;
QVector<QProgressDialog *> m_progressDialogs;
public slots:
void downloadCompleteGUI(const QString &filename, bool success);
};
#endif /* INCLUDE_HTTPDOWNLOADMANAGERGUI_H */