1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-03-23 20:58:42 -04:00

Merge pull request from srcejon/fix_2378

Radiosonde: Add option to display predicted paths.
This commit is contained in:
Edouard Griffiths 2025-01-23 10:26:24 +01:00 committed by GitHub
commit 19d8a1da0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 241 additions and 13 deletions

View File

@ -157,6 +157,13 @@ RadiosondeGUI::RadiosondeGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, F
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_sondeHub = SondeHub::create();
if (m_sondeHub)
{
connect(m_sondeHub, &SondeHub::prediction, this, &RadiosondeGUI::handlePrediction);
connect(&m_predicitionTimer, &QTimer::timeout, this, &RadiosondeGUI::requestPredictions);
m_predicitionTimer.setInterval(60 * 1000);
m_predicitionTimer.setSingleShot(false);
}
// Initialise chart
ui->chart->setRenderHint(QPainter::Antialiasing);
@ -257,12 +264,14 @@ void RadiosondeGUI::displaySettings()
ui->y2->setCurrentIndex((int)m_settings.m_y2);
ui->feed->setChecked(m_settings.m_feedEnabled);
ui->showPredictedPaths->setChecked(m_settings.m_showPredictedPaths);
getRollupContents()->restoreState(m_rollupState);
blockApplySettings(false);
getRollupContents()->arrangeRollups();
updatePosition();
applyShowPredictedPaths();
}
void RadiosondeGUI::onMenuDialogCalled(const QPoint &p)
@ -673,6 +682,10 @@ void RadiosondeGUI::updateRadiosondes(RS41Frame *message, QDateTime dateTime)
MainCore::instance()->getSettings().getAltitude()
);
}
if (!found) {
requestPredictions();
}
}
void RadiosondeGUI::on_radiosondes_itemSelectionChanged()
@ -908,16 +921,38 @@ void RadiosondeGUI::on_deleteAll_clicked()
{
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
// Remove from map
sendToMap(serial, "",
"", "",
"", 0.0f,
0.0f, 0.0f, 0.0f, QDateTime(),
0.0f);
clearFromMapFeature(serial, 0);
// Remove from table
ui->radiosondes->removeRow(row);
// Remove from hash and free memory
delete m_radiosondes.take(serial);
}
deletePredictedPaths();
}
void RadiosondeGUI::deletePredictedPaths()
{
for (const auto& prediction : m_predictions) {
clearFromMapFeature(prediction, 3);
}
m_predictions.clear();
}
void RadiosondeGUI::clearFromMapFeature(const QString& name, int type)
{
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_radiosonde, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setImage(new QString(""));
swgMapItem->setType(type);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_radiosonde, swgMapItem);
messageQueue->push(msg);
}
}
void RadiosondeGUI::makeUIConnections()
@ -928,6 +963,7 @@ void RadiosondeGUI::makeUIConnections()
QObject::connect(ui->y2, qOverload<int>(&QComboBox::currentIndexChanged), this, &RadiosondeGUI::on_y2_currentIndexChanged);
QObject::connect(ui->deleteAll, &QPushButton::clicked, this, &RadiosondeGUI::on_deleteAll_clicked);
QObject::connect(ui->feed, &ButtonSwitch::clicked, this, &RadiosondeGUI::on_feed_clicked);
QObject::connect(ui->showPredictedPaths, &ButtonSwitch::clicked, this, &RadiosondeGUI::on_showPredictedPaths_clicked);
}
void RadiosondeGUI::on_feed_clicked(bool checked)
@ -956,6 +992,28 @@ void RadiosondeGUI::feedSelect(const QPoint& p)
}
}
void RadiosondeGUI::on_showPredictedPaths_clicked(bool checked)
{
m_settings.m_showPredictedPaths = checked;
m_settingsKeys.append("showPredictedPaths");
applySettings();
applyShowPredictedPaths();
}
void RadiosondeGUI::applyShowPredictedPaths()
{
if (m_settings.m_showPredictedPaths)
{
requestPredictions();
m_predicitionTimer.start();
}
else
{
m_predicitionTimer.stop();
deletePredictedPaths();
}
}
// Get names of devices with radiosonde demods, for SondeHub Radio string
QStringList RadiosondeGUI::getRadios()
{
@ -965,7 +1023,7 @@ QStringList RadiosondeGUI::getRadios()
for (const auto& channel : channels)
{
DeviceAPI *device = mainCore->getDevice(channel.m_index);
DeviceAPI *device = mainCore->getDevice(channel.m_superIndex);
if (device)
{
QString name = device->getHardwareId();
@ -1019,6 +1077,71 @@ void RadiosondeGUI::updatePosition()
}
}
void RadiosondeGUI::requestPredictions()
{
if (m_sondeHub && m_settings.m_showPredictedPaths)
{
for (int row = 0; row < ui->radiosondes->rowCount(); row++)
{
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
m_sondeHub->getPrediction(serial);
}
}
}
void RadiosondeGUI::handlePrediction(const QString& serial, const QList<SondeHub::Position>& positions)
{
if (positions.size() < 2) {
return;
}
// Send to Map feature
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_radiosonde, "mapitems", mapPipes);
if (mapPipes.size() > 0)
{
QString name = QString("%1_prediction").arg(serial);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setLatitude(positions[0].m_latitude);
swgMapItem->setLongitude(positions[0].m_longitude);
swgMapItem->setAltitude(positions[0].m_altitude);
QString image = QString("none");
swgMapItem->setImage(new QString(image));
swgMapItem->setImageRotation(0);
swgMapItem->setFixedPosition(true);
swgMapItem->setLabel(new QString(serial));
swgMapItem->setAltitudeReference(0);
QList<SWGSDRangel::SWGMapCoordinate *> *coords = new QList<SWGSDRangel::SWGMapCoordinate *>();
for (const auto& position : positions)
{
SWGSDRangel::SWGMapCoordinate* c = new SWGSDRangel::SWGMapCoordinate();
c->setLatitude(position.m_latitude);
c->setLongitude(position.m_longitude);
c->setAltitude(position.m_altitude);
coords->append(c);
}
swgMapItem->setCoordinates(coords);
swgMapItem->setType(3);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_radiosonde, swgMapItem);
messageQueue->push(msg);
if (!m_predictions.contains(name)) {
m_predictions.append(name);
}
}
}
}
void RadiosondeGUI::preferenceChanged(int elementType)
{
Preferences::ElementType pref = (Preferences::ElementType)elementType;

View File

@ -108,6 +108,9 @@ private:
static const int m_minMobilePositionUpdateTime = 30; // In seconds
static const int m_minFixedPositionUpdateTime = 5 * 60;
QTimer m_predicitionTimer;
QStringList m_predictions;
explicit RadiosondeGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~RadiosondeGUI();
@ -130,6 +133,9 @@ private:
float getData(RadiosondeSettings::ChartData dataType, RadiosondeData *radiosonde, RS41Frame *message);
void updatePosition();
QStringList getRadios();
void applyShowPredictedPaths();
void deletePredictedPaths();
void clearFromMapFeature(const QString& name, int type);
enum RadiosondeCol {
RADIOSONDE_COL_SERIAL,
@ -168,6 +174,9 @@ private slots:
void on_deleteAll_clicked();
void on_feed_clicked(bool checked);
void feedSelect(const QPoint& p);
void on_showPredictedPaths_clicked(bool checked);
void requestPredictions();
void handlePrediction(const QString& serial, const QList<SondeHub::Position>& positions);
void preferenceChanged(int elementType);
};

View File

@ -31,7 +31,7 @@
<string>Radiosonde</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
<enum>Qt::LayoutDirection::LeftToRight</enum>
</property>
<widget class="QWidget" name="tableContainer" native="true">
<property name="geometry">
@ -76,20 +76,20 @@
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
<enum>Qt::Orientation::Vertical</enum>
</property>
<widget class="QTableWidget" name="radiosondes">
<property name="toolTip">
<string>Radiosondes</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<column>
<property name="text">
@ -416,6 +416,23 @@
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="showPredictedPaths">
<property name="toolTip">
<string>Show predicted paths on map</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/logarithmic.png</normaloff>:/logarithmic.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@ -60,6 +60,7 @@ void RadiosondeSettings::resetToDefaults()
m_displayPosition = false;
m_mobile = false;
m_email = "";
m_showPredictedPaths = false;
for (int i = 0; i < RADIOSONDES_COLUMNS; i++)
{
@ -95,6 +96,7 @@ QByteArray RadiosondeSettings::serialize() const
s.writeBool(17, m_displayPosition);
s.writeBool(18, m_mobile);
s.writeString(19, m_email);
s.writeBool(20, m_showPredictedPaths);
for (int i = 0; i < RADIOSONDES_COLUMNS; i++) {
@ -159,6 +161,7 @@ bool RadiosondeSettings::deserialize(const QByteArray& data)
d.readBool(17, &m_displayPosition, false);
d.readBool(18, &m_mobile, false);
d.readString(19, &m_email, "");
d.readBool(20, &m_showPredictedPaths, false);
for (int i = 0; i < RADIOSONDES_COLUMNS; i++) {
d.readS32(300 + i, &m_radiosondesColumnIndexes[i], i);
@ -224,6 +227,9 @@ void RadiosondeSettings::applySettings(const QStringList& settingsKeys, const Ra
if (settingsKeys.contains("email")) {
m_email = settings.m_email;
}
if (settingsKeys.contains("showPredictedPaths")) {
m_showPredictedPaths = settings.m_showPredictedPaths;
}
if (settingsKeys.contains("workspaceIndex")) {
m_workspaceIndex = settings.m_workspaceIndex;
}
@ -292,6 +298,9 @@ QString RadiosondeSettings::getDebugString(const QStringList& settingsKeys, bool
if (settingsKeys.contains("email") || force) {
ostr << " m_email: " << m_email.toStdString();
}
if (settingsKeys.contains("showPredictedPaths") || force) {
ostr << " m_showPredictedPaths: " << m_showPredictedPaths;
}
if (settingsKeys.contains("workspaceIndex") || force) {
ostr << " m_workspaceIndex: " << m_workspaceIndex;
}

View File

@ -62,6 +62,7 @@ struct RadiosondeSettings
bool m_displayPosition;
bool m_mobile;
QString m_email;
bool m_showPredictedPaths;
int m_radiosondesColumnIndexes[RADIOSONDES_COLUMNS];
int m_radiosondesColumnSizes[RADIOSONDES_COLUMNS];

View File

@ -7,7 +7,7 @@ based on data received via [Radiosonde Demodulators](../../channelrx/demodradios
The chart can plot two data series vs time for the radiosonde selected in the table.
The Radiosonde feature can draw balloons objects on the [Map](../../feature/map/readme.md) feature in 2D and 3D.
The Radiosonde feature can draw balloons objects and predicted paths on the [Map](../../feature/map/readme.md) feature in 2D and 3D.
Received data can be forwarded to [SondeHub](https://sondehub.org/). Your location can be displayed on the SondeHub map, as either a stationary receiver or chase car.
@ -48,6 +48,7 @@ The Radiosonde feature can plot balloons (during ascent) and parachutes (during
To use, simply open a Map feature and the Radiosonde plugin will display objects based upon the data it receives from that point.
Selecting a radiosonde item on the map will display a text bubble containing information from the above table.
To centre the map on an item in the table, double click in the Lat or Lon columns.
Predicted paths can be displayed by checking the Show Predicted Paths button. The path is predicted by SondeHub.
![Radiosonde on map](../../../doc/img/Radiosonde_plugin_map.png)

View File

@ -183,6 +183,17 @@ void SondeHub::updatePosition(
m_networkManager->put(request, data);
}
void SondeHub::getPrediction(const QString& serial)
{
QUrl url(QString("https://api.v2.sondehub.org/predictions?vehicles=%1").arg(serial));
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setHeader(QNetworkRequest::UserAgentHeader, "sdrangel");
m_networkManager->get(request);
}
void SondeHub::handleReply(QNetworkReply* reply)
{
if (reply)
@ -221,7 +232,53 @@ void SondeHub::handleReply(QNetworkReply* reply)
}
}
}
//qDebug() << "SondeHub::handleReply: obj" << QJsonDocument(obj);
//qDebug() << "SondeHub::handleReply: obj" << QJsonDocument(obj);
}
else if (document.isArray())
{
QJsonArray array = document.array();
for (auto arrayRef : array)
{
if (arrayRef.isObject())
{
QJsonObject obj = arrayRef.toObject();
if (obj.contains(QStringLiteral("vehicle")) && obj.contains(QStringLiteral("data")))
{
QJsonArray data;
// Perhaps a bug that data is a string rather than an array?
if (obj.value(QStringLiteral("data")).isString())
{
QJsonDocument dataDocument = QJsonDocument::fromJson(obj.value(QStringLiteral("data")).toString().toUtf8());
data = dataDocument.array();
}
else
{
data = obj.value(QStringLiteral("data")).toArray();
}
QList<Position> positions;
for (auto dataObjRef : data)
{
QJsonObject positionObj = dataObjRef.toObject();
Position position;
position.m_dateTime = QDateTime::fromSecsSinceEpoch(positionObj.value(QStringLiteral("time")).toInt());
position.m_latitude = positionObj.value(QStringLiteral("lat")).toDouble();
position.m_longitude = positionObj.value(QStringLiteral("lon")).toDouble();
position.m_altitude = positionObj.value(QStringLiteral("alt")).toDouble();
positions.append(position);
}
emit prediction(obj.value("vehicle").toString(), positions);
}
}
else
{
qDebug() << "SondeHub::handleReply:" << bytes;
}
}
}
else
{

View File

@ -36,6 +36,13 @@ protected:
public:
struct Position {
float m_latitude;
float m_longitude;
float m_altitude;
QDateTime m_dateTime;
};
static SondeHub* create();
~SondeHub();
@ -61,10 +68,14 @@ public:
bool mobile
);
void getPrediction(const QString& serial);
private slots:
void handleReply(QNetworkReply* reply);
signals:
void prediction(const QString& serial, const QList<Position>& path);
private:
QNetworkAccessManager *m_networkManager;