Map: Add support for Ionosonde stations

pull/1354/head
Jon Beniston 2022-07-20 17:41:11 +01:00
rodzic 2a1476bb29
commit 22a30b5ea0
21 zmienionych plików z 725 dodań i 47 usunięć

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 368 KiB

Wyświetl plik

@ -159,6 +159,24 @@ void CesiumInterface::setAntiAliasing(const QString &antiAliasing)
send(obj); send(obj);
} }
void CesiumInterface::showMUF(bool show)
{
QJsonObject obj {
{"command", "showMUF"},
{"show", show}
};
send(obj);
}
void CesiumInterface::showfoF2(bool show)
{
QJsonObject obj {
{"command", "showfoF2"},
{"show", show}
};
send(obj);
}
void CesiumInterface::updateImage(const QString &name, float east, float west, float north, float south, float altitude, const QString &data) void CesiumInterface::updateImage(const QString &name, float east, float west, float north, float south, float altitude, const QString &data)
{ {
QJsonObject obj { QJsonObject obj {

Wyświetl plik

@ -64,6 +64,8 @@ public:
void setCameraReferenceFrame(bool eci); void setCameraReferenceFrame(bool eci);
void setSunLight(bool useSunLight); void setSunLight(bool useSunLight);
void setAntiAliasing(const QString &antiAliasing); void setAntiAliasing(const QString &antiAliasing);
void showMUF(bool show);
void showfoF2(bool show);
void updateImage(const QString &name, float east, float west, float north, float south, float altitude, const QString &data); void updateImage(const QString &name, float east, float west, float north, float south, float altitude, const QString &data);
void removeImage(const QString &name); void removeImage(const QString &name);
void removeAllImages(); void removeAllImages();

Wyświetl plik

@ -75,13 +75,8 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
bool removeObj = false; bool removeObj = false;
bool fixedPosition = mapItem->m_fixedPosition; bool fixedPosition = mapItem->m_fixedPosition;
if (mapItem->m_image == "")
float displayDistanceMax = std::numeric_limits<float>::max(); {
QString image = mapItem->m_image;
if ((image == "antenna.png") || (image == "antennaam.png") || (image == "antennadab.png") || (image == "antennafm.png") || (image == "antennatime.png")) {
displayDistanceMax = 1000000;
}
if (image == "") {
// Need to remove this from the map // Need to remove this from the map
removeObj = true; removeObj = true;
} }
@ -212,12 +207,6 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
{"heightReference", heightReferences[mapItem->m_altitudeReference]}, {"heightReference", heightReferences[mapItem->m_altitudeReference]},
{"show", mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DPoint} {"show", mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DPoint}
}; };
// If clamping to ground, we need to disable depth test, so part of the point isn't clipped
// However, when the point isn't clamped to ground, we shouldn't use this, otherwise
// the point will become visible through the globe
if (mapItem->m_altitudeReference == 1) {
point.insert("disableDepthTestDistance", 100000000);
}
// Model // Model
QJsonArray node0Cartesian { QJsonArray node0Cartesian {
@ -276,8 +265,26 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
}; };
// Label // Label
// Prevent labels from being too cluttered when zoomed out
// FIXME: These values should come from mapItem or mapItemSettings
float displayDistanceMax = std::numeric_limits<float>::max();
if ((mapItem->m_group == "Beacons") || (mapItem->m_group == "AM") || (mapItem->m_group == "FM") || (mapItem->m_group == "DAB")) {
displayDistanceMax = 1000000;
} else if ((mapItem->m_group == "Station") || (mapItem->m_group == "Radar") || (mapItem->m_group == "Radio Time Transmitters")) {
displayDistanceMax = 10000000;
} else if (mapItem->m_group == "Ionosonde Stations") {
displayDistanceMax = 30000000;
}
QJsonArray labelPixelOffsetScaleArray {
1000000, 20, 10000000, 5
};
QJsonObject labelPixelOffsetScaleObject {
{"nearFarScalar", labelPixelOffsetScaleArray}
};
QJsonArray labelPixelOffsetArray { QJsonArray labelPixelOffsetArray {
20, 0 1, 0
}; };
QJsonObject labelPixelOffset { QJsonObject labelPixelOffset {
{"cartesian2", labelPixelOffsetArray} {"cartesian2", labelPixelOffsetArray}
@ -302,14 +309,13 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
{"show", m_settings->m_displayNames && mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DLabel}, {"show", m_settings->m_displayNames && mapItem->m_itemSettings->m_enabled && mapItem->m_itemSettings->m_display3DLabel},
{"scale", mapItem->m_itemSettings->m_3DLabelScale}, {"scale", mapItem->m_itemSettings->m_3DLabelScale},
{"pixelOffset", labelPixelOffset}, {"pixelOffset", labelPixelOffset},
{"pixelOffsetScaleByDistance", labelPixelOffsetScaleObject},
{"eyeOffset", labelEyeOffset}, {"eyeOffset", labelEyeOffset},
{"verticalOrigin", "BASELINE"}, {"verticalOrigin", "BASELINE"},
{"horizontalOrigin", "LEFT"}, {"horizontalOrigin", "LEFT"},
{"heightReference", heightReferences[mapItem->m_altitudeReference]}, {"heightReference", heightReferences[mapItem->m_altitudeReference]},
}; };
if (displayDistanceMax != std::numeric_limits<float>::max()) if (displayDistanceMax != std::numeric_limits<float>::max()) {
{
label.insert("disableDepthTestDistance", 100000000.0);
label.insert("distanceDisplayCondition", labelDistanceDisplayCondition); label.insert("distanceDisplayCondition", labelDistanceDisplayCondition);
} }
@ -323,9 +329,6 @@ QJsonObject CZML::update(MapItem *mapItem, bool isTarget, bool isSelected)
{"heightReference", heightReferences[mapItem->m_altitudeReference]}, {"heightReference", heightReferences[mapItem->m_altitudeReference]},
{"verticalOrigin", "BOTTOM"} // To stop it being cut in half when zoomed out {"verticalOrigin", "BOTTOM"} // To stop it being cut in half when zoomed out
}; };
if (mapItem->m_altitudeReference == 1) {
billboard.insert("disableDepthTestDistance", 100000000);
}
QJsonObject obj { QJsonObject obj {
{"id", id} // id must be unique {"id", id} // id must be unique

Wyświetl plik

@ -3,5 +3,7 @@
<file>icons/groundtracks.png</file> <file>icons/groundtracks.png</file>
<file>icons/clock.png</file> <file>icons/clock.png</file>
<file>icons/ibp.png</file> <file>icons/ibp.png</file>
<file>icons/muf.png</file>
<file>icons/fof2.png</file>
</qresource> </qresource>
</RCC> </RCC>

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 5.4 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 5.4 KiB

Wyświetl plik

@ -7,6 +7,7 @@
<file>map/antennadab.png</file> <file>map/antennadab.png</file>
<file>map/antennafm.png</file> <file>map/antennafm.png</file>
<file>map/antennaam.png</file> <file>map/antennaam.png</file>
<file>map/ionosonde.png</file>
<file>map/map3d.html</file> <file>map/map3d.html</file>
</qresource> </qresource>
<qresource prefix="/"> <qresource prefix="/">

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 71 KiB

Wyświetl plik

@ -157,11 +157,40 @@
geocoder: false, geocoder: false,
fullscreenButton: true, fullscreenButton: true,
navigationHelpButton: false, navigationHelpButton: false,
navigationInstructionsInitiallyVisible: false navigationInstructionsInitiallyVisible: false,
terrainProviderViewModels: [] // User should adjust terrain via dialog, so depthTestAgainstTerrain doesn't get set
}); });
viewer.scene.globe.depthTestAgainstTerrain = false; // So labels/points aren't clipped by terrain
var buildings = undefined; var buildings = undefined;
const images = new Map(); const images = new Map();
var mufGeoJSONStream = null;
var foF2GeoJSONStream = null;
// Generate HTML for MUF contour info box from properties in GeoJSON
function describeMUF(properties, nameProperty) {
let html = "";
if (properties.hasOwnProperty("level-value")) {
const value = properties["level-value"];
if (Cesium.defined(value)) {
html = `<p>MUF: ${value} MHz<p>MUF (Maximum Usable Frequency) is the highest frequency that will reflect from the ionosphere on a 3000km path`;
}
}
return html;
}
// Generate HTML for foF2 contour info box from properties in GeoJSON
function describefoF2(properties, nameProperty) {
let html = "";
if (properties.hasOwnProperty("level-value")) {
const value = properties["level-value"];
if (Cesium.defined(value)) {
html = `<p>foF2: ${value} MHz<p>foF2 (F2 region critical frequency) is the highest frequency that will be reflected vertically from the F2 ionosphere region`;
}
}
return html;
}
// Use CZML to stream data from Map plugin to Cesium // Use CZML to stream data from Map plugin to Cesium
var czmlStream = new Cesium.CzmlDataSource(); var czmlStream = new Cesium.CzmlDataSource();
@ -238,6 +267,7 @@
} else { } else {
console.log(`Unknown terrain ${command.terrain}`); console.log(`Unknown terrain ${command.terrain}`);
} }
viewer.scene.globe.depthTestAgainstTerrain = false; // So labels/points aren't clipped by terrain
} else if (command.command == "setBuildings") { } else if (command.command == "setBuildings") {
if (command.buildings == "None") { if (command.buildings == "None") {
if (buildings !== undefined) { if (buildings !== undefined) {
@ -274,6 +304,32 @@
} else { } else {
viewer.scene.postProcessStages.fxaa.enabled = false; viewer.scene.postProcessStages.fxaa.enabled = false;
} }
} else if (command.command == "showMUF") {
if (mufGeoJSONStream != null) {
viewer.dataSources.remove(mufGeoJSONStream, true);
mufGeoJSONStream = null;
}
if (command.show == true) {
viewer.dataSources.add(
Cesium.GeoJsonDataSource.load(
"muf.geojson",
{describe: describeMUF}
)
).then(function(dataSource) {mufGeoJSONStream = dataSource; });
}
} else if (command.command == "showfoF2") {
if (foF2GeoJSONStream != null) {
viewer.dataSources.remove(foF2GeoJSONStream, true);
foF2GeoJSONStream = null;
}
if (command.show == true) {
viewer.dataSources.add(
Cesium.GeoJsonDataSource.load(
"fof2.geojson",
{describe: describefoF2}
)
).then(function(dataSource) {foF2GeoJSONStream = dataSource; });
}
} else if (command.command == "updateImage") { } else if (command.command == "updateImage") {
// Textures on entities can flash white when changed: https://github.com/CesiumGS/cesium/issues/1640 // Textures on entities can flash white when changed: https://github.com/CesiumGS/cesium/issues/1640
@ -426,7 +482,6 @@
reportClock(); reportClock();
}; };
</script> </script>
</div> </div>
</body> </body>

Wyświetl plik

@ -275,6 +275,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
addRadioTimeTransmitters(); addRadioTimeTransmitters();
addRadar(); addRadar();
addIonosonde();
displaySettings(); displaySettings();
applySettings(true); applySettings(true);
@ -302,6 +303,7 @@ MapGUI::~MapGUI()
m_webServer->close(); m_webServer->close();
delete m_webServer; delete m_webServer;
} }
delete m_giro;
delete ui; delete ui;
} }
@ -452,6 +454,72 @@ void MapGUI::addRadar()
update(m_map, &radarMapItem, "Radar"); update(m_map, &radarMapItem, "Radar");
} }
// Ionosonde stations
void MapGUI::addIonosonde()
{
m_giro = GIRO::create();
if (m_giro)
{
connect(m_giro, &GIRO::dataUpdated, this, &MapGUI::giroDataUpdated);
connect(m_giro, &GIRO::mufUpdated, this, &MapGUI::mufUpdated);
connect(m_giro, &GIRO::foF2Updated, this, &MapGUI::foF2Updated);
}
}
void MapGUI::giroDataUpdated(const GIRO::GIROStationData& data)
{
if (!data.m_station.isEmpty())
{
IonosondeStation *station = nullptr;
// See if we already have the station in our hash
if (!m_ionosondeStations.contains(data.m_station))
{
// Create new station
station = new IonosondeStation(data);
m_ionosondeStations.insert(data.m_station, station);
}
else
{
station = m_ionosondeStations.value(data.m_station);
}
station->update(data);
// Add/update map
SWGSDRangel::SWGMapItem ionosondeStationMapItem;
ionosondeStationMapItem.setName(new QString(station->m_name));
ionosondeStationMapItem.setLatitude(station->m_latitude);
ionosondeStationMapItem.setLongitude(station->m_longitude);
ionosondeStationMapItem.setAltitude(0.0);
ionosondeStationMapItem.setImage(new QString("ionosonde.png"));
ionosondeStationMapItem.setImageRotation(0);
ionosondeStationMapItem.setText(new QString(station->m_text));
ionosondeStationMapItem.setModel(new QString("antenna.glb"));
ionosondeStationMapItem.setFixedPosition(true);
ionosondeStationMapItem.setOrientation(0);
ionosondeStationMapItem.setLabel(new QString(station->m_label));
ionosondeStationMapItem.setLabelAltitudeOffset(4.5);
ionosondeStationMapItem.setAltitudeReference(1);
update(m_map, &ionosondeStationMapItem, "Ionosonde Stations");
}
}
void MapGUI::mufUpdated(const QJsonDocument& document)
{
// Could possibly try render on 2D map, but contours
// that cross anti-meridian are not drawn properly
//${Qt5Location_PRIVATE_INCLUDE_DIRS}
//#include <QtLocation/private/qgeojson_p.h>
//QVariantList list = QGeoJson::importGeoJson(document);
m_webServer->addFile("/map/map/muf.geojson", document.toJson());
m_cesium->showMUF(m_settings.m_displayMUF);
}
void MapGUI::foF2Updated(const QJsonDocument& document)
{
m_webServer->addFile("/map/map/fof2.geojson", document.toJson());
m_cesium->showfoF2(m_settings.m_displayfoF2);
}
static QString arrayToString(QJsonArray array) static QString arrayToString(QJsonArray array)
{ {
QString s; QString s;
@ -841,7 +909,15 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
m_cesium->setCameraReferenceFrame(m_settings.m_eciCamera); m_cesium->setCameraReferenceFrame(m_settings.m_eciCamera);
m_cesium->setAntiAliasing(m_settings.m_antiAliasing); m_cesium->setAntiAliasing(m_settings.m_antiAliasing);
m_cesium->getDateTime(); m_cesium->getDateTime();
m_cesium->showMUF(m_settings.m_displayMUF);
m_cesium->showfoF2(m_settings.m_displayfoF2);
} }
MapSettings::MapItemSettings *ionosondeItemSettings = getItemSettings("Ionosonde Stations");
if (ionosondeItemSettings) {
m_giro->getDataPeriodically(ionosondeItemSettings->m_enabled ? 2 : 0);
}
m_giro->getMUFPeriodically(m_settings.m_displayMUF ? 15 : 0);
m_giro->getfoF2Periodically(m_settings.m_displayfoF2 ? 15 : 0);
} }
void MapGUI::init3DMap() void MapGUI::init3DMap()
@ -863,6 +939,9 @@ void MapGUI::init3DMap()
// Set 3D view after loading initial objects // Set 3D view after loading initial objects
m_cesium->setHomeView(stationLatitude, stationLongitude); m_cesium->setHomeView(stationLatitude, stationLongitude);
m_cesium->showMUF(m_settings.m_displayMUF);
m_cesium->showfoF2(m_settings.m_displayfoF2);
} }
void MapGUI::displaySettings() void MapGUI::displaySettings()
@ -874,6 +953,8 @@ void MapGUI::displaySettings()
ui->displayNames->setChecked(m_settings.m_displayNames); ui->displayNames->setChecked(m_settings.m_displayNames);
ui->displaySelectedGroundTracks->setChecked(m_settings.m_displaySelectedGroundTracks); ui->displaySelectedGroundTracks->setChecked(m_settings.m_displaySelectedGroundTracks);
ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks); ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks);
ui->displayMUF->setChecked(m_settings.m_displayMUF);
ui->displayfoF2->setChecked(m_settings.m_displayfoF2);
m_mapModel.setDisplayNames(m_settings.m_displayNames); m_mapModel.setDisplayNames(m_settings.m_displayNames);
m_mapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks); m_mapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks);
m_mapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks); m_mapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks);
@ -949,6 +1030,26 @@ void MapGUI::on_displayAllGroundTracks_clicked(bool checked)
m_mapModel.setDisplayAllGroundTracks(checked); m_mapModel.setDisplayAllGroundTracks(checked);
} }
void MapGUI::on_displayMUF_clicked(bool checked)
{
m_settings.m_displayMUF = checked;
// Only call show if disabling, so we don't get two updates
// (as getMUFPeriodically results in a call to showMUF when the data is available)
m_giro->getMUFPeriodically(m_settings.m_displayMUF ? 15 : 0);
if (m_cesium && !m_settings.m_displayMUF) {
m_cesium->showMUF(m_settings.m_displayMUF);
}
}
void MapGUI::on_displayfoF2_clicked(bool checked)
{
m_settings.m_displayfoF2 = checked;
m_giro->getfoF2Periodically(m_settings.m_displayfoF2 ? 15 : 0);
if (m_cesium && !m_settings.m_displayfoF2) {
m_cesium->showfoF2(m_settings.m_displayfoF2);
}
}
void MapGUI::on_find_returnPressed() void MapGUI::on_find_returnPressed()
{ {
find(ui->find->text().trimmed()); find(ui->find->text().trimmed());
@ -1234,6 +1335,8 @@ void MapGUI::makeUIConnections()
QObject::connect(ui->displayNames, &ButtonSwitch::clicked, this, &MapGUI::on_displayNames_clicked); QObject::connect(ui->displayNames, &ButtonSwitch::clicked, this, &MapGUI::on_displayNames_clicked);
QObject::connect(ui->displayAllGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displayAllGroundTracks_clicked); QObject::connect(ui->displayAllGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displayAllGroundTracks_clicked);
QObject::connect(ui->displaySelectedGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displaySelectedGroundTracks_clicked); QObject::connect(ui->displaySelectedGroundTracks, &ButtonSwitch::clicked, this, &MapGUI::on_displaySelectedGroundTracks_clicked);
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->find, &QLineEdit::returnPressed, this, &MapGUI::on_find_returnPressed); 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->maidenhead, &QToolButton::clicked, this, &MapGUI::on_maidenhead_clicked);
QObject::connect(ui->deleteAll, &QToolButton::clicked, this, &MapGUI::on_deleteAll_clicked); QObject::connect(ui->deleteAll, &QToolButton::clicked, this, &MapGUI::on_deleteAll_clicked);

