kopia lustrzana https://github.com/f4exb/sdrangel
				
				
				
			Add util classes for getting data from GOES, SDO, Solar Orbiter STIX and Fermi satellites.
							rodzic
							
								
									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 | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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 */ | ||||
| 
 | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -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 */ | ||||
|  | @ -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"; | ||||
|     } | ||||
| } | ||||
|  | @ -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 */ | ||||
|  | @ -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); | ||||
|  } | ||||
|  | @ -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 */ | ||||
| 
 | ||||
		Ładowanie…
	
		Reference in New Issue
	
	 srcejon
						srcejon