mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-04-06 03:29:12 -04:00
Add util classes for getting data from GOES, SDO, Solar Orbiter STIX and Fermi satellites.
This commit is contained in:
parent
b890c32f13
commit
2be14f944a
@ -238,7 +238,9 @@ set(sdrbase_SOURCES
|
||||
util/flightinformation.cpp
|
||||
util/ft8message.cpp
|
||||
util/giro.cpp
|
||||
util/goesxray.cpp
|
||||
util/golay2312.cpp
|
||||
util/grb.cpp
|
||||
util/httpdownloadmanager.cpp
|
||||
util/interpolation.cpp
|
||||
util/kiwisdrlist.cpp
|
||||
@ -266,8 +268,10 @@ set(sdrbase_SOURCES
|
||||
util/samplesourceserializer.cpp
|
||||
util/simpleserializer.cpp
|
||||
util/serialutil.cpp
|
||||
util/solardynamicsobservatory.cpp
|
||||
#util/spinlock.cpp
|
||||
util/spyserverlist.cpp
|
||||
util/stix.cpp
|
||||
util/rtty.cpp
|
||||
util/uid.cpp
|
||||
util/units.cpp
|
||||
@ -487,7 +491,9 @@ set(sdrbase_HEADERS
|
||||
util/flightinformation.h
|
||||
util/ft8message.h
|
||||
util/giro.h
|
||||
util/goesxray.h
|
||||
util/golay2312.h
|
||||
util/grb.h
|
||||
util/httpdownloadmanager.h
|
||||
util/incrementalarray.h
|
||||
util/incrementalvector.h
|
||||
@ -520,8 +526,10 @@ set(sdrbase_HEADERS
|
||||
util/samplesourceserializer.h
|
||||
util/simpleserializer.h
|
||||
util/serialutil.h
|
||||
util/solardynamicsobservatory.h
|
||||
#util/spinlock.h
|
||||
util/spyserverlist.h
|
||||
util/stix.h
|
||||
util/uid.h
|
||||
util/units.h
|
||||
util/timeutil.h
|
||||
|
223
sdrbase/util/goesxray.cpp
Normal file
223
sdrbase/util/goesxray.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 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 "goesxray.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
|
||||
GOESXRay::GOESXRay()
|
||||
{
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, &QNetworkAccessManager::finished, this, &GOESXRay::handleReply);
|
||||
connect(&m_dataTimer, &QTimer::timeout, this, &GOESXRay::getData);
|
||||
}
|
||||
|
||||
|
||||
GOESXRay::~GOESXRay()
|
||||
{
|
||||
disconnect(&m_dataTimer, &QTimer::timeout, this, &GOESXRay::getData);
|
||||
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &GOESXRay::handleReply);
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
GOESXRay* GOESXRay::create(const QString& service)
|
||||
{
|
||||
if (service == "services.swpc.noaa.gov")
|
||||
{
|
||||
return new GOESXRay();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::create: Unsupported service: " << service;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GOESXRay::getDataPeriodically(int periodInMins)
|
||||
{
|
||||
if (periodInMins > 0)
|
||||
{
|
||||
m_dataTimer.setInterval(periodInMins*60*1000);
|
||||
m_dataTimer.start();
|
||||
getData();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dataTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void GOESXRay::getData()
|
||||
{
|
||||
// Around 160kB per file
|
||||
QUrl url(QString("https://services.swpc.noaa.gov/json/goes/primary/xrays-6-hour.json"));
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
|
||||
QUrl secondaryURL(QString("https://services.swpc.noaa.gov/json/goes/secondary/xrays-6-hour.json"));
|
||||
m_networkManager->get(QNetworkRequest(secondaryURL));
|
||||
|
||||
QUrl protonPrimaryURL(QString("https://services.swpc.noaa.gov/json/goes/primary/integral-protons-plot-6-hour.json"));
|
||||
m_networkManager->get(QNetworkRequest(protonPrimaryURL));
|
||||
}
|
||||
|
||||
bool GOESXRay::containsNonNull(const QJsonObject& obj, const QString &key) const
|
||||
{
|
||||
if (obj.contains(key))
|
||||
{
|
||||
QJsonValue val = obj.value(key);
|
||||
return !val.isNull();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GOESXRay::handleReply(QNetworkReply* reply)
|
||||
{
|
||||
if (reply)
|
||||
{
|
||||
if (!reply->error())
|
||||
{
|
||||
QByteArray bytes = reply->readAll();
|
||||
bool primary = reply->url().toString().contains("primary");
|
||||
|
||||
if (reply->url().fileName() == "xrays-6-hour.json") {
|
||||
handleXRayJson(bytes, primary);
|
||||
} else if (reply->url().fileName() == "integral-protons-plot-6-hour.json") {
|
||||
handleProtonJson(bytes, primary);
|
||||
} else {
|
||||
qDebug() << "GOESXRay::handleReply: unexpected filename: " << reply->url().fileName();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::handleReply: error: " << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::handleReply: reply is null";
|
||||
}
|
||||
}
|
||||
|
||||
void GOESXRay::handleXRayJson(const QByteArray& bytes, bool primary)
|
||||
{
|
||||
QJsonDocument document = QJsonDocument::fromJson(bytes);
|
||||
if (document.isArray())
|
||||
{
|
||||
QJsonArray array = document.array();
|
||||
QList<XRayData> data;
|
||||
for (auto valRef : array)
|
||||
{
|
||||
if (valRef.isObject())
|
||||
{
|
||||
QJsonObject obj = valRef.toObject();
|
||||
|
||||
XRayData measurement;
|
||||
|
||||
if (obj.contains(QStringLiteral("satellite"))) {
|
||||
measurement.m_satellite = QString("GOES %1").arg(obj.value(QStringLiteral("satellite")).toInt());
|
||||
}
|
||||
if (containsNonNull(obj, QStringLiteral("time_tag"))) {
|
||||
measurement.m_dateTime = QDateTime::fromString(obj.value(QStringLiteral("time_tag")).toString(), Qt::ISODate);
|
||||
}
|
||||
if (containsNonNull(obj, QStringLiteral("flux"))) {
|
||||
measurement.m_flux = obj.value(QStringLiteral("flux")).toDouble();
|
||||
}
|
||||
if (containsNonNull(obj, QStringLiteral("energy")))
|
||||
{
|
||||
QString energy = obj.value(QStringLiteral("energy")).toString();
|
||||
if (energy == "0.05-0.4nm") {
|
||||
measurement.m_band = XRayData::SHORT;
|
||||
} else if (energy == "0.1-0.8nm") {
|
||||
measurement.m_band = XRayData::LONG;
|
||||
} else {
|
||||
qDebug() << "GOESXRay::handleXRayJson: Unknown energy: " << energy;
|
||||
}
|
||||
}
|
||||
|
||||
data.append(measurement);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::handleXRayJson: Array element is not an object: " << valRef;
|
||||
}
|
||||
}
|
||||
if (data.size() > 0) {
|
||||
emit xRayDataUpdated(data, primary);
|
||||
} else {
|
||||
qDebug() << "GOESXRay::handleXRayJson: No data in array: " << document;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::handleXRayJson: Document is not an array: " << document;
|
||||
}
|
||||
}
|
||||
|
||||
void GOESXRay::handleProtonJson(const QByteArray& bytes, bool primary)
|
||||
{
|
||||
QJsonDocument document = QJsonDocument::fromJson(bytes);
|
||||
if (document.isArray())
|
||||
{
|
||||
QJsonArray array = document.array();
|
||||
QList<ProtonData> data;
|
||||
for (auto valRef : array)
|
||||
{
|
||||
if (valRef.isObject())
|
||||
{
|
||||
QJsonObject obj = valRef.toObject();
|
||||
|
||||
ProtonData measurement;
|
||||
|
||||
if (obj.contains(QStringLiteral("satellite"))) {
|
||||
measurement.m_satellite = QString("GOES %1").arg(obj.value(QStringLiteral("satellite")).toInt());
|
||||
}
|
||||
if (containsNonNull(obj, QStringLiteral("time_tag"))) {
|
||||
measurement.m_dateTime = QDateTime::fromString(obj.value(QStringLiteral("time_tag")).toString(), Qt::ISODate);
|
||||
}
|
||||
if (containsNonNull(obj, QStringLiteral("flux"))) {
|
||||
measurement.m_flux = obj.value(QStringLiteral("flux")).toDouble();
|
||||
}
|
||||
if (containsNonNull(obj, QStringLiteral("energy")))
|
||||
{
|
||||
QString energy = obj.value(QStringLiteral("energy")).toString();
|
||||
QString value = energy.mid(2).split(' ')[0];
|
||||
measurement.m_energy = value.toInt(); // String like: ">=50 MeV"
|
||||
}
|
||||
|
||||
data.append(measurement);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::handleProtonJson: Array element is not an object: " << valRef;
|
||||
}
|
||||
}
|
||||
if (data.size() > 0) {
|
||||
emit protonDataUpdated(data, primary);
|
||||
} else {
|
||||
qDebug() << "GOESXRay::handleProtonJson: No data in array: " << document;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GOESXRay::handleProtonJson: Document is not an array: " << document;
|
||||
}
|
||||
}
|
96
sdrbase/util/goesxray.h
Normal file
96
sdrbase/util/goesxray.h
Normal file
@ -0,0 +1,96 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2023 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_GOESXRAY_H
|
||||
#define INCLUDE_GOESXRAY_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
// GOES X-Ray data
|
||||
// This gets 1-minute averages of solar X-rays the 1-8 Angstrom (0.1-0.8 nm) and 0.5-4.0 Angstrom (0.05-0.4 nm) passbands from the GOES satellites
|
||||
// https://www.swpc.noaa.gov/products/goes-x-ray-flux
|
||||
// There are primary and secondary data sources, from different satellites, as sometimes they can be in eclipse
|
||||
// Also gets Proton flux (Which may be observed on Earth a couple of days after a large flare/CME)
|
||||
class SDRBASE_API GOESXRay : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
GOESXRay();
|
||||
|
||||
public:
|
||||
struct XRayData {
|
||||
QDateTime m_dateTime;
|
||||
QString m_satellite;
|
||||
double m_flux;
|
||||
enum Band {
|
||||
UNKNOWN,
|
||||
SHORT, // 0.05-0.4nm
|
||||
LONG // 0.1-0.8nm
|
||||
} m_band;
|
||||
XRayData() :
|
||||
m_flux(NAN),
|
||||
m_band(UNKNOWN)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct ProtonData {
|
||||
QDateTime m_dateTime;
|
||||
QString m_satellite;
|
||||
double m_flux;
|
||||
int m_energy; // 10=10MeV, 50MeV, 100MeV, 500MeV
|
||||
ProtonData() :
|
||||
m_flux(NAN),
|
||||
m_energy(0)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static GOESXRay* create(const QString& service="services.swpc.noaa.gov");
|
||||
|
||||
~GOESXRay();
|
||||
void getDataPeriodically(int periodInMins=10);
|
||||
|
||||
public slots:
|
||||
void getData();
|
||||
|
||||
private slots:
|
||||
void handleReply(QNetworkReply* reply);
|
||||
|
||||
signals:
|
||||
void xRayDataUpdated(const QList<GOESXRay::XRayData>& data, bool primary); // Called when new data available.
|
||||
void protonDataUpdated(const QList<GOESXRay::ProtonData> &data, bool primary);
|
||||
|
||||
private:
|
||||
bool containsNonNull(const QJsonObject& obj, const QString &key) const;
|
||||
void handleXRayJson(const QByteArray& bytes, bool primary);
|
||||
void handleProtonJson(const QByteArray& bytes, bool primary);
|
||||
|
||||
QTimer m_dataTimer; // Timer for periodic updates
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_GOESXRAY_H */
|
||||
|
198
sdrbase/util/grb.cpp
Normal file
198
sdrbase/util/grb.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 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 "grb.h"
|
||||
#include "util/csv.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QTextStream>
|
||||
|
||||
GRB::GRB()
|
||||
{
|
||||
connect(&m_dataTimer, &QTimer::timeout, this,&GRB::getData);
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, &QNetworkAccessManager::finished, this, &GRB::handleReply);
|
||||
|
||||
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
QDir writeableDir(locations[0]);
|
||||
if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("grb"))) {
|
||||
qDebug() << "Failed to create cache/grb";
|
||||
}
|
||||
|
||||
m_cache = new QNetworkDiskCache();
|
||||
m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("grb"));
|
||||
m_cache->setMaximumCacheSize(100000000);
|
||||
m_networkManager->setCache(m_cache);
|
||||
}
|
||||
|
||||
GRB::~GRB()
|
||||
{
|
||||
disconnect(&m_dataTimer, &QTimer::timeout, this, &GRB::getData);
|
||||
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &GRB::handleReply);
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
GRB* GRB::create()
|
||||
{
|
||||
return new GRB();
|
||||
}
|
||||
|
||||
void GRB::getDataPeriodically(int periodInMins)
|
||||
{
|
||||
if (periodInMins > 0)
|
||||
{
|
||||
m_dataTimer.setInterval(periodInMins*60*1000);
|
||||
m_dataTimer.start();
|
||||
getData();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dataTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void GRB::getData()
|
||||
{
|
||||
QUrl url("https://user-web.icecube.wisc.edu/~grbweb_public/Summary_table.txt");
|
||||
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
void GRB::handleReply(QNetworkReply* reply)
|
||||
{
|
||||
if (reply)
|
||||
{
|
||||
if (!reply->error())
|
||||
{
|
||||
if (reply->url().fileName().endsWith(".txt"))
|
||||
{
|
||||
QByteArray bytes = reply->readAll();
|
||||
handleText(bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GRB::handleReply: Unexpected file" << reply->url().fileName();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GRB::handleReply: Error: " << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "GRB::handleReply: Reply is null";
|
||||
}
|
||||
}
|
||||
|
||||
void GRB::handleText(QByteArray& bytes)
|
||||
{
|
||||
// Convert to CSV
|
||||
QString s(bytes);
|
||||
QStringList l = s.split("\n");
|
||||
for (int i = 0; i < l.size(); i++) {
|
||||
l[i] = l[i].simplified().replace(" ", ",");
|
||||
}
|
||||
s = l.join("\n");
|
||||
|
||||
QTextStream in(&s);
|
||||
|
||||
// Skip header
|
||||
for (int i = 0; i < 4; i++) {
|
||||
in.readLine();
|
||||
}
|
||||
|
||||
QList<Data> grbs;
|
||||
QStringList cols;
|
||||
while(CSV::readRow(in, &cols))
|
||||
{
|
||||
Data grb;
|
||||
|
||||
if (cols.length() >= 10)
|
||||
{
|
||||
grb.m_name = cols[0];
|
||||
grb.m_fermiName = cols[1];
|
||||
int year = grb.m_name.mid(3, 2).toInt();
|
||||
if (year >= 90) {
|
||||
year += 1900;
|
||||
} else {
|
||||
year += 2000;
|
||||
}
|
||||
QDate date(year, grb.m_name.mid(5, 2).toInt(), grb.m_name.mid(7, 2).toInt());
|
||||
QTime time = QTime::fromString(cols[2]);
|
||||
grb.m_dateTime = QDateTime(date, time);
|
||||
grb.m_ra = cols[3].toFloat();
|
||||
grb.m_dec = cols[4].toFloat();
|
||||
grb.m_fluence = cols[9].toFloat();
|
||||
|
||||
//qDebug() << grb.m_name << grb.m_dateTime.toString() << grb.m_ra << grb.m_dec << grb.m_fluence ;
|
||||
|
||||
if (grb.m_dateTime.isValid()) {
|
||||
grbs.append(grb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emit dataUpdated(grbs);
|
||||
}
|
||||
|
||||
QString GRB::Data::getFermiURL() const
|
||||
{
|
||||
if (m_fermiName.isEmpty() || (m_fermiName == "None")) {
|
||||
return "";
|
||||
}
|
||||
QString base = "https://heasarc.gsfc.nasa.gov/FTP/fermi/data/gbm/bursts/";
|
||||
QString yearDir = "20" + m_fermiName.mid(3, 2);
|
||||
QString dataDir = m_fermiName;
|
||||
dataDir.replace("GRB", "bn");
|
||||
return base + yearDir + "/" + dataDir + "/current/";
|
||||
}
|
||||
|
||||
QString GRB::Data::getFermiPlotURL() const
|
||||
{
|
||||
QString base = getFermiURL();
|
||||
if (base.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QString name = m_fermiName;
|
||||
name.replace("GRB", "bn");
|
||||
return getFermiURL() + "glg_lc_all_" + name + "_v00.gif"; // Could be v01.gif? How to know without fetching index?
|
||||
}
|
||||
|
||||
QString GRB::Data::getFermiSkyMapURL() const
|
||||
{
|
||||
QString base = getFermiURL();
|
||||
if (base.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QString name = m_fermiName;
|
||||
name.replace("GRB", "bn");
|
||||
return getFermiURL() + "glg_skymap_all_" + name + "_v00.png";
|
||||
}
|
||||
|
||||
QString GRB::Data::getSwiftURL() const
|
||||
{
|
||||
QString name = m_name;
|
||||
name.replace("GRB", "");
|
||||
return "https://swift.gsfc.nasa.gov/archive/grb_table/" + name;
|
||||
}
|
81
sdrbase/util/grb.h
Normal file
81
sdrbase/util/grb.h
Normal file
@ -0,0 +1,81 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 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_GRB_H
|
||||
#define INCLUDE_GRB_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QTimer>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QNetworkDiskCache;
|
||||
|
||||
// GRB (Gamma Ray Burst) database
|
||||
// Gets GRB database from GRBweb https://user-web.icecube.wisc.edu/~grbweb_public/
|
||||
// Uses summary .txt file so only contains last 1000 GRBs
|
||||
class SDRBASE_API GRB : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
GRB();
|
||||
|
||||
public:
|
||||
|
||||
struct SDRBASE_API Data {
|
||||
|
||||
QString m_name; // E.g: GRB240310A
|
||||
QString m_fermiName; // Name used by Fermi telescope. E.g. GRB240310236. Can be None if not detected by Fermi
|
||||
QDateTime m_dateTime;
|
||||
float m_ra; // Right Ascension
|
||||
float m_dec; // Declination
|
||||
float m_fluence; // erg/cm^2
|
||||
|
||||
QString getFermiURL() const; // Get URL where Fermi data is stored
|
||||
QString getFermiPlotURL() const;
|
||||
QString getFermiSkyMapURL() const;
|
||||
QString getSwiftURL() const;
|
||||
|
||||
};
|
||||
|
||||
static GRB* create();
|
||||
|
||||
~GRB();
|
||||
void getDataPeriodically(int periodInMins=1440); // GRBweb is updated every 24 hours, usually just after 9am UTC
|
||||
|
||||
public slots:
|
||||
void getData();
|
||||
|
||||
private slots:
|
||||
void handleReply(QNetworkReply* reply);
|
||||
|
||||
signals:
|
||||
void dataUpdated(const QList<Data>data); // Called when new data is available.
|
||||
|
||||
private:
|
||||
|
||||
QTimer m_dataTimer; // Timer for periodic updates
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkDiskCache *m_cache;
|
||||
|
||||
void handleText(QByteArray& bytes);
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_GRB_H */
|
386
sdrbase/util/solardynamicsobservatory.cpp
Normal file
386
sdrbase/util/solardynamicsobservatory.cpp
Normal file
@ -0,0 +1,386 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 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 "solardynamicsobservatory.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkDiskCache>
|
||||
|
||||
SolarDynamicsObservatory::SolarDynamicsObservatory() :
|
||||
m_size(512)
|
||||
{
|
||||
connect(&m_dataTimer, &QTimer::timeout, this, qOverload<>(&SolarDynamicsObservatory::getImage));
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, &QNetworkAccessManager::finished, this, &SolarDynamicsObservatory::handleReply);
|
||||
|
||||
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
QDir writeableDir(locations[0]);
|
||||
if (!writeableDir.mkpath(QStringLiteral("cache") + QDir::separator() + QStringLiteral("solardynamicsobservatory"))) {
|
||||
qDebug() << "SolarDynamicsObservatory::SolarDynamicsObservatory: Failed to create cache/solardynamicsobservatory";
|
||||
}
|
||||
|
||||
m_cache = new QNetworkDiskCache();
|
||||
m_cache->setCacheDirectory(locations[0] + QDir::separator() + QStringLiteral("cache") + QDir::separator() + QStringLiteral("solardynamicsobservatory"));
|
||||
m_cache->setMaximumCacheSize(100000000);
|
||||
m_networkManager->setCache(m_cache);
|
||||
}
|
||||
|
||||
SolarDynamicsObservatory::~SolarDynamicsObservatory()
|
||||
{
|
||||
disconnect(&m_dataTimer, &QTimer::timeout, this, qOverload<>(&SolarDynamicsObservatory::getImage));
|
||||
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &SolarDynamicsObservatory::handleReply);
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
SolarDynamicsObservatory* SolarDynamicsObservatory::create()
|
||||
{
|
||||
return new SolarDynamicsObservatory();
|
||||
}
|
||||
|
||||
QList<int> SolarDynamicsObservatory::getImageSizes()
|
||||
{
|
||||
return {512, 1024, 2048, 4096};
|
||||
}
|
||||
|
||||
QList<int> SolarDynamicsObservatory::getVideoSizes()
|
||||
{
|
||||
return {512, 1024};
|
||||
}
|
||||
|
||||
const QStringList SolarDynamicsObservatory::getImageNames()
|
||||
{
|
||||
QChar angstronm(0x212B);
|
||||
QStringList names;
|
||||
|
||||
// SDO
|
||||
names.append(QString("AIA 094 %1").arg(angstronm));
|
||||
names.append(QString("AIA 131 %1").arg(angstronm));
|
||||
names.append(QString("AIA 171 %1").arg(angstronm));
|
||||
names.append(QString("AIA 193 %1").arg(angstronm));
|
||||
names.append(QString("AIA 211 %1").arg(angstronm));
|
||||
names.append(QString("AIA 304 %1").arg(angstronm));
|
||||
names.append(QString("AIA 335 %1").arg(angstronm));
|
||||
names.append(QString("AIA 1600 %1").arg(angstronm));
|
||||
names.append(QString("AIA 1700 %1").arg(angstronm));
|
||||
names.append(QString("AIA 211 %1, 193 %1, 171 %1").arg(angstronm));
|
||||
names.append(QString("AIA 304 %1, 211 %1, 171 %1").arg(angstronm));
|
||||
names.append(QString("AIA 094 %1, 335 %1, 193 %1").arg(angstronm));
|
||||
names.append(QString("AIA 171 %1, HMIB").arg(angstronm));
|
||||
names.append("HMI Magneotgram");
|
||||
names.append("HMI Colorized Magneotgram");
|
||||
names.append("HMI Intensitygram - Colored");
|
||||
names.append("HMI Intensitygram - Flattened");
|
||||
names.append("HMI Intensitygram");
|
||||
names.append("HMI Dopplergram");
|
||||
|
||||
// SOHO
|
||||
names.append("LASCO C2");
|
||||
names.append("LASCO C3");
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
const QStringList SolarDynamicsObservatory::getChannelNames()
|
||||
{
|
||||
QStringList channelNames = {
|
||||
"0094",
|
||||
"0131",
|
||||
"0171",
|
||||
"0193",
|
||||
"0211",
|
||||
"0304",
|
||||
"0335",
|
||||
"1600",
|
||||
"1700",
|
||||
"211193171",
|
||||
"304211171",
|
||||
"094335193",
|
||||
"HMImag",
|
||||
"HMIB",
|
||||
"HMIBC",
|
||||
"HMIIC",
|
||||
"HMIIF",
|
||||
"HMII",
|
||||
"HMID",
|
||||
"c2",
|
||||
"c3"
|
||||
};
|
||||
|
||||
return channelNames;
|
||||
}
|
||||
|
||||
const QStringList SolarDynamicsObservatory::getImageFileNames()
|
||||
{
|
||||
// Ordering needs to match getImageNames()
|
||||
// %1 replaced with size
|
||||
QStringList filenames = {
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0094.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0131.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0171.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0193.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0211.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0304.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_0335.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_1600.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_1700.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_211193171.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/f_304_211_171_%1.jpg",
|
||||
//"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_304211171.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/f_094_335_193_%1.jpg",
|
||||
//"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_094335193.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/f_HMImag_171_%1.jpg",
|
||||
//"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMImag.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIB.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIBC.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIIC.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMIIF.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMII.jpg",
|
||||
"https://sdo.gsfc.nasa.gov/assets/img/latest/latest_%1_HMID.jpg",
|
||||
"https://soho.nascom.nasa.gov/data/realtime/c2/512/latest.jpg",
|
||||
"https://soho.nascom.nasa.gov/data/realtime/c3/512/latest.jpg"
|
||||
};
|
||||
|
||||
return filenames;
|
||||
}
|
||||
|
||||
const QStringList SolarDynamicsObservatory::getVideoNames()
|
||||
{
|
||||
QChar angstronm(0x212B);
|
||||
QStringList names;
|
||||
|
||||
// SDO
|
||||
names.append(QString("AIA 094 %1").arg(angstronm));
|
||||
names.append(QString("AIA 131 %1").arg(angstronm));
|
||||
names.append(QString("AIA 171 %1").arg(angstronm));
|
||||
names.append(QString("AIA 193 %1").arg(angstronm));
|
||||
names.append(QString("AIA 211 %1").arg(angstronm));
|
||||
names.append(QString("AIA 304 %1").arg(angstronm));
|
||||
names.append(QString("AIA 335 %1").arg(angstronm));
|
||||
names.append(QString("AIA 1600 %1").arg(angstronm));
|
||||
names.append(QString("AIA 1700 %1").arg(angstronm));
|
||||
|
||||
// SOHO
|
||||
names.append("LASCO C2");
|
||||
names.append("LASCO C3");
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
const QStringList SolarDynamicsObservatory::getVideoFileNames()
|
||||
{
|
||||
const QStringList filenames = {
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0094.mp4", // Videos sometimes fail to load on Windows if https used
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0131.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0171.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0193.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0211.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0304.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_0335.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_1600.mp4",
|
||||
"http://sdo.gsfc.nasa.gov/assets/img/latest/mpeg/latest_%1_1700.mp4",
|
||||
"http://soho.nascom.nasa.gov/data/LATEST/current_c2.mp4",
|
||||
"http://soho.nascom.nasa.gov/data/LATEST/current_c3.mp4",
|
||||
};
|
||||
|
||||
return filenames;
|
||||
}
|
||||
|
||||
QString SolarDynamicsObservatory::getImageURL(const QString& image, int size)
|
||||
{
|
||||
const QStringList names = SolarDynamicsObservatory::getImageNames();
|
||||
const QStringList filenames = SolarDynamicsObservatory::getImageFileNames();
|
||||
int idx = names.indexOf(image);
|
||||
|
||||
if (idx != -1) {
|
||||
return QString(filenames[idx]).arg(size);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
QString SolarDynamicsObservatory::getVideoURL(const QString& video, int size)
|
||||
{
|
||||
const QStringList names = SolarDynamicsObservatory::getVideoNames();
|
||||
const QStringList filenames = SolarDynamicsObservatory::getVideoFileNames();
|
||||
int idx = names.indexOf(video);
|
||||
|
||||
if (idx != -1) {
|
||||
return QString(filenames[idx]).arg(size);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::getImagePeriodically(const QString& image, int size, int periodInMins)
|
||||
{
|
||||
m_image = image;
|
||||
m_size = size;
|
||||
if (periodInMins > 0)
|
||||
{
|
||||
m_dataTimer.setInterval(periodInMins*60*1000);
|
||||
m_dataTimer.start();
|
||||
getImage();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dataTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::getImage()
|
||||
{
|
||||
getImage(m_image, m_size);
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::getImage(const QString& imageName, int size)
|
||||
{
|
||||
QString urlString = getImageURL(imageName, size);
|
||||
if (!urlString.isEmpty())
|
||||
{
|
||||
QUrl url(urlString);
|
||||
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
}
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::getImage(const QString& imageName, QDateTime dateTime, int size)
|
||||
{
|
||||
// Stop periodic updates, if not after latest data
|
||||
m_dataTimer.stop();
|
||||
|
||||
// Get file index, as we don't know what time will be used in the file
|
||||
QDate date = dateTime.date();
|
||||
QString urlString = QString("https://sdo.gsfc.nasa.gov/assets/img/browse/%1/%2/%3/")
|
||||
.arg(date.year())
|
||||
.arg(date.month(), 2, 10, QLatin1Char('0'))
|
||||
.arg(date.day(), 2, 10, QLatin1Char('0'));
|
||||
QUrl url(urlString);
|
||||
|
||||
// Save details of image we are after
|
||||
m_dateTime = dateTime;
|
||||
m_size = size;
|
||||
m_image = imageName;
|
||||
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::handleReply(QNetworkReply* reply)
|
||||
{
|
||||
if (reply)
|
||||
{
|
||||
if (!reply->error())
|
||||
{
|
||||
if (reply->url().fileName().endsWith(".jpg"))
|
||||
{
|
||||
handleJpeg(reply->readAll());
|
||||
}
|
||||
else
|
||||
{
|
||||
handleIndex(reply->readAll());
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "SolarDynamicsObservatory::handleReply: Error: " << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "SolarDynamicsObservatory::handleReply: Reply is null";
|
||||
}
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::handleJpeg(const QByteArray& bytes)
|
||||
{
|
||||
QImage image;
|
||||
|
||||
if (image.loadFromData(bytes)) {
|
||||
emit imageUpdated(image);
|
||||
} else {
|
||||
qWarning() << "SolarDynamicsObservatory::handleJpeg: Failed to load image";
|
||||
}
|
||||
}
|
||||
|
||||
void SolarDynamicsObservatory::handleIndex(const QByteArray& bytes)
|
||||
{
|
||||
const QStringList names = SolarDynamicsObservatory::getImageNames();
|
||||
const QStringList channelNames = SolarDynamicsObservatory::getChannelNames();
|
||||
int idx = names.indexOf(m_image);
|
||||
if (idx < 0) {
|
||||
return;
|
||||
}
|
||||
QString channel = channelNames[idx];
|
||||
|
||||
QString file(bytes);
|
||||
QStringList lines = file.split("\n");
|
||||
|
||||
QString date = m_dateTime.date().toString("yyyyMMdd");
|
||||
QString pattern = QString("\"%1_([0-9]{6})_%2_%3.jpg\"").arg(date).arg(m_size).arg(channel);
|
||||
QRegularExpression re(pattern);
|
||||
|
||||
// Get all times the image is available
|
||||
QList<QTime> times;
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
QRegularExpressionMatch match = re.match(line);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
QString t = match.capturedTexts()[1];
|
||||
int h = t.left(2).toInt();
|
||||
int m = t.mid(2, 2).toInt();
|
||||
int s = t.right(2).toInt();
|
||||
times.append(QTime(h, m, s));
|
||||
}
|
||||
}
|
||||
|
||||
if (times.length() > 0)
|
||||
{
|
||||
QTime target = m_dateTime.time();
|
||||
QTime current = times[0];
|
||||
for (int i = 1; i < times.size(); i++)
|
||||
{
|
||||
if (target < times[i]) {
|
||||
break;
|
||||
}
|
||||
current = times[i];
|
||||
}
|
||||
|
||||
// Get image
|
||||
QDate date = m_dateTime.date();
|
||||
QString urlString = QString("https://sdo.gsfc.nasa.gov/assets/img/browse/%1/%2/%3/%1%2%3_%4%5%6_%7_%8.jpg")
|
||||
.arg(date.year())
|
||||
.arg(date.month(), 2, 10, QLatin1Char('0'))
|
||||
.arg(date.day(), 2, 10, QLatin1Char('0'))
|
||||
.arg(current.hour(), 2, 10, QLatin1Char('0'))
|
||||
.arg(current.minute(), 2, 10, QLatin1Char('0'))
|
||||
.arg(current.second(), 2, 10, QLatin1Char('0'))
|
||||
.arg(m_size)
|
||||
.arg(channel);
|
||||
|
||||
QUrl url(urlString);
|
||||
|
||||
m_networkManager->get(QNetworkRequest(url));
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "SolarDynamicsObservatory: No image available";
|
||||
}
|
||||
}
|
81
sdrbase/util/solardynamicsobservatory.h
Normal file
81
sdrbase/util/solardynamicsobservatory.h
Normal file
@ -0,0 +1,81 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 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_SOLARDYNAMICSOBSERVATORY_H
|
||||
#define INCLUDE_SOLARDYNAMICSOBSERVATORY_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QTimer>
|
||||
#include <QImage>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QNetworkDiskCache;
|
||||
|
||||
// This gets solar imagery from SDO (Solar Dynamics Observatory) - https://sdo.gsfc.nasa.gov/
|
||||
// and LASCO images from SOHO - https://soho.nascom.nasa.gov/
|
||||
class SDRBASE_API SolarDynamicsObservatory : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
SolarDynamicsObservatory();
|
||||
|
||||
public:
|
||||
|
||||
static SolarDynamicsObservatory* create();
|
||||
|
||||
~SolarDynamicsObservatory();
|
||||
void getImagePeriodically(const QString& image, int size=512, int periodInMins=15);
|
||||
void getImage(const QString& m_image, int size);
|
||||
void getImage(const QString& m_image, QDateTime dateTime, int size=512);
|
||||
|
||||
static QString getImageURL(const QString& image, int size);
|
||||
static QString getVideoURL(const QString& video, int size=512);
|
||||
|
||||
static QList<int> getImageSizes();
|
||||
static const QStringList getChannelNames();
|
||||
static const QStringList getImageNames();
|
||||
static QList<int> getVideoSizes();
|
||||
static const QStringList getVideoNames();
|
||||
|
||||
private slots:
|
||||
void getImage();
|
||||
void handleReply(QNetworkReply* reply);
|
||||
|
||||
signals:
|
||||
void imageUpdated(const QImage& image); // Called when new image is available.
|
||||
|
||||
private:
|
||||
|
||||
QTimer m_dataTimer; // Timer for periodic updates
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkDiskCache *m_cache;
|
||||
|
||||
QString m_image;
|
||||
int m_size;
|
||||
QDateTime m_dateTime;
|
||||
|
||||
void handleJpeg(const QByteArray& bytes);
|
||||
void handleIndex(const QByteArray& bytes);
|
||||
static const QStringList getImageFileNames();
|
||||
static const QStringList getVideoFileNames();
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_SOLARDYNAMICSOBSERVATORY_H */
|
176
sdrbase/util/stix.cpp
Normal file
176
sdrbase/util/stix.cpp
Normal file
@ -0,0 +1,176 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 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 "stix.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
|
||||
STIX::STIX()
|
||||
{
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, &QNetworkAccessManager::finished, this, &STIX::handleReply);
|
||||
connect(&m_dataTimer, &QTimer::timeout, this, &STIX::getData);
|
||||
}
|
||||
|
||||
|
||||
STIX::~STIX()
|
||||
{
|
||||
disconnect(&m_dataTimer, &QTimer::timeout, this, &STIX::getData);
|
||||
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &STIX::handleReply);
|
||||
delete m_networkManager;
|
||||
}
|
||||
|
||||
STIX* STIX::create()
|
||||
{
|
||||
return new STIX();
|
||||
}
|
||||
|
||||
void STIX::getDataPeriodically(int periodInMins)
|
||||
{
|
||||
if (periodInMins > 0)
|
||||
{
|
||||
m_dataTimer.setInterval(periodInMins*60*1000);
|
||||
m_dataTimer.start();
|
||||
getData();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dataTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void STIX::getData()
|
||||
{
|
||||
QUrlQuery data(QString("https://datacenter.stix.i4ds.net/api/request/flare-list"));
|
||||
QDateTime start;
|
||||
|
||||
if (m_mostRecent.isValid()) {
|
||||
start = m_mostRecent;
|
||||
} else {
|
||||
start = QDateTime::currentDateTime().addDays(-5);
|
||||
}
|
||||
|
||||
data.addQueryItem("start_utc", start.toString(Qt::ISODate));
|
||||
data.addQueryItem("end_utc", QDateTime::currentDateTime().toString(Qt::ISODate));
|
||||
data.addQueryItem("sort", "time");
|
||||
|
||||
QUrl url("https://datacenter.stix.i4ds.net/api/request/flare-list");
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
m_networkManager->post(request, data.toString(QUrl::FullyEncoded).toUtf8());
|
||||
}
|
||||
|
||||
bool STIX::containsNonNull(const QJsonObject& obj, const QString &key) const
|
||||
{
|
||||
if (obj.contains(key))
|
||||
{
|
||||
QJsonValue val = obj.value(key);
|
||||
return !val.isNull();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void STIX::handleReply(QNetworkReply* reply)
|
||||
{
|
||||
if (reply)
|
||||
{
|
||||
if (!reply->error())
|
||||
{
|
||||
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
if (document.isArray())
|
||||
{
|
||||
QJsonArray array = document.array();
|
||||
QList<FlareData> data;
|
||||
for (auto valRef : array)
|
||||
{
|
||||
if (valRef.isObject())
|
||||
{
|
||||
QJsonObject obj = valRef.toObject();
|
||||
|
||||
FlareData measurement;
|
||||
|
||||
if (obj.contains(QStringLiteral("flare_id"))) {
|
||||
measurement.m_id = obj.value(QStringLiteral("flare_id")).toString();
|
||||
}
|
||||
if (obj.contains(QStringLiteral("start_UTC")))
|
||||
{
|
||||
measurement.m_startDateTime = QDateTime::fromString(obj.value(QStringLiteral("start_UTC")).toString(), Qt::ISODate);
|
||||
if (!m_mostRecent.isValid() || (measurement.m_startDateTime > m_mostRecent)) {
|
||||
m_mostRecent = measurement.m_startDateTime;
|
||||
}
|
||||
}
|
||||
if (obj.contains(QStringLiteral("end_UTC"))) {
|
||||
measurement.m_endDateTime = QDateTime::fromString(obj.value(QStringLiteral("end_UTC")).toString(), Qt::ISODate);
|
||||
}
|
||||
if (obj.contains(QStringLiteral("peak_UTC"))) {
|
||||
measurement.m_peakDateTime = QDateTime::fromString(obj.value(QStringLiteral("peak_UTC")).toString(), Qt::ISODate);
|
||||
}
|
||||
if (obj.contains(QStringLiteral("duration"))) {
|
||||
measurement.m_duration = obj.value(QStringLiteral("duration")).toInt();
|
||||
}
|
||||
if (obj.contains(QStringLiteral("GOES_flux"))) {
|
||||
measurement.m_flux = obj.value(QStringLiteral("GOES_flux")).toDouble();
|
||||
}
|
||||
|
||||
data.append(measurement);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "STIX::handleReply: Array element is not an object: " << valRef;
|
||||
}
|
||||
}
|
||||
if (data.size() > 0)
|
||||
{
|
||||
m_data.append(data);
|
||||
emit dataUpdated(m_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "STIX::handleReply: No data in array: " << document;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "STIX::handleReply: Document is not an array: " << document;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "STIX::handleReply: error: " << reply->error();
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "STIX::handleReply: reply is null";
|
||||
}
|
||||
}
|
||||
|
||||
QString STIX::FlareData::getLightCurvesURL() const
|
||||
{
|
||||
return QString("https://datacenter.stix.i4ds.net/view/plot/lightcurves?start=%1&span=%2").arg(m_startDateTime.toSecsSinceEpoch()).arg(m_duration);
|
||||
}
|
||||
|
||||
QString STIX::FlareData::getDataURL() const
|
||||
{
|
||||
return QString("https://datacenter.stix.i4ds.net/view/list/fits/%1/%2").arg(m_startDateTime.toSecsSinceEpoch()).arg(m_duration);
|
||||
}
|
83
sdrbase/util/stix.h
Normal file
83
sdrbase/util/stix.h
Normal file
@ -0,0 +1,83 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 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_STIX_H
|
||||
#define INCLUDE_STIX_H
|
||||
|
||||
#include <QtCore>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
// Solar Orbiter STIX (Spectrometer/Telescope for Imaging X-rays) instrument
|
||||
// Gets solar flare data - Newest data is often about 24 hours old
|
||||
class SDRBASE_API STIX : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
STIX();
|
||||
|
||||
public:
|
||||
struct SDRBASE_API FlareData {
|
||||
QString m_id;
|
||||
QDateTime m_startDateTime;
|
||||
QDateTime m_endDateTime;
|
||||
QDateTime m_peakDateTime;
|
||||
int m_duration; // In seconds
|
||||
double m_flux;
|
||||
FlareData() :
|
||||
m_duration(0),
|
||||
m_flux(NAN)
|
||||
{
|
||||
}
|
||||
|
||||
QString getLightCurvesURL() const;
|
||||
QString getDataURL() const;
|
||||
};
|
||||
|
||||
static STIX* create();
|
||||
|
||||
~STIX();
|
||||
void getDataPeriodically(int periodInMins=60);
|
||||
|
||||
public slots:
|
||||
void getData();
|
||||
|
||||
private slots:
|
||||
void handleReply(QNetworkReply* reply);
|
||||
|
||||
signals:
|
||||
void dataUpdated(const QList<STIX::FlareData>& data); // Called when new data available.
|
||||
|
||||
private:
|
||||
bool containsNonNull(const QJsonObject& obj, const QString &key) const;
|
||||
|
||||
QTimer m_dataTimer; // Timer for periodic updates
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
|
||||
QDateTime m_mostRecent;
|
||||
QList<STIX::FlareData> m_data;
|
||||
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_STIX_H */
|
||||
|
Loading…
Reference in New Issue
Block a user