Wyświetl plik

@ -29,6 +29,7 @@
#include "feature/featuregui.h" #include "feature/featuregui.h"
#include "util/messagequeue.h" #include "util/messagequeue.h"
#include "util/giro.h"
#include "util/azel.h" #include "util/azel.h"
#include "settings/rollupstate.h" #include "settings/rollupstate.h"
@ -62,6 +63,68 @@ struct RadioTimeTransmitter {
int m_power; // In kW int m_power; // In kW
}; };
struct IonosondeStation {
QString m_name;
float m_latitude; // In degrees
float m_longitude; // In degrees
QString m_text;
QString m_label;
IonosondeStation(const GIRO::GIROStationData& data) :
m_name(data.m_station)
{
update(data);
}
void update(const GIRO::GIROStationData& data)
{
m_latitude = data.m_latitude;
m_longitude = data.m_longitude;
QStringList text;
QStringList label;
text.append("Ionosonde Station");
text.append(QString("Name: %1").arg(m_name.split(",")[0]));
if (!isnan(data.m_mufd))
{
text.append(QString("MUF: %1 MHz").arg(data.m_mufd));
label.append(QString("%1").arg((int)round(data.m_mufd)));
}
else
{
label.append("-");
}
if (!isnan(data.m_md)) {
text.append(QString("M(D): %1").arg(data.m_md));
}
if (!isnan(data.m_foF2))
{
text.append(QString("foF2: %1 MHz").arg(data.m_foF2));
label.append(QString("%1").arg((int)round(data.m_foF2)));
}
else
{
label.append("-");
}
if (!isnan(data.m_hmF2)) {
text.append(QString("hmF2: %1 km").arg(data.m_hmF2));
}
if (!isnan(data.m_foE)) {
text.append(QString("foE: %1 MHz").arg(data.m_foE));
}
if (!isnan(data.m_tec)) {
text.append(QString("TEC: %1").arg(data.m_tec));
}
if (data.m_confidence >= 0) {
text.append(QString("Confidence: %1").arg(data.m_confidence));
}
if (data.m_dateTime.isValid()) {
text.append(data.m_dateTime.toString());
}
m_text = text.join("\n");
m_label = label.join("/");
}
};
class MapGUI : public FeatureGUI { class MapGUI : public FeatureGUI {
Q_OBJECT Q_OBJECT
public: public:
@ -86,6 +149,7 @@ public:
QList<RadioTimeTransmitter> getRadioTimeTransmitters() { return m_radioTimeTransmitters; } QList<RadioTimeTransmitter> getRadioTimeTransmitters() { return m_radioTimeTransmitters; }
void addRadioTimeTransmitters(); void addRadioTimeTransmitters();
void addRadar(); void addRadar();
void addIonosonde();
void addDAB(); void addDAB();
void find(const QString& target); void find(const QString& target);
void track3D(const QString& target); void track3D(const QString& target);
@ -114,6 +178,8 @@ private:
quint16 m_osmPort; quint16 m_osmPort;
OSMTemplateServer *m_templateServer; OSMTemplateServer *m_templateServer;
QTimer m_redrawMapTimer; QTimer m_redrawMapTimer;
GIRO *m_giro;
QHash<QString, IonosondeStation *> m_ionosondeStations;
CesiumInterface *m_cesium; CesiumInterface *m_cesium;
WebServer *m_webServer; WebServer *m_webServer;
@ -149,6 +215,8 @@ private slots:
void on_displayNames_clicked(bool checked=false); void on_displayNames_clicked(bool checked=false);
void on_displayAllGroundTracks_clicked(bool checked=false); void on_displayAllGroundTracks_clicked(bool checked=false);
void on_displaySelectedGroundTracks_clicked(bool checked=false); void on_displaySelectedGroundTracks_clicked(bool checked=false);
void on_displayMUF_clicked(bool checked=false);
void on_displayfoF2_clicked(bool checked=false);
void on_find_returnPressed(); void on_find_returnPressed();
void on_maidenhead_clicked(); void on_maidenhead_clicked();
void on_deleteAll_clicked(); void on_deleteAll_clicked();
@ -162,6 +230,9 @@ private slots:
virtual bool eventFilter(QObject *obj, QEvent *event); virtual bool eventFilter(QObject *obj, QEvent *event);
void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest); void fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest);
void preferenceChanged(int elementType); void preferenceChanged(int elementType);
void giroDataUpdated(const GIRO::GIROStationData& data);
void mufUpdated(const QJsonDocument& document);
void foF2Updated(const QJsonDocument& document);
}; };

