mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-09-04 06:07:49 -04:00
Map: Add Aurora prediction and Maidenhead grid
This commit is contained in:
parent
1aeac6e268
commit
6c9dd36f8b
@ -177,6 +177,24 @@ void CesiumInterface::showfoF2(bool show)
|
||||
send(obj);
|
||||
}
|
||||
|
||||
void CesiumInterface::showMagDec(bool show)
|
||||
{
|
||||
QJsonObject obj {
|
||||
{"command", "showMagneticDeclination"},
|
||||
{"show", show}
|
||||
};
|
||||
send(obj);
|
||||
}
|
||||
|
||||
void CesiumInterface::showMaidenheadGrid(bool show)
|
||||
{
|
||||
QJsonObject obj {
|
||||
{"command", "showMaidenheadGrid"},
|
||||
{"show", show}
|
||||
};
|
||||
send(obj);
|
||||
}
|
||||
|
||||
void CesiumInterface::showLayer(const QString& layer, bool show)
|
||||
{
|
||||
QJsonObject obj {
|
||||
|
@ -67,6 +67,8 @@ public:
|
||||
void setAntiAliasing(const QString &antiAliasing);
|
||||
void showMUF(bool show);
|
||||
void showfoF2(bool show);
|
||||
void showMagDec(bool show);
|
||||
void showMaidenheadGrid(bool show);
|
||||
void showLayer(const QString& layer, bool show);
|
||||
void setLayerSettings(const QString& layer, const QStringList& settings, const QList<QVariant>& values);
|
||||
void updateImage(const QString &name, float east, float west, float north, float south, float altitude, const QString &data);
|
||||
|
BIN
plugins/feature/map/icons/aurora.png
Normal file
BIN
plugins/feature/map/icons/aurora.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
BIN
plugins/feature/map/icons/grid.png
Normal file
BIN
plugins/feature/map/icons/grid.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
@ -27,6 +27,8 @@
|
||||
<file>map/waypoint.png</file>
|
||||
<file>map/pager.png</file>
|
||||
<file>map/map3d.html</file>
|
||||
<file>map/cockpit.js</file>
|
||||
<file>map/grid.js</file>
|
||||
<file>data/transmitters.csv</file>
|
||||
</qresource>
|
||||
<qresource prefix="/">
|
||||
|
165
plugins/feature/map/map/grid.js
Normal file
165
plugins/feature/map/map/grid.js
Normal file
@ -0,0 +1,165 @@
|
||||
// Maidenhead grid for Cesium
|
||||
|
||||
const polylines = new Cesium.PolylineCollection();
|
||||
const polylinesFine = new Cesium.PolylineCollection();
|
||||
|
||||
const labels = new Cesium.LabelCollection();
|
||||
const labelsFine = new Cesium.LabelCollection();
|
||||
|
||||
const segLen = 2;
|
||||
|
||||
const gridMaterial = Cesium.Material.fromType('Color');
|
||||
gridMaterial.uniforms.color = new Cesium.Color(0.6, 0.6, 0.6, 1.0);
|
||||
const gridLabelColor = new Cesium.Color(1, 1, 1, 1.0);
|
||||
|
||||
function createLat() {
|
||||
for (var lon = -180; lon < 180; lon += 20) {
|
||||
const array = [];
|
||||
for (var lat = -90; lat <= 90; lat += segLen) {
|
||||
array.push(lon);
|
||||
array.push(lat);
|
||||
}
|
||||
polylines.add({
|
||||
positions: Cesium.Cartesian3.fromDegreesArray(array),
|
||||
width: 1,
|
||||
material: gridMaterial
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function createLatFine() {
|
||||
for (var lon = -180; lon < 180; lon += 2) {
|
||||
if (lon % 20 != 0) {
|
||||
const array = [];
|
||||
for (var lat = -90; lat <= 90; lat += segLen) {
|
||||
array.push(lon);
|
||||
array.push(lat);
|
||||
}
|
||||
polylinesFine.add({
|
||||
positions: Cesium.Cartesian3.fromDegreesArray(array),
|
||||
width: 1,
|
||||
material: gridMaterial
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLon() {
|
||||
for (var lat = -80; lat <= 80; lat += 10) {
|
||||
const array = [];
|
||||
for (var lon = -180; lon <= 180; lon += segLen) {
|
||||
array.push(lon);
|
||||
array.push(lat);
|
||||
}
|
||||
polylines.add({
|
||||
positions: Cesium.Cartesian3.fromDegreesArray(array),
|
||||
width: 1,
|
||||
material: gridMaterial
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createLonFine() {
|
||||
for (var lat = -80; lat <= 80; lat += 1) {
|
||||
if (lat % 10 != 0) {
|
||||
const array = [];
|
||||
for (var lon = -180; lon <= 180; lon += segLen) {
|
||||
array.push(lon);
|
||||
array.push(lat);
|
||||
}
|
||||
polylinesFine.add({
|
||||
positions: Cesium.Cartesian3.fromDegreesArray(array),
|
||||
width: 1,
|
||||
material: gridMaterial
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createLabels() {
|
||||
var latField = 'B'.charCodeAt(0);
|
||||
for (var lat = -80; lat <= 80; lat += 10) {
|
||||
var lonField = 'A'.charCodeAt(0);
|
||||
for (var lon = -180; lon < 180; lon += 20) {
|
||||
labels.add({
|
||||
position: Cesium.Cartesian3.fromDegrees(lon + 0.5, lat + 0.5, 0.0),
|
||||
text: String.fromCharCode(lonField) + String.fromCharCode(latField),
|
||||
fillColor: gridLabelColor,
|
||||
font: '20px sans-serif'
|
||||
});
|
||||
lonField++;
|
||||
}
|
||||
latField++;
|
||||
}
|
||||
}
|
||||
|
||||
function createLabelsFine() {
|
||||
var latField = 'B'.charCodeAt(0);
|
||||
for (var lat = -80; lat <= 80; lat += 1) {
|
||||
var latSq = '0'.charCodeAt(0) + mod(lat, 10);
|
||||
var lonField = 'A'.charCodeAt(0);
|
||||
for (var lon = -180; lon < 180; lon += 2) {
|
||||
var lonSq = '0'.charCodeAt(0) + mod(lon, 20) / 2;
|
||||
labelsFine.add({
|
||||
position: Cesium.Cartesian3.fromDegrees(lon + 0.05, lat + 0.05, 0.0),
|
||||
text: String.fromCharCode(lonField) + String.fromCharCode(latField) + String.fromCharCode(lonSq) + String.fromCharCode(latSq),
|
||||
fillColor: gridLabelColor,
|
||||
font: '20px sans-serif'
|
||||
});
|
||||
if (mod(lon, 20) == 18) {
|
||||
lonField++;
|
||||
}
|
||||
}
|
||||
if (mod(lat, 10) == 9) {
|
||||
latField++;
|
||||
}
|
||||
}
|
||||
}
|
||||
function mod(n, m) {
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
|
||||
function showGridByDistance() {
|
||||
|
||||
var cameraHeight = viewer.scene.ellipsoid.cartesianToCartographic(viewer.scene.camera.position).height;
|
||||
|
||||
const showFine = cameraHeight < 1500000;
|
||||
|
||||
polylinesFine.show = showFine;
|
||||
labels.show = !showFine;
|
||||
labelsFine.show = showFine;
|
||||
}
|
||||
|
||||
function createGrid(viewer) {
|
||||
|
||||
createLatFine();
|
||||
createLat();
|
||||
createLonFine();
|
||||
createLon();
|
||||
polylinesFine.show = false;
|
||||
viewer.scene.primitives.add(polylines);
|
||||
viewer.scene.primitives.add(polylinesFine);
|
||||
|
||||
createLabels();
|
||||
createLabelsFine();
|
||||
labelsFine.show = false;
|
||||
viewer.scene.primitives.add(labels);
|
||||
viewer.scene.primitives.add(labelsFine);
|
||||
}
|
||||
|
||||
function showGrid(show) {
|
||||
if (show) {
|
||||
if (polylines.length == 0) {
|
||||
createGrid(viewer);
|
||||
}
|
||||
polylines.show = true;
|
||||
viewer.clock.onTick.addEventListener(showGridByDistance);
|
||||
} else {
|
||||
viewer.clock.onTick.removeEventListener(showGridByDistance);
|
||||
polylines.show = false;
|
||||
polylinesFine.show = false;
|
||||
labels.show = false;
|
||||
labelsFine.show = false;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
</head>
|
||||
<body style="margin:0;padding:0">
|
||||
<div id="cesiumContainer"></div>
|
||||
<script>
|
||||
<script src="grid.js"></script>
|
||||
|
||||
// See: https://community.cesium.com/t/how-to-run-an-animation-for-an-entity-model/16932
|
||||
function getActiveAnimations(viewer, entity) {
|
||||
|
187
plugins/feature/map/map/wmm.geojson
Normal file
187
plugins/feature/map/map/wmm.geojson
Normal file
File diff suppressed because one or more lines are too long
44
plugins/feature/map/map/wmm.qmd
Normal file
44
plugins/feature/map/map/wmm.qmd
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
|
||||
<qgis version="3.42.1-Münster">
|
||||
<identifier>D_2025</identifier>
|
||||
<parentidentifier></parentidentifier>
|
||||
<language>ENG</language>
|
||||
<type>dataset</type>
|
||||
<title>D_2025</title>
|
||||
<abstract></abstract>
|
||||
<contact>
|
||||
<name></name>
|
||||
<organization></organization>
|
||||
<position></position>
|
||||
<voice></voice>
|
||||
<fax></fax>
|
||||
<email></email>
|
||||
<role></role>
|
||||
</contact>
|
||||
<links/>
|
||||
<dates/>
|
||||
<fees></fees>
|
||||
<encoding></encoding>
|
||||
<crs>
|
||||
<spatialrefsys nativeFormat="Wkt">
|
||||
<wkt>GEOGCRS["WGS 84",ENSEMBLE["World Geodetic System 1984 ensemble",MEMBER["World Geodetic System 1984 (Transit)"],MEMBER["World Geodetic System 1984 (G730)"],MEMBER["World Geodetic System 1984 (G873)"],MEMBER["World Geodetic System 1984 (G1150)"],MEMBER["World Geodetic System 1984 (G1674)"],MEMBER["World Geodetic System 1984 (G1762)"],MEMBER["World Geodetic System 1984 (G2139)"],MEMBER["World Geodetic System 1984 (G2296)"],ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]],ENSEMBLEACCURACY[2.0]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["Horizontal component of 3D system."],AREA["World."],BBOX[-90,-180,90,180]],ID["EPSG",4326]]</wkt>
|
||||
<proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
|
||||
<srsid>3452</srsid>
|
||||
<srid>4326</srid>
|
||||
<authid>EPSG:4326</authid>
|
||||
<description>WGS 84</description>
|
||||
<projectionacronym>longlat</projectionacronym>
|
||||
<ellipsoidacronym>EPSG:7030</ellipsoidacronym>
|
||||
<geographicflag>true</geographicflag>
|
||||
</spatialrefsys>
|
||||
</crs>
|
||||
<extent>
|
||||
<spatial crs="EPSG:4326" minx="179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368" maxy="-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368" miny="179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368" minz="0" maxx="-179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368" dimensions="2" maxz="0"/>
|
||||
<temporal>
|
||||
<period>
|
||||
<start></start>
|
||||
<end></end>
|
||||
</period>
|
||||
</temporal>
|
||||
</extent>
|
||||
</qgis>
|
@ -361,6 +361,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
addNAT();
|
||||
addRadioTimeTransmitters();
|
||||
addRadar();
|
||||
addAurora();
|
||||
addIonosonde();
|
||||
addBroadcast();
|
||||
addNavAids();
|
||||
@ -813,6 +814,14 @@ void MapGUI::sdrangelServerUpdated(const QList<SDRangelServerList::SDRangelServe
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::addAurora()
|
||||
{
|
||||
m_aurora = Aurora::create();
|
||||
if (m_aurora) {
|
||||
connect(m_aurora, &Aurora::dataUpdated, this, &MapGUI::auroraUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
// Ionosonde stations
|
||||
void MapGUI::addIonosonde()
|
||||
{
|
||||
@ -901,7 +910,7 @@ void MapGUI::updateGIRO(const QDateTime& mapDateTime)
|
||||
if (m_giroRunId.isEmpty() || (!giroRunId.isEmpty() && (giroRunId != m_giroRunId)))
|
||||
{
|
||||
m_giro->getMUF(giroRunId);
|
||||
m_giro->getMUF(giroRunId);
|
||||
m_giro->getfoF2(giroRunId);
|
||||
m_giroRunId = giroRunId;
|
||||
m_giroDateTime = mapDateTime;
|
||||
}
|
||||
@ -909,6 +918,20 @@ void MapGUI::updateGIRO(const QDateTime& mapDateTime)
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::auroraUpdated(const QImage& image)
|
||||
{
|
||||
//QString filename = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)[0] + "/aurora.png";
|
||||
//image.save(filename);
|
||||
QByteArray ba;
|
||||
QBuffer buffer(&ba);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
image.save(&buffer, "PNG");
|
||||
m_webServer->addFile("/map/map/aurora.png", ba);
|
||||
if (m_cesium) {
|
||||
m_cesium->setLayerSettings("aurora", {"show"}, {m_settings.m_displayAurora});
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::pathUpdated(const QString& radarPath, const QString& satellitePath)
|
||||
{
|
||||
m_radarPath = radarPath;
|
||||
@ -1814,7 +1837,9 @@ void MapGUI::displayToolbar()
|
||||
ui->displayNASAGlobalImagery->setVisible(overlayButtons);
|
||||
ui->displayMUF->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->displayfoF2->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->displayAurora->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->displayMagDec->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->displayMaidenheadGrid->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->save->setVisible(m_settings.m_map3DEnabled);
|
||||
}
|
||||
|
||||
@ -1925,11 +1950,13 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
|
||||
m_cesium->showMUF(m_settings.m_displayMUF);
|
||||
m_cesium->showfoF2(m_settings.m_displayfoF2);
|
||||
m_cesium->showMagDec(m_settings.m_displayMagDec);
|
||||
m_cesium->showMaidenheadGrid(m_settings.m_displayMaidenheadGrid);
|
||||
m_cesium->showLayer("rain", m_settings.m_displayRain);
|
||||
m_cesium->showLayer("clouds", m_settings.m_displayClouds);
|
||||
m_cesium->showLayer("seaMarks", m_settings.m_displaySeaMarks);
|
||||
m_cesium->showLayer("railways", m_settings.m_displayRailways);
|
||||
m_cesium->showLayer("nasaGlobalImagery", m_settings.m_displayNASAGlobalImagery);
|
||||
m_cesium->showLayer("aurora", m_settings.m_displayAurora);
|
||||
applyNASAGlobalImagerySettings();
|
||||
m_objectMapModel.allUpdated();
|
||||
m_imageMapModel.allUpdated();
|
||||
@ -1941,9 +1968,15 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
|
||||
if (ionosondeItemSettings) {
|
||||
m_giro->getDataPeriodically(ionosondeItemSettings->m_enabled ? 2 : 0);
|
||||
}
|
||||
if (m_aurora) {
|
||||
m_aurora->getDataPeriodically(m_settings.m_displayAurora ? 30 : 0);
|
||||
}
|
||||
#else
|
||||
ui->displayMUF->setVisible(false);
|
||||
ui->displayfoF2->setVisible(false);
|
||||
ui->displayAurora->setVisible(false);
|
||||
ui->displayMagDec->setVisible(false);
|
||||
ui->displayMaidenheadGrid->setVisible(false);
|
||||
m_objectMapModel.allUpdated();
|
||||
m_imageMapModel.allUpdated();
|
||||
m_polygonMapModel.allUpdated();
|
||||
@ -2015,11 +2048,16 @@ void MapGUI::init3DMap()
|
||||
m_cesium->showMUF(m_settings.m_displayMUF);
|
||||
m_cesium->showfoF2(m_settings.m_displayfoF2);
|
||||
m_cesium->showMagDec(m_settings.m_displayMagDec);
|
||||
m_cesium->showMaidenheadGrid(m_settings.m_displayMaidenheadGrid);
|
||||
|
||||
m_cesium->setViewFirstPerson(m_settings.m_viewFirstPerson);
|
||||
m_cesium->showPFD(m_settings.m_displayPFD);
|
||||
|
||||
m_cesium->showLayer("rain", m_settings.m_displayRain);
|
||||
m_cesium->showLayer("clouds", m_settings.m_displayClouds);
|
||||
m_cesium->showLayer("seaMarks", m_settings.m_displaySeaMarks);
|
||||
m_cesium->showLayer("railways", m_settings.m_displayRailways);
|
||||
m_cesium->showLayer("aurora", m_settings.m_displayAurora);
|
||||
applyNASAGlobalImagerySettings();
|
||||
|
||||
if (!m_radarPath.isEmpty()) {
|
||||
@ -2066,7 +2104,9 @@ void MapGUI::displaySettings()
|
||||
m_displayMUF->setChecked(m_settings.m_displayMUF);
|
||||
ui->displayfoF2->setChecked(m_settings.m_displayfoF2);
|
||||
m_displayfoF2->setChecked(m_settings.m_displayfoF2);
|
||||
ui->displayAurora->setChecked(m_settings.m_displayAurora);
|
||||
ui->displayMagDec->setChecked(m_settings.m_displayMagDec);
|
||||
ui->displayMaidenheadGrid->setChecked(m_settings.m_displayMaidenheadGrid);
|
||||
m_objectMapModel.setDisplayNames(m_settings.m_displayNames);
|
||||
m_objectMapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks);
|
||||
m_objectMapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks);
|
||||
@ -2366,6 +2406,26 @@ void MapGUI::on_displayfoF2_clicked(bool checked)
|
||||
if (m_cesium && !m_settings.m_displayfoF2) {
|
||||
m_cesium->showfoF2(m_settings.m_displayfoF2);
|
||||
}
|
||||
void MapGUI::on_displayAurora_clicked(bool checked)
|
||||
{
|
||||
if (this->sender() != ui->displayAurora) {
|
||||
ui->displayAurora->setChecked(checked);
|
||||
}
|
||||
if (this->sender() != m_displayAurora) {
|
||||
m_displayAurora->setChecked(checked);
|
||||
}
|
||||
m_settings.m_displayAurora = checked;
|
||||
// Only call show if disabling, so we don't get two updates
|
||||
// (as getDataPeriodically results in a call to showLayer when the data is available)
|
||||
if (m_aurora) {
|
||||
m_aurora->getDataPeriodically(m_settings.m_displayAurora ? 30 : 0);
|
||||
}
|
||||
if (m_cesium && !m_settings.m_displayAurora) {
|
||||
m_cesium->showLayer("aurora", m_settings.m_displayAurora);
|
||||
}
|
||||
applySetting("displayAurora");
|
||||
}
|
||||
|
||||
void MapGUI::on_displayMagDec_clicked(bool checked)
|
||||
{
|
||||
if (this->sender() != ui->displayMagDec) {
|
||||
@ -2438,10 +2498,21 @@ void MapGUI::createLayersMenu()
|
||||
m_displayfoF2->setToolTip("Display F2 layer critical frequency contours");
|
||||
connect(m_displayfoF2, &QAction::triggered, this, &MapGUI::on_displayfoF2_clicked);
|
||||
|
||||
m_displayAurora = menu->addAction("Aurora");
|
||||
m_displayAurora->setCheckable(true);
|
||||
m_displayAurora->setToolTip("Display aurora prediction");
|
||||
connect(m_displayAurora, &QAction::triggered, this, &MapGUI::on_displayAurora_clicked);
|
||||
|
||||
m_displayMagDec = menu->addAction("Mag Dec");
|
||||
m_displayMagDec->setCheckable(true);
|
||||
m_displayMagDec->setToolTip("Display magnetic declination");
|
||||
connect(m_displayMagDec, &QAction::triggered, this, &MapGUI::on_displayMagDec_clicked);
|
||||
|
||||
m_displayMaidenheadGrid = menu->addAction("Maidenhead grid");
|
||||
m_displayMaidenheadGrid->setCheckable(true);
|
||||
m_displayMaidenheadGrid->setToolTip("Display magnetic declination");
|
||||
connect(m_displayMaidenheadGrid, &QAction::triggered, this, &MapGUI::on_displayMaidenheadGrid_clicked);
|
||||
|
||||
ui->layersMenu->setMenu(menu);
|
||||
}
|
||||
|
||||
@ -2916,7 +2987,9 @@ void MapGUI::makeUIConnections()
|
||||
QObject::connect(ui->nasaGlobalImageryOpacity, qOverload<int>(&QDial::valueChanged), this, &MapGUI::on_nasaGlobalImageryOpacity_valueChanged);
|
||||
QObject::connect(ui->displayMUF, &ButtonSwitch::clicked, this, &MapGUI::on_displayMUF_clicked);
|
||||
QObject::connect(ui->displayfoF2, &ButtonSwitch::clicked, this, &MapGUI::on_displayfoF2_clicked);
|
||||
QObject::connect(ui->displayAurora, &ButtonSwitch::clicked, this, &MapGUI::on_displayAurora_clicked);
|
||||
QObject::connect(ui->displayMagDec, &ButtonSwitch::clicked, this, &MapGUI::on_displayMagDec_clicked);
|
||||
QObject::connect(ui->displayMaidenheadGrid, &ButtonSwitch::clicked, this, &MapGUI::on_displayMaidenheadGrid_clicked);
|
||||
QObject::connect(ui->find, &QLineEdit::returnPressed, this, &MapGUI::on_find_returnPressed);
|
||||
QObject::connect(ui->maidenhead, &QToolButton::clicked, this, &MapGUI::on_maidenhead_clicked);
|
||||
QObject::connect(ui->save, &QToolButton::clicked, this, &MapGUI::on_save_clicked);
|
||||
|
@ -40,6 +40,7 @@
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/giro.h"
|
||||
#include "util/azel.h"
|
||||
#include "util/aurora.h"
|
||||
#include "util/openaip.h"
|
||||
#include "util/ourairportsdb.h"
|
||||
#include "util/waypoints.h"
|
||||
@ -173,6 +174,7 @@ public:
|
||||
void addRadioTimeTransmitters();
|
||||
void addNAT();
|
||||
void addRadar();
|
||||
void addAurora();
|
||||
void addIonosonde();
|
||||
void addBroadcast();
|
||||
void addDAB();
|
||||
@ -228,6 +230,7 @@ private:
|
||||
QDateTime m_giroDateTime;
|
||||
QString m_giroRunId;
|
||||
QHash<QString, IonosondeStation *> m_ionosondeStations;
|
||||
Aurora *m_aurora;
|
||||
QSharedPointer<const QList<NavAid *>> m_navAids;
|
||||
QSharedPointer<const QList<Airspace *>> m_airspaces;
|
||||
QSharedPointer<const QHash<int, AirportInformation *>> m_airportInfo;
|
||||
@ -252,7 +255,9 @@ private:
|
||||
QAction *m_displayNASAGlobalImagery;
|
||||
QAction *m_displayMUF;
|
||||
QAction *m_displayfoF2;
|
||||
QAction *m_displayAurora;
|
||||
QAction *m_displayMagDec;
|
||||
QAction *m_displayMaidenheadGrid;
|
||||
|
||||
QString m_radarPath;
|
||||
QString m_satellitePath;
|
||||
@ -331,7 +336,9 @@ private slots:
|
||||
void on_displayNASAGlobalImagery_clicked(bool checked=false);
|
||||
void on_nasaGlobalImageryIdentifier_currentIndexChanged(int index);
|
||||
void on_nasaGlobalImageryOpacity_valueChanged(int index);
|
||||
void on_displayAurora_clicked(bool checked=false);
|
||||
void on_displayMagDec_clicked(bool checked=false);
|
||||
void on_displayMaidenheadGrid_clicked(bool checked=false);
|
||||
void on_layersMenu_clicked();
|
||||
void on_find_returnPressed();
|
||||
void on_maidenhead_clicked();
|
||||
@ -367,6 +374,7 @@ private slots:
|
||||
void nasaGlobalImageryMetaDataUpdated(const NASAGlobalImagery::MetaData& metaData);
|
||||
void nasaGlobalImageryLegendAvailable(const QString& url, const QByteArray& data);
|
||||
void nasaGlobalImageryHTMLAvailable(const QString& url, const QByteArray& data);
|
||||
void auroraUpdated(const QImage& image);
|
||||
void navAidsUpdated();
|
||||
void airspacesUpdated();
|
||||
void airportsUpdated();
|
||||
|
@ -222,6 +222,7 @@ set(sdrbase_SOURCES
|
||||
util/ax25.cpp
|
||||
util/aprs.cpp
|
||||
util/astronomy.cpp
|
||||
util/aurora.cpp
|
||||
util/azel.cpp
|
||||
util/baudot.cpp
|
||||
util/callsign.cpp
|
||||
@ -474,6 +475,7 @@ set(sdrbase_HEADERS
|
||||
util/ais.h
|
||||
util/android.h
|
||||
util/aprsfi.h
|
||||
util/aurora.h
|
||||
util/aviationweather.h
|
||||
util/ax25.h
|
||||
util/aprs.h
|
||||
|
430
sdrbase/util/aurora.cpp
Normal file
430
sdrbase/util/aurora.cpp
Normal file
@ -0,0 +1,430 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2025 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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 "aurora.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkDiskCache>
|
||||
|
||||
// Green -> Yellow -> Red
|
||||
const unsigned char Aurora::m_colorMap[] = {
|
||||
0, 255, 0,
|
||||
1, 255, 0,
|
||||
3, 255, 0,
|
||||
5, 255, 0,
|
||||
7, 255, 0,
|
||||
9, 255, 0,
|
||||
11, 255, 0,
|
||||
13, 255, 0,
|
||||
15, 255, 0,
|
||||
17, 255, 0,
|
||||
19, 255, 0,
|
||||
21, 255, 0,
|
||||
23, 255, 0,
|
||||
25, 255, 0,
|
||||
27, 255, 0,
|
||||
29, 255, 0,
|
||||
31, 255, 0,
|
||||
33, 255, 0,
|
||||
35, 255, 0,
|
||||
37, 255, 0,
|
||||
39, 255, 0,
|
||||
41, 255, 0,
|
||||
43, 255, 0,
|
||||
45, 255, 0,
|
||||
47, 255, 0,
|
||||
49, 255, 0,
|
||||
51, 255, 0,
|
||||
53, 255, 0,
|
||||
55, 255, 0,
|
||||
57, 255, 0,
|
||||
59, 255, 0,
|
||||
61, 255, 0,
|
||||
63, 255, 0,
|
||||
65, 255, 0,
|
||||
67, 255, 0,
|
||||
69, 255, 0,
|
||||
71, 255, 0,
|
||||
73, 255, 0,
|
||||
75, 255, 0,
|
||||
77, 255, 0,
|
||||
79, 255, 0,
|
||||
81, 255, 0,
|
||||
83, 255, 0,
|
||||
85, 255, 0,
|
||||
87, 255, 0,
|
||||
89, 255, 0,
|
||||
91, 255, 0,
|
||||
93, 255, 0,
|
||||
95, 255, 0,
|
||||
97, 255, 0,
|
||||
99, 255, 0,
|
||||
101, 255, 0,
|
||||
103, 255, 0,
|
||||
105, 255, 0,
|
||||
107, 255, 0,
|
||||
109, 255, 0,
|
||||
111, 255, 0,
|
||||
113, 255, 0,
|
||||
115, 255, 0,
|
||||
117, 255, 0,
|
||||
119, 255, 0,
|
||||
121, 255, 0,
|
||||
123, 255, 0,
|
||||
125, 255, 0,
|
||||
127, 255, 0,
|
||||
129, 255, 0,
|
||||
131, 255, 0,
|
||||
133, 255, 0,
|
||||
135, 255, 0,
|
||||
137, 255, 0,
|
||||
139, 255, 0,
|
||||
141, 255, 0,
|
||||
143, 255, 0,
|
||||
145, 255, 0,
|
||||
147, 255, 0,
|
||||
149, 255, 0,
|
||||
151, 255, 0,
|
||||
153, 255, 0,
|
||||
155, 255, 0,
|
||||
157, 255, 0,
|
||||
159, 255, 0,
|
||||
161, 255, 0,
|
||||
163, 255, 0,
|
||||
165, 255, 0,
|
||||
167, 255, 0,
|
||||
169, 255, 0,
|
||||
171, 255, 0,
|
||||
173, 255, 0,
|
||||
175, 255, 0,
|
||||
177, 255, 0,
|
||||
179, 255, 0,
|
||||
181, 255, 0,
|
||||
183, 255, 0,
|
||||
185, 255, 0,
|
||||
187, 255, 0,
|
||||
189, 255, 0,
|
||||
191, 255, 0,
|
||||
193, 255, 0,
|
||||
195, 255, 0,
|
||||
197, 255, 0,
|
||||
199, 255, 0,
|
||||
201, 255, 0,
|
||||
203, 255, 0,
|
||||
205, 255, 0,
|
||||
207, 255, 0,
|
||||
209, 255, 0,
|
||||
211, 255, 0,
|
||||
213, 255, 0,
|
||||
215, 255, 0,
|
||||
217, 255, 0,
|
||||
219, 255, 0,
|
||||
221, 255, 0,
|
||||
223, 255, 0,
|
||||
225, 255, 0,
|
||||
227, 255, 0,
|
||||
229, 255, 0,
|
||||
231, 255, 0,
|
||||
233, 255, 0,
|
||||
235, 255, 0,
|
||||
237, 255, 0,
|
||||
239, 255, 0,
|
||||
241, 255, 0,
|
||||
243, 255, 0,
|
||||
245, 255, 0,
|
||||
247, 255, 0,
|
||||
249, 255, 0,
|
||||
251, 255, 0,
|
||||
253, 255, 0,
|
||||
255, 255, 0,
|
||||
255, 253, 0,
|
||||
255, 251, 0,
|
||||
255, 249, 0,
|
||||
255, 247, 0,
|
||||
255, 245, 0,
|
||||
255, 243, 0,
|
||||
255, 241, 0,
|
||||
255, 239, 0,
|
||||
255, 237, 0,
|
||||
255, 235, 0,
|
||||
255, 233, 0,
|
||||
255, 231, 0,
|
||||
255, 229, 0,
|
||||
255, 227, 0,
|
||||
255, 225, 0,
|
||||
255, 223, 0,
|
||||
255, 221, 0,
|
||||
255, 219, 0,
|
||||
255, 217, 0,
|
||||
255, 215, 0,
|
||||
255, 213, 0,
|
||||
255, 211, 0,
|
||||
255, 209, 0,
|
||||
255, 207, 0,
|
||||
255, 205, 0,
|
||||
255, 203, 0,
|
||||
255, 201, 0,
|
||||
255, 199, 0,
|
||||
255, 197, 0,
|
||||
255, 195, 0,
|
||||
255, 193, 0,
|
||||
255, 191, 0,
|
||||
255, 189, 0,
|
||||
255, 187, 0,
|
||||
255, 185, 0,
|
||||
255, 183, 0,
|
||||
255, 181, 0,
|
||||
255, 179, 0,
|
||||
255, 177, 0,
|
||||
255, 175, 0,
|
||||
255, 173, 0,
|
||||
255, 171, 0,
|
||||
255, 169, 0,
|
||||
255, 167, 0,
|
||||
255, 165, 0,
|
||||
255, 163, 0,
|
||||
255, 161, 0,
|
||||
255, 159, 0,
|
||||
255, 157, 0,
|
||||
255, 155, 0,
|
||||
255, 153, 0,
|
||||
255, 151, 0,
|
||||
255, 149, 0,
|
||||
255, 147, 0,
|
||||
255, 145, 0,
|
||||
255, 143, 0,
|
||||
255, 141, 0,
|
||||
255, 139, 0,
|
||||
255, 137, 0,
|
||||
255, 135, 0,
|
||||
255, 133, 0,
|
||||
255, 131, 0,
|
||||
255, 129, 0,
|
||||
255, 127, 0,
|
||||
255, 125, 0,
|
||||
255, 123, 0,
|
||||
255, 121, 0,
|
||||
255, 119, 0,
|
||||
255, 117, 0,
|
||||
255, 115, 0,
|
||||
255, 113, 0,
|
||||
255, 111, 0,
|
||||
255, 109, 0,
|
||||
255, 107, 0,
|
||||
255, 105, 0,
|
||||
255, 103, 0,
|
||||
255, 101, 0,
|
||||
255, 99, 0,
|
||||
255, 97, 0,
|
||||
255, 95, 0,
|
||||
255, 93, 0,
|
||||
255, 91, 0,
|
||||
255, 89, 0,
|
||||
255, 87, 0,
|
||||
255, 85, 0,
|
||||
255, 83, 0,
|
||||
255, 81, 0,
|
||||
255, 79, 0,
|
||||
255, 77, 0,
|
||||
255, 75, 0,
|
||||
255, 73, 0,
|
||||
255, 71, 0,
|
||||
255, 69, 0,
|
||||
255, 67, 0,
|
||||
255, 65, 0,
|
||||
255, 63, 0,
|
||||
255, 61, 0,
|
||||
255, 59, 0,
|
||||
255, 57, 0,
|
||||
255, 55, 0,
|
||||
255, 53, 0,
|
||||
255, 51, 0,
|
||||
255, 49, 0,
|
||||
255, 47, 0,
|
||||
255, 45, 0,
|
||||
255, 43, 0,
|
||||
255, 41, 0,
|
||||
255, 39, 0,
|
||||
255, 37, 0,
|
||||
255, 35, 0,
|
||||
255, 33, 0,
|
||||
255, 31, 0,
|
||||
255, 29, 0,
|
||||
255, 27, 0,
|
||||
255, 25, 0,
|
||||
255, 23, 0,
|
||||
255, 21, 0,
|
||||
255, 19, 0,
|
||||
255, 17, 0,
|
||||
255, 15, 0,
|
||||
255, 13, 0,
|
||||
255, 11, 0,
|
||||
255, 9, 0,
|
||||
255, 7, 0,
|
||||
255, 5, 0,
|
||||
255, 3, 0,
|
||||
255, 1, 0,
|
||||
};
|
||||
|
||||
Aurora::Aurora()
|
||||
{
|
||||
connect(&m_dataTimer, &QTimer::timeout, this, &Aurora::getData);
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, &QNetworkAccessManager::finished, this, &Aurora::handleReply);
|
||||
|
||||
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
QDir writeableDir(locations[0]);
|
||||
if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("aurora"))) {
|
||||
qDebug() << "Failed to create cache/aurora";
|
||||
}
|
||||
|
||||
m_cache = new QNetworkDiskCache();
|
||||
m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("giro"));
|
||||
m_cache->setMaximumCacheSize(100000000);
|
||||
m_networkManager->setCache(m_cache);
|
||||
}
|
||||
|
||||
Aurora::~Aurora()
|
||||
{
|
||||
disconnect(&m_dataTimer, &QTimer::timeout, this, &Aurora::getData);
|
||||
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &Aurora::handleReply);
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
Aurora* Aurora::create(const QString& service)
|
||||
{
|
||||
if (service == "noaa.gov")
|
||||
{
|
||||
return new Aurora();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::create: Unsupported service: " << service;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Aurora::getDataPeriodically(int periodInMins)
|
||||
{
|
||||
if (periodInMins > 0)
|
||||
{
|
||||
m_dataTimer.setInterval(periodInMins*60*1000);
|
||||
m_dataTimer.start();
|
||||
getData();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dataTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void Aurora::getData()
|
||||
{
|
||||
QUrl url(QString("https://services.swpc.noaa.gov/json/ovation_aurora_latest.json"));
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
void Aurora::handleReply(QNetworkReply* reply)
|
||||
{
|
||||
if (reply)
|
||||
{
|
||||
if (!reply->error())
|
||||
{
|
||||
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
QString fileName = reply->url().fileName();
|
||||
if (fileName == "ovation_aurora_latest.json")
|
||||
{
|
||||
handleJSON(document);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::handleReply: unexpected filename: " << fileName;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::handleReply: error: " << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::handleReply: reply is null";
|
||||
}
|
||||
}
|
||||
|
||||
void Aurora::handleJSON(QJsonDocument& document)
|
||||
{
|
||||
if (document.isObject())
|
||||
{
|
||||
QJsonObject obj = document.object();
|
||||
|
||||
if (obj.contains(QStringLiteral("coordinates")))
|
||||
{
|
||||
QJsonArray array = obj.value(QStringLiteral("coordinates")).toArray();
|
||||
|
||||
// Longitude: [0, 359]
|
||||
// Latitude: [-90, 90] including 0
|
||||
QImage image(360, 181, QImage::Format_ARGB32);
|
||||
image.fill(qRgba(0, 0, 0, 0));
|
||||
|
||||
for (auto valRef : array)
|
||||
{
|
||||
if (valRef.isArray())
|
||||
{
|
||||
QJsonArray coords = valRef.toArray();
|
||||
|
||||
if (coords.size() == 3)
|
||||
{
|
||||
int longitude = coords[0].toInt();
|
||||
int latitude = coords[1].toInt();
|
||||
int probabilty = coords[2].toInt();
|
||||
const int min = 5; // Don't display anything for <5% probabilty
|
||||
const int alphaMax = 180; // Always slightly transparent
|
||||
|
||||
if ((probabilty > min) && (std::abs(latitude) > 5)) // Ignore data around equator, as can incorrectly predict aurora
|
||||
{
|
||||
// Scale from 5%-100% to 256 entry colormap
|
||||
int index = (int) ((probabilty - min) / (100.0f - min) * 255.0f);
|
||||
// Make lowest probababilties a bit more transparent
|
||||
int alpha = index < 25 ? (int)((index / 25.0f) * alphaMax) : alphaMax;
|
||||
//qDebug() << probabilty << index << alpha;
|
||||
image.setPixel((longitude + 180) % 360, 180 - (latitude + 90), qRgba(m_colorMap[index*3], m_colorMap[index*3+1], m_colorMap[index*3+2], alpha));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::handleJSON: Expected coordinates array to be of length 3";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit dataUpdated(image);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::handleJSON: No coordinates";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "Aurora::handleJSON: Expected an object";
|
||||
}
|
||||
}
|
68
sdrbase/util/aurora.h
Normal file
68
sdrbase/util/aurora.h
Normal file
@ -0,0 +1,68 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2025 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// 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_AURORA_H
|
||||
#define INCLUDE_AURORA_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QTimer>
|
||||
#include <QJsonDocument>
|
||||
#include <QImage>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QNetworkDiskCache;
|
||||
|
||||
// Aurora prediction
|
||||
// Data from https://services.swpc.noaa.gov/
|
||||
class SDRBASE_API Aurora : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
Aurora();
|
||||
|
||||
public:
|
||||
|
||||
static Aurora* create(const QString& service="noaa.gov");
|
||||
|
||||
~Aurora();
|
||||
void getDataPeriodically(int periodInMins=30);
|
||||
|
||||
public slots:
|
||||
void getData();
|
||||
|
||||
private slots:
|
||||
void handleReply(QNetworkReply* reply);
|
||||
|
||||
signals:
|
||||
void dataUpdated(const QImage& data); // Called when new data available.
|
||||
|
||||
private:
|
||||
|
||||
void handleJSON(QJsonDocument& document);
|
||||
|
||||
QTimer m_dataTimer; // Timer for periodic updates
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkDiskCache *m_cache;
|
||||
|
||||
static const unsigned char m_colorMap[256*3];
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_AURORA_H */
|
Loading…
x
Reference in New Issue
Block a user