Wyświetl plik

@ -39,13 +39,13 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>471</width> <width>480</width>
<height>41</height> <height>41</height>
</rect> </rect>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>350</width> <width>480</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -165,6 +165,46 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="ButtonSwitch" name="displayMUF">
<property name="toolTip">
<string>Display MUF contours</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/map/icons/muf.png</normaloff>:/map/icons/muf.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="displayfoF2">
<property name="toolTip">
<string>Display foF2 contours</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/map/icons/fof2.png</normaloff>:/map/icons/fof2.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="ButtonSwitch" name="displayNames"> <widget class="ButtonSwitch" name="displayNames">
<property name="toolTip"> <property name="toolTip">
@ -187,12 +227,6 @@
</item> </item>
<item> <item>
<widget class="ButtonSwitch" name="displaySelectedGroundTracks"> <widget class="ButtonSwitch" name="displaySelectedGroundTracks">
<property name="font">
<font>
<family>Adobe Devanagari</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Display ground tracks for selected item</string> <string>Display ground tracks for selected item</string>
</property> </property>
@ -213,12 +247,6 @@
</item> </item>
<item> <item>
<widget class="ButtonSwitch" name="displayAllGroundTracks"> <widget class="ButtonSwitch" name="displayAllGroundTracks">
<property name="font">
<font>
<family>Adobe Devanagari</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="toolTip"> <property name="toolTip">
<string>Display all ground tracks</string> <string>Display all ground tracks</string>
</property> </property>
@ -339,7 +367,7 @@
</url> </url>
</property> </property>
</widget> </widget>
<widget class="QWebEngineView" name="web"> <widget class="QWebEngineView" name="web" native="true">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>

Wyświetl plik

@ -69,6 +69,11 @@ MapSettings::MapSettings() :
m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", QColor(102, 0, 102), false, 11, modelMinPixelSize)); m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", QColor(102, 0, 102), false, 11, modelMinPixelSize));
m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", QColor(255, 0, 0), true, 8)); m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", QColor(255, 0, 0), true, 8));
m_itemSettings.insert("Radar", new MapItemSettings("Radar", QColor(255, 0, 0), true, 8)); m_itemSettings.insert("Radar", new MapItemSettings("Radar", QColor(255, 0, 0), true, 8));
MapItemSettings *ionosondeItemSettings = new MapItemSettings("Ionosonde Stations", QColor(255, 255, 0), true, 4);
ionosondeItemSettings->m_display2DIcon = false;
m_itemSettings.insert("Ionosonde Stations", ionosondeItemSettings);
MapItemSettings *stationItemSettings = new MapItemSettings("Station", QColor(255, 0, 0), true, 11); MapItemSettings *stationItemSettings = new MapItemSettings("Station", QColor(255, 0, 0), true, 11);
stationItemSettings->m_display2DTrack = false; stationItemSettings->m_display2DTrack = false;
m_itemSettings.insert("Station", stationItemSettings); m_itemSettings.insert("Station", stationItemSettings);
@ -110,6 +115,8 @@ void MapSettings::resetToDefaults()
m_eciCamera = false; m_eciCamera = false;
m_modelDir = HttpDownloadManager::downloadDir() + "/3d"; m_modelDir = HttpDownloadManager::downloadDir() + "/3d";
m_antiAliasing = "None"; m_antiAliasing = "None";
m_displayMUF = false;
m_displayfoF2 = false;
m_workspaceIndex = 0; m_workspaceIndex = 0;
} }
@ -152,6 +159,9 @@ QByteArray MapSettings::serialize() const
s.writeS32(33, m_workspaceIndex); s.writeS32(33, m_workspaceIndex);
s.writeBlob(34, m_geometryBytes); s.writeBlob(34, m_geometryBytes);
s.writeBool(35, m_displayMUF);
s.writeBool(36, m_displayfoF2);
return s.final(); return s.final();
} }
@ -224,6 +234,9 @@ bool MapSettings::deserialize(const QByteArray& data)
d.readS32(33, &m_workspaceIndex, 0); d.readS32(33, &m_workspaceIndex, 0);
d.readBlob(34, &m_geometryBytes); d.readBlob(34, &m_geometryBytes);
d.readBool(35, &m_displayMUF, false);
d.readBool(36, &m_displayfoF2, false);
return true; return true;
} }
else else

Wyświetl plik

@ -104,6 +104,9 @@ struct MapSettings
QString m_cesiumIonAPIKey; QString m_cesiumIonAPIKey;
QString m_antiAliasing; QString m_antiAliasing;
bool m_displayMUF; // Plot MUF contours
bool m_displayfoF2; // Plot foF2 contours
// Per source settings // Per source settings
QHash<QString, MapItemSettings *> m_itemSettings; QHash<QString, MapItemSettings *> m_itemSettings;

Wyświetl plik

@ -11,10 +11,14 @@ On top of this, it can plot data from other plugins, such as:
* Satellites from the Satellite Tracker, * Satellites from the Satellite Tracker,
* Weather imagery from APT Demodulator, * Weather imagery from APT Demodulator,
* The Sun, Moon and Stars from the Star Tracker, * The Sun, Moon and Stars from the Star Tracker,
* Weather ballons from the RadioSonde feature, * Weather ballons from the RadioSonde feature.
As well as other other data sources:
* Beacons based on the IARU Region 1 beacon database and International Beacon Project, * Beacons based on the IARU Region 1 beacon database and International Beacon Project,
* Radio time transmitters, * Radio time transmitters,
* GRAVES radar. * GRAVES radar,
* Ionosonde station data.
It can also create tracks showing the path aircraft, ships and APRS objects have taken, as well as predicted paths for satellites. It can also create tracks showing the path aircraft, ships and APRS objects have taken, as well as predicted paths for satellites.
@ -22,7 +26,7 @@ It can also create tracks showing the path aircraft, ships and APRS objects have
![3D Map feature](../../../doc/img/Map_plugin_apt.png) ![3D Map feature](../../../doc/img/Map_plugin_apt.png)
3D Models are not included with SDRangel. They must be downloaded by pressing the Download 3D Models button in the Display Settings dialog (11). 3D Models are not included with SDRangel. They must be downloaded by pressing the Download 3D Models button in the Display Settings dialog (13).
<h2>Interface</h2> <h2>Interface</h2>
@ -78,23 +82,33 @@ When clicked, opens the Radio Time Transmitters dialog.
![Radio Time transmitters dialog](../../../doc/img/Map_plugin_radiotime_dialog.png) ![Radio Time transmitters dialog](../../../doc/img/Map_plugin_radiotime_dialog.png)
<h3>7: Display Names</h3> <h3>7: Display MUF Contours</h3>
When checked, contours will be downloaded and displayed on the 3D map, showing the MUF (Maximum Usable Frequency) for a 3000km path that reflects off the ionosphere.
The contours will be updated every 15 minutes. The latest contour data will always be displayed, irrespective of the time set on the 3D Map.
<h3>8: Display coF2 Contours</h3>
When checked, contours will be downloaded and displayed on the 3D map, showing coF2 (F2 layer critical frequency), the maximum frequency at which radio waves will be reflected vertically from the F2 region of the ionosphere.
The contours will be updated every 15 minutes. The latest contour data will always be displayed, irrespective of the time set on the 3D Map.
<h3>8: Display Names</h3>
When checked, names of objects are displayed in a bubble next to each object. When checked, names of objects are displayed in a bubble next to each object.
<h3>8: Display tracks for selected object</h3> <h3>9: Display tracks for selected object</h3>
When checked, displays the track (taken or predicted) for the selected object. When checked, displays the track (taken or predicted) for the selected object.
<h3>9: Display tracks for all objects</h3> <h3>10: Display tracks for all objects</h3>
When checked, displays the track (taken or predicted) for the all objects. When checked, displays the track (taken or predicted) for the all objects.
<h3>10: Delete</h3> <h3>11: Delete</h3>
When clicked, all items will be deleted from the map. When clicked, all items will be deleted from the map.
<h3>11: Display settings</h3> <h3>12: Display settings</h3>
When clicked, opens the Map Display Settings dialog: When clicked, opens the Map Display Settings dialog:
@ -154,6 +168,25 @@ The 2D map will only display the last reported positions for objects.
The 3D map, however, has a timeline that allows replaying how objects have moved over time. The 3D map, however, has a timeline that allows replaying how objects have moved over time.
To the right of the timeline is the fullscreen toggle button, which allows the 3D map to be displayed fullscreen. To the right of the timeline is the fullscreen toggle button, which allows the 3D map to be displayed fullscreen.
<h4>Ionosonde Stations</h4>
When Ionosonde Stations are displayed, data is downloaded and displayed every 2 minutes. The data includes:
* MUF - Maximum Usable Frequency in MHz for 3000km path.
* M(D) - M-factor (~MUF/foF2) for 3000km path.
* foF2 - F2 region critical frequency in MHz.
* hmF2 - F2 region height in km.
* foE - E region critical frequency in MHz.
* TEC - Total Electron Content.
Each station is labelled on the maps as "MUF/foF2".
MUF and foF2 can be displayed as countors:
![MUF contours](../../../doc/img/Map_plugin_muf.png)
The contours can be clicked on which will display the data for that contour in the info box.
<h2>Attribution</h2> <h2>Attribution</h2>
IARU Region 1 beacon list used with permission from: https://iaru-r1-c5-beacons.org/ To add or update a beacon, see: https://iaru-r1-c5-beacons.org/index.php/beacon-update/ IARU Region 1 beacon list used with permission from: https://iaru-r1-c5-beacons.org/ To add or update a beacon, see: https://iaru-r1-c5-beacons.org/index.php/beacon-update/
@ -161,7 +194,10 @@ IARU Region 1 beacon list used with permission from: https://iaru-r1-c5-beacons.
Mapping and geolocation services are by Open Street Map: https://www.openstreetmap.org/ esri: https://www.esri.com/ Mapping and geolocation services are by Open Street Map: https://www.openstreetmap.org/ esri: https://www.esri.com/
Mapbox: https://www.mapbox.com/ Cesium: https://www.cesium.com Bing: https://www.bing.com/maps/ Mapbox: https://www.mapbox.com/ Cesium: https://www.cesium.com Bing: https://www.bing.com/maps/
Ionosonde data and MUF/coF2 contours from [KC2G](https://prop.kc2g.com/) with source data from [GIRO](https://giro.uml.edu/) and [NOAA NCEI](https://www.ngdc.noaa.gov/stp/iono/ionohome.html).
Icons made by Google from Flaticon https://www.flaticon.com Icons made by Google from Flaticon https://www.flaticon.com
World icons created by turkkub from Flaticon https://www.flaticon.com
3D models are by various artists under a variety of liceneses. See: https://github.com/srcejon/sdrangel-3d-models 3D models are by various artists under a variety of liceneses. See: https://github.com/srcejon/sdrangel-3d-models

Wyświetl plik

@ -37,6 +37,7 @@ WebServer::WebServer(quint16 &port, QObject* parent) :
m_mimeTypes.insert(".js", new MimeType("text/javascript")); m_mimeTypes.insert(".js", new MimeType("text/javascript"));
m_mimeTypes.insert(".css", new MimeType("text/css")); m_mimeTypes.insert(".css", new MimeType("text/css"));
m_mimeTypes.insert(".json", new MimeType("application/json")); m_mimeTypes.insert(".json", new MimeType("application/json"));
m_mimeTypes.insert(".geojson", new MimeType("application/geo+json"));
} }
void WebServer::incomingConnection(qintptr socket) void WebServer::incomingConnection(qintptr socket)
@ -88,6 +89,11 @@ QString WebServer::substitute(QString path, QString html)
return html; return html;
} }
void WebServer::addFile(const QString &path, const QByteArray &data)
{
m_files.insert(path, data);
}
void WebServer::sendFile(QTcpSocket* socket, const QByteArray &data, MimeType *mimeType, const QString &path) void WebServer::sendFile(QTcpSocket* socket, const QByteArray &data, MimeType *mimeType, const QString &path)
{ {
QString header = QString("HTTP/1.0 200 Ok\r\nContent-Type: %1\r\n\r\n").arg(mimeType->m_type); QString header = QString("HTTP/1.0 200 Ok\r\nContent-Type: %1\r\n\r\n").arg(mimeType->m_type);
@ -163,9 +169,14 @@ void WebServer::readClient()
sendFile(socket, data, mimeType, path); sendFile(socket, data, mimeType, path);
} }
#endif #endif
else if (m_files.contains(path))
{
// Path is a file held in memory
sendFile(socket, m_files.value(path).data(), mimeType, path);
}
else else
{ {
// See if we can find a file // See if we can find a file on disk
QFile file(path); QFile file(path);
if (file.open(QIODevice::ReadOnly)) if (file.open(QIODevice::ReadOnly))
{ {

Wyświetl plik

@ -49,12 +49,15 @@ class WebServer : public QTcpServer
private: private:
// Hash of a list of paths to substitude // Hash of a list of paths to substitute
QHash<QString, QString> m_pathSubstitutions; QHash<QString, QString> m_pathSubstitutions;
// Hash of path to a list of substitutions to make in the file // Hash of path to a list of substitutions to make in the file
QHash<QString, QList<Substitution *>*> m_substitutions; QHash<QString, QList<Substitution *>*> m_substitutions;
// Hash of files held in memory
QHash<QString, QByteArray> m_files;
// Hash of filename extension to MIME type information // Hash of filename extension to MIME type information
QHash<QString, MimeType *> m_mimeTypes; QHash<QString, MimeType *> m_mimeTypes;
MimeType m_defaultMimeType; MimeType m_defaultMimeType;
@ -64,6 +67,7 @@ public:
void incomingConnection(qintptr socket) override; void incomingConnection(qintptr socket) override;
void addPathSubstitution(const QString &from, const QString &to); void addPathSubstitution(const QString &from, const QString &to);
void addSubstitution(QString path, QString from, QString to); void addSubstitution(QString path, QString from, QString to);
void addFile(const QString &path, const QByteArray &data);
QString substitute(QString path, QString html); QString substitute(QString path, QString html);
void sendFile(QTcpSocket* socket, const QByteArray &data, MimeType *mimeType, const QString &path); void sendFile(QTcpSocket* socket, const QByteArray &data, MimeType *mimeType, const QString &path);

Wyświetl plik

@ -181,6 +181,7 @@ set(sdrbase_SOURCES
util/fixedtraits.cpp util/fixedtraits.cpp
util/fits.cpp util/fits.cpp
util/flightinformation.cpp util/flightinformation.cpp
util/giro.cpp
util/golay2312.cpp util/golay2312.cpp
util/httpdownloadmanager.cpp util/httpdownloadmanager.cpp
util/interpolation.cpp util/interpolation.cpp
@ -395,6 +396,7 @@ set(sdrbase_HEADERS
util/fixedtraits.h util/fixedtraits.h
util/fits.h util/fits.h
util/flightinformation.h util/flightinformation.h
util/giro.h
util/golay2312.h util/golay2312.h
util/httpdownloadmanager.h util/httpdownloadmanager.h
util/incrementalarray.h util/incrementalarray.h

Wyświetl plik

@ -0,0 +1,227 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "giro.h"
#include <QDebug>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkReply>
#include <QJsonObject>
GIRO::GIRO()
{
connect(&m_dataTimer, &QTimer::timeout, this, &GIRO::getData);
connect(&m_mufTimer, &QTimer::timeout, this, &GIRO::getMUF);
connect(&m_foF2Timer, &QTimer::timeout, this, &GIRO::getfoF2);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, &QNetworkAccessManager::finished, this, &GIRO::handleReply);
}
GIRO::~GIRO()
{
disconnect(&m_dataTimer, &QTimer::timeout, this, &GIRO::getData);
disconnect(&m_mufTimer, &QTimer::timeout, this, &GIRO::getMUF);
disconnect(&m_foF2Timer, &QTimer::timeout, this, &GIRO::getfoF2);
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &GIRO::handleReply);
delete m_networkManager;
}
GIRO* GIRO::create(const QString& service)
{
if (service == "prop.kc2g.com")
{
return new GIRO();
}
else
{
qDebug() << "GIRO::create: Unsupported service: " << service;
return nullptr;
}
}
void GIRO::getDataPeriodically(int periodInMins)
{
if (periodInMins > 0)
{
m_dataTimer.setInterval(periodInMins*60*1000);
m_dataTimer.start();
getData();
}
else
{
m_dataTimer.stop();
}
}
void GIRO::getMUFPeriodically(int periodInMins)
{
if (periodInMins > 0)
{
m_mufTimer.setInterval(periodInMins*60*1000);
m_mufTimer.start();
getMUF();
}
else
{
m_mufTimer.stop();
}
}
void GIRO::getfoF2Periodically(int periodInMins)
{
if (periodInMins > 0)
{
m_foF2Timer.setInterval(periodInMins*60*1000);
m_foF2Timer.start();
getfoF2();
}
else
{
m_foF2Timer.stop();
}
}
void GIRO::getData()
{
QUrl url(QString("https://prop.kc2g.com/api/stations.json"));
m_networkManager->get(QNetworkRequest(url));
}
void GIRO::getMUF()
{
QUrl url(QString("https://prop.kc2g.com/renders/current/mufd-normal-now.geojson"));
m_networkManager->get(QNetworkRequest(url));
}
void GIRO::getfoF2()
{
QUrl url(QString("https://prop.kc2g.com/renders/current/fof2-normal-now.geojson"));
m_networkManager->get(QNetworkRequest(url));
}
bool GIRO::containsNonNull(const QJsonObject& obj, const QString &key) const
{
if (obj.contains(key))
{
QJsonValue val = obj.value(key);
return !val.isNull();
}
return false;
}
void GIRO::handleReply(QNetworkReply* reply)
{
if (reply)
{
if (!reply->error())
{
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if (reply->url().fileName() == "stations.json")
{
if (document.isArray())
{
QJsonArray array = document.array();
for (auto valRef : array)
{
if (valRef.isObject())
{
QJsonObject obj = valRef.toObject();
GIROStationData data;
if (obj.contains(QStringLiteral("station")))
{
QJsonObject stationObj = obj.value(QStringLiteral("station")).toObject();
if (stationObj.contains(QStringLiteral("name"))) {
data.m_station = stationObj.value(QStringLiteral("name")).toString();
}
if (stationObj.contains(QStringLiteral("latitude"))) {
data.m_latitude = (float)stationObj.value(QStringLiteral("latitude")).toString().toFloat();
}
if (stationObj.contains(QStringLiteral("longitude"))) {
data.m_longitude = (float)stationObj.value(QStringLiteral("longitude")).toString().toFloat();
if (data.m_longitude >= 180.0f) {
data.m_longitude -= 360.0f;
}
}
}
if (containsNonNull(obj, QStringLiteral("time"))) {
data.m_dateTime = QDateTime::fromString(obj.value(QStringLiteral("time")).toString(), Qt::ISODateWithMs);
}
if (containsNonNull(obj, QStringLiteral("mufd"))) {
data.m_mufd = (float)obj.value(QStringLiteral("mufd")).toDouble();
}
if (containsNonNull(obj, QStringLiteral("md"))) {
data.m_md = obj.value(QStringLiteral("md")).toString().toFloat();
}
if (containsNonNull(obj, QStringLiteral("tec"))) {
data.m_tec = (float)obj.value(QStringLiteral("tec")).toDouble();
}
if (containsNonNull(obj, QStringLiteral("fof2"))) {
data.m_foF2 = (float)obj.value(QStringLiteral("fof2")).toDouble();
}
if (containsNonNull(obj, QStringLiteral("hmf2"))) {
data.m_hmF2 = (float)obj.value(QStringLiteral("hmf2")).toDouble();
}
if (containsNonNull(obj, QStringLiteral("foe"))) {
data.m_foE = (float)obj.value(QStringLiteral("foe")).toDouble();
}
if (containsNonNull(obj, QStringLiteral("cs"))) {
data.m_confidence = (int)obj.value(QStringLiteral("cs")).toDouble();
}
emit dataUpdated(data);
}
else
{
qDebug() << "GIRO::handleReply: Array element is not an object: " << valRef;
}
}
}
else
{
qDebug() << "GIRO::handleReply: Document is not an array: " << document;
}
}
else if (reply->url().fileName() == "mufd-normal-now.geojson")
{
emit mufUpdated(document);
}
else if (reply->url().fileName() == "fof2-normal-now.geojson")
{
emit foF2Updated(document);
}
else
{
qDebug() << "GIRO::handleReply: unexpected filename: " << reply->url().fileName();
}
}
else
{
qDebug() << "GIRO::handleReply: error: " << reply->error();
}
reply->deleteLater();
}
else
{
qDebug() << "GIRO::handleReply: reply is null";
}
}

Wyświetl plik

@ -0,0 +1,99 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_GIRO_H
#define INCLUDE_GIRO_H
#include <QtCore>
#include <QTimer>
#include <QJsonDocument>
#include "export.h"
class QNetworkAccessManager;
class QNetworkReply;
// GIRO - Global Ionosphere Radio Observatory
// Gets MUFD, TEC, foF2 and other data for various stations around the world
// Also gets MUF and foF2 contours as GeoJSON
// Data from https://prop.kc2g.com/stations/
class SDRBASE_API GIRO : public QObject
{
Q_OBJECT
protected:
GIRO();
public:
// See the following paper for an explanation of some of these variables
// https://sbgf.org.br/mysbgf/eventos/expanded_abstracts/13th_CISBGf/A%20Simple%20Method%20to%20Calculate%20the%20Maximum%20Usable%20Frequency.pdf
struct GIROStationData {
QString m_station;
float m_latitude;
float m_longitude;
QDateTime m_dateTime;
float m_mufd; // Maximum usable frequency
float m_md; // Propagation coefficient? D=3000km?
float m_tec; // Total electron content
float m_foF2; // Critical frequency of F2 layer in ionosphere (highest frequency to be reflected)
float m_hmF2; // F2 layer height of peak electron density (km?)
float m_foE; // Critical frequency of E layer
int m_confidence;
GIROStationData() :
m_latitude(NAN),
m_longitude(NAN),
m_mufd(NAN),
m_md(NAN),
m_tec(NAN),
m_foF2(NAN),
m_hmF2(NAN),
m_foE(NAN),
m_confidence(-1)
{
}
};
static GIRO* create(const QString& service="prop.kc2g.com");
~GIRO();
void getDataPeriodically(int periodInMins);
void getMUFPeriodically(int periodInMins);
void getfoF2Periodically(int periodInMins);
private slots:
void getData();
void getMUF();
void getfoF2();
private slots:
void handleReply(QNetworkReply* reply);
signals:
void dataUpdated(const GIROStationData& data); // Called when new data available.
void mufUpdated(const QJsonDocument& doc);
void foF2Updated(const QJsonDocument& doc);
private:
bool GIRO::containsNonNull(const QJsonObject& obj, const QString &key) const;
QTimer m_dataTimer; // Timer for periodic updates
QTimer m_mufTimer;
QTimer m_foF2Timer;
QNetworkAccessManager *m_networkManager;
};
#endif /* INCLUDE_GIRO_H */