mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-22 17:45:48 -05:00
Plot SID paths on map.
This commit is contained in:
parent
35603a8c25
commit
535f5c5e8f
Binary file not shown.
Before Width: | Height: | Size: 915 KiB After Width: | Height: | Size: 641 KiB |
BIN
doc/img/SID_plugin_paths.png
Normal file
BIN
doc/img/SID_plugin_paths.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 935 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
@ -228,6 +228,18 @@ Specifies the date and time for which SDO imagery should be displayed. Images ar
|
||||
Select a Map to link to the SID feature. When a time is selected on the SID charts, the [Map](../../feature/map/readme.md) feature will have it's time set accordingly.
|
||||
This allows you, for example, to see the corresponding impact on MUF/foF2 displayed on the 3D map.
|
||||
|
||||
<h3>Show Paths on Map</h3>
|
||||
|
||||
When clicked, shows the great circle paths between transmitters and receivers on a [Map](../../feature/map/readme.md).
|
||||
|
||||
![SID paths](../../../doc/img/SID_plugin_paths.png)
|
||||
|
||||
The positions of the transmitters are taken from the Map's VLF database. The position of the receiver is for most devices taken from Preferences > My Position.
|
||||
For KiwiSDRs, the position is taken from the GPS position indicated by the device.
|
||||
|
||||
In order to match a transmitter in the Map's VLF database, the label used in the SID chart must match the transmitter's name. It is possible to add user-defined VLF transmitters via
|
||||
a `vlftransmitters.csv` file. See the [Map](../../feature/map/readme.md) documentation.
|
||||
|
||||
<h2>Tips</h2>
|
||||
|
||||
In order to check that a peak in the spectrum is a real VLF signal, you can:
|
||||
|
@ -33,12 +33,15 @@
|
||||
#include "device/deviceuiset.h"
|
||||
#include "util/csv.h"
|
||||
#include "util/astronomy.h"
|
||||
#include "util/vlftransmitters.h"
|
||||
|
||||
#include "ui_sidgui.h"
|
||||
#include "sid.h"
|
||||
#include "sidgui.h"
|
||||
#include "sidsettingsdialog.h"
|
||||
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
SIDGUI* SIDGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
|
||||
{
|
||||
SIDGUI* gui = new SIDGUI(pluginAPI, featureUISet, feature);
|
||||
@ -1521,6 +1524,7 @@ void SIDGUI::makeUIConnections()
|
||||
QObject::connect(ui->sdoDateTime, &WrappingDateTimeEdit::dateTimeChanged, this, &SIDGUI::on_sdoDateTime_dateTimeChanged);
|
||||
QObject::connect(ui->showSats, &QToolButton::clicked, this, &SIDGUI::on_showSats_clicked);
|
||||
QObject::connect(ui->map, &QComboBox::currentTextChanged, this, &SIDGUI::on_map_currentTextChanged);
|
||||
QObject::connect(ui->showPaths, &QToolButton::clicked, this, &SIDGUI::on_showPaths_clicked);
|
||||
QObject::connect(ui->autoscaleX, &QPushButton::clicked, this, &SIDGUI::on_autoscaleX_clicked);
|
||||
QObject::connect(ui->autoscaleY, &QPushButton::clicked, this, &SIDGUI::on_autoscaleY_clicked);
|
||||
QObject::connect(ui->today, &QPushButton::clicked, this, &SIDGUI::on_today_clicked);
|
||||
@ -1955,6 +1959,96 @@ void SIDGUI::on_map_currentTextChanged(const QString& text)
|
||||
applyDateTime();
|
||||
}
|
||||
|
||||
// Plot paths from transmitters to receivers on map
|
||||
void SIDGUI::on_showPaths_clicked()
|
||||
{
|
||||
|
||||
for (int i = 0; i < m_settings.m_channelSettings.size(); i++)
|
||||
{
|
||||
unsigned int deviceSetIndex;
|
||||
unsigned int channelIndex;
|
||||
|
||||
if (MainCore::getDeviceAndChannelIndexFromId(m_settings.m_channelSettings[i].m_id, deviceSetIndex, channelIndex))
|
||||
{
|
||||
// Get position of device, defaulting to My Position
|
||||
QGeoCoordinate rxPosition;
|
||||
if (!ChannelWebAPIUtils::getDevicePosition(deviceSetIndex, rxPosition))
|
||||
{
|
||||
rxPosition.setLatitude(MainCore::instance()->getSettings().getLatitude());
|
||||
rxPosition.setLongitude(MainCore::instance()->getSettings().getLongitude());
|
||||
rxPosition.setAltitude(MainCore::instance()->getSettings().getAltitude());
|
||||
}
|
||||
|
||||
// Get position of transmitter
|
||||
if (VLFTransmitters::m_callsignHash.contains(m_settings.m_channelSettings[i].m_label))
|
||||
{
|
||||
const VLFTransmitters::Transmitter *transmitter = VLFTransmitters::m_callsignHash.value(m_settings.m_channelSettings[i].m_label);
|
||||
QGeoCoordinate txPosition;
|
||||
txPosition.setLatitude(transmitter->m_latitude);
|
||||
txPosition.setLongitude(transmitter->m_longitude);
|
||||
txPosition.setAltitude(0);
|
||||
|
||||
// Calculate mid point for position of label
|
||||
qreal distance = txPosition.distanceTo(rxPosition);
|
||||
qreal az = txPosition.azimuthTo(rxPosition);
|
||||
QGeoCoordinate midPoint = txPosition.atDistanceAndAzimuth(distance / 2.0, az);
|
||||
|
||||
// Create a path from transmitter to receiver
|
||||
QList<ObjectPipe*> mapPipes;
|
||||
MainCore::instance()->getMessagePipes().getMessagePipes(m_sid, "mapitems", mapPipes);
|
||||
if (mapPipes.size() > 0)
|
||||
{
|
||||
for (const auto& pipe : mapPipes)
|
||||
{
|
||||
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
|
||||
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
|
||||
|
||||
QString deviceId = QString("%1%2").arg(m_settings.m_channelSettings[i].m_id[0]).arg(deviceSetIndex);
|
||||
|
||||
QString name = QString("SID %1 to %2").arg(m_settings.m_channelSettings[i].m_label).arg(deviceId);
|
||||
QString details = QString("%1<br>Distance: %2 km").arg(name).arg((int) std::round(distance / 1000.0));
|
||||
|
||||
swgMapItem->setName(new QString(name));
|
||||
swgMapItem->setLatitude(midPoint.latitude());
|
||||
swgMapItem->setLongitude(midPoint.longitude());
|
||||
swgMapItem->setAltitude(midPoint.altitude());
|
||||
QString image = QString("none");
|
||||
swgMapItem->setImage(new QString(image));
|
||||
swgMapItem->setImageRotation(0);
|
||||
swgMapItem->setText(new QString(details)); // Not used - label is used instead for now
|
||||
swgMapItem->setFixedPosition(true);
|
||||
swgMapItem->setLabel(new QString(details));
|
||||
swgMapItem->setAltitudeReference(0);
|
||||
QList<SWGSDRangel::SWGMapCoordinate *> *coords = new QList<SWGSDRangel::SWGMapCoordinate *>();
|
||||
|
||||
SWGSDRangel::SWGMapCoordinate* c = new SWGSDRangel::SWGMapCoordinate();
|
||||
c->setLatitude(rxPosition.latitude());
|
||||
c->setLongitude(rxPosition.longitude());
|
||||
c->setAltitude(rxPosition.altitude());
|
||||
coords->append(c);
|
||||
|
||||
c = new SWGSDRangel::SWGMapCoordinate();
|
||||
c->setLatitude(txPosition.latitude());
|
||||
c->setLongitude(txPosition.longitude());
|
||||
c->setAltitude(txPosition.altitude());
|
||||
coords->append(c);
|
||||
|
||||
swgMapItem->setColorValid(1);
|
||||
swgMapItem->setColor(m_settings.m_channelSettings[i].m_color.rgba());
|
||||
|
||||
swgMapItem->setCoordinates(coords);
|
||||
swgMapItem->setType(3);
|
||||
|
||||
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_sid, swgMapItem);
|
||||
messageQueue->push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void SIDGUI::featuresChanged(const QStringList& renameFrom, const QStringList& renameTo)
|
||||
{
|
||||
const AvailableChannelOrFeatureList availableFeatures = m_availableFeatureHandler.getAvailableChannelOrFeatureList();
|
||||
|
@ -305,6 +305,7 @@ private slots:
|
||||
void on_showSats_clicked();
|
||||
void onSatTrackerAdded(int featureSetIndex, Feature *feature);
|
||||
void on_map_currentTextChanged(const QString& text);
|
||||
void on_showPaths_clicked();
|
||||
void featuresChanged(const QStringList& renameFrom, const QStringList& renameTo);
|
||||
void channelsChanged(const QStringList& renameFrom, const QStringList& renameTo, const QStringList& removed, const QStringList& added);
|
||||
void removeChannels(const QStringList& ids);
|
||||
|
@ -607,6 +607,20 @@
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="showPaths">
|
||||
<property name="toolTip">
|
||||
<string>Show propagation paths on map</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/world.png</normaloff>:/world.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@ -725,17 +739,17 @@
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RollupContents</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/rollupcontents.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QChartView</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
|
@ -277,6 +277,7 @@ set(sdrbase_SOURCES
|
||||
util/units.cpp
|
||||
util/timeutil.cpp
|
||||
util/visa.cpp
|
||||
util/vlftransmitters.cpp
|
||||
util/waypoints.cpp
|
||||
util/weather.cpp
|
||||
util/iot/device.cpp
|
||||
@ -534,6 +535,7 @@ set(sdrbase_HEADERS
|
||||
util/units.h
|
||||
util/timeutil.h
|
||||
util/visa.h
|
||||
util/vlftransmitters.h
|
||||
util/waypoints.h
|
||||
util/weather.h
|
||||
util/iot/device.h
|
||||
|
@ -1156,6 +1156,35 @@ bool ChannelWebAPIUtils::getDeviceReportList(unsigned int deviceIndex, const QSt
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool ChannelWebAPIUtils::getDevicePosition(unsigned int deviceIndex, QGeoCoordinate& position)
|
||||
{
|
||||
SWGSDRangel::SWGDeviceReport deviceReport;
|
||||
|
||||
if (getDeviceReport(deviceIndex, deviceReport))
|
||||
{
|
||||
QJsonObject *jsonObj = deviceReport.asJsonObject();
|
||||
double latitude, longitude, altitude;
|
||||
|
||||
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "latitude", latitude)
|
||||
&& WebAPIUtils::getSubObjectDouble(*jsonObj, "longitude", longitude)
|
||||
&& WebAPIUtils::getSubObjectDouble(*jsonObj, "altitude", altitude))
|
||||
{
|
||||
position.setLatitude(latitude);
|
||||
position.setLongitude(longitude);
|
||||
position.setAltitude(altitude);
|
||||
// Done
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//qWarning("ChannelWebAPIUtils::getDevicePosition: no latitude/longitude/altitude in device report");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChannelWebAPIUtils::runFeature(unsigned int featureSetIndex, unsigned int featureIndex)
|
||||
{
|
||||
SWGSDRangel::SWGDeviceState runResponse;
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonArray>
|
||||
#include <QGeoCoordinate>
|
||||
|
||||
#include "SWGDeviceSettings.h"
|
||||
#include "SWGDeviceReport.h"
|
||||
@ -71,6 +72,7 @@ public:
|
||||
static bool getDeviceSetting(unsigned int deviceIndex, const QString &setting, int &value);
|
||||
static bool getDeviceReportValue(unsigned int deviceIndex, const QString &key, QString &value);
|
||||
static bool getDeviceReportList(unsigned int deviceIndex, const QString &key, const QString &subKey, QList<int> &values);
|
||||
static bool getDevicePosition(unsigned int deviceIndex, QGeoCoordinate& position);
|
||||
static bool patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value);
|
||||
static bool runFeature(unsigned int featureSetIndex, unsigned int featureIndex);
|
||||
static bool stopFeature(unsigned int featureSetIndex, unsigned int featureIndex);
|
||||
|
116
sdrbase/util/vlftransmitters.cpp
Normal file
116
sdrbase/util/vlftransmitters.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
|
||||
#include "util/csv.h"
|
||||
|
||||
#include "vlftransmitters.h"
|
||||
|
||||
// https://sidstation.loudet.org/stations-list-en.xhtml
|
||||
// https://core.ac.uk/download/pdf/224769021.pdf -- Table 1
|
||||
// GQD/GQZ callsigns: https://groups.io/g/VLF/message/19212?p=%2C%2C%2C20%2C0%2C0%2C0%3A%3Arecentpostdate%2Fsticky%2C%2C19.6%2C20%2C2%2C0%2C38924431
|
||||
QList<VLFTransmitters::Transmitter> VLFTransmitters::m_transmitters = {
|
||||
{QStringLiteral("JXN"), 16400, 66.974353, 13.873617, -1}, // Novik, Norway (Only transmits 6 times a day)
|
||||
{QStringLiteral("VTX2"), 17000, 8.387015, 77.752762, -1}, // South Vijayanarayanam, India
|
||||
{QStringLiteral("RDL"), 18100, 44.773333, 39.547222, -1}, // Krasnodar, Russia (Transmits short bursts, possibly FSK)
|
||||
{QStringLiteral("GQD"), 19580, 54.911643, -3.278456, 100}, // Anthorn, UK, Often referred to as GBZ
|
||||
{QStringLiteral("NWC"), 19800, -21.816325, 114.16546, 1000}, // Exmouth, Aus
|
||||
{QStringLiteral("ICV"), 20270, 40.922946, 9.731881, 50}, // Isola di Tavolara, Italy (Can be distorted on 3D map if terrain used)
|
||||
{QStringLiteral("FTA"), 20900, 48.544632, 2.579429, 50}, // Sainte-Assise, France (Satellite imagary obfuscated)
|
||||
{QStringLiteral("NPM"), 21400, 21.420166, -158.151140, 600}, // Pearl Harbour, Lualuahei, USA (Not seen?)
|
||||
{QStringLiteral("HWU"), 21750, 46.713129, 1.245248, 200}, // Rosnay, France
|
||||
{QStringLiteral("GQZ"), 22100, 54.731799, -2.883033, 100}, // Skelton, UK (GVT in paper)
|
||||
{QStringLiteral("DHO38"), 23400, 53.078900, 7.615000, 300}, // Rhauderfehn, Germany - Off air 7-8 UTC
|
||||
{QStringLiteral("NAA"), 24000, 44.644506, -67.284565, 1000}, // Cutler, Maine, USA
|
||||
{QStringLiteral("TBB"), 26700, 37.412725, 27.323342, -1}, // Bafa, Turkey
|
||||
{QStringLiteral("TFK/NRK"), 37500, 63.850365, -22.466773, 100}, // Grindavik, Iceland
|
||||
{QStringLiteral("SRC"), 40400, 57.120328, 16.153083, -1}, // Grimeton, Sweden
|
||||
{QStringLiteral("NSY"), 45900, 37.125660, 14.436416, -1}, // Niscemi, Italy
|
||||
{QStringLiteral("SXA"), 49000, 38.145155, 24.019718, -1}, // Marathon, Greece
|
||||
{QStringLiteral("GYW1"), 51950, 57.617463, -1.887589, -1}, // Crimond, UK
|
||||
{QStringLiteral("FUE"), 65800, 48.637673, -4.350758, -1}, // Kerlouan, France
|
||||
};
|
||||
|
||||
QHash<QString, const VLFTransmitters::Transmitter*> VLFTransmitters::m_callsignHash;
|
||||
|
||||
VLFTransmitters::Init VLFTransmitters::m_init;
|
||||
|
||||
VLFTransmitters::Init::Init()
|
||||
{
|
||||
// Get directory to store app data in
|
||||
QStringList locations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
|
||||
// First dir is writable
|
||||
QString dir = locations[0];
|
||||
|
||||
// Try reading transmitters from .csv file
|
||||
QString filename = QString("%1/%2/%3/vlftransmitters.csv").arg(dir).arg(COMPANY).arg(APPLICATION_NAME); // Because this method is called before main(), we need to add f4exb/SDRangel
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
{
|
||||
QTextStream in(&file);
|
||||
|
||||
QString error;
|
||||
QHash<QString, int> colIndexes = CSV::readHeader(in, {
|
||||
QStringLiteral("Callsign"),
|
||||
QStringLiteral("Frequency"),
|
||||
QStringLiteral("Latitude"),
|
||||
QStringLiteral("Longitude"),
|
||||
QStringLiteral("Power")
|
||||
}, error);
|
||||
if (error.isEmpty())
|
||||
{
|
||||
QStringList cols;
|
||||
int callsignCol = colIndexes.value(QStringLiteral("Callsign"));
|
||||
int frequencyCol = colIndexes.value(QStringLiteral("Frequency"));
|
||||
int latitudeCol = colIndexes.value(QStringLiteral("Latitude"));
|
||||
int longitudeCol = colIndexes.value(QStringLiteral("Longitude"));
|
||||
int powerCol = colIndexes.value(QStringLiteral("Power"));
|
||||
int maxCol = std::max({callsignCol, frequencyCol, latitudeCol, longitudeCol, powerCol});
|
||||
|
||||
m_transmitters.clear(); // Replace builtin list
|
||||
|
||||
while(CSV::readRow(in, &cols))
|
||||
{
|
||||
if (cols.size() > maxCol)
|
||||
{
|
||||
Transmitter transmitter;
|
||||
|
||||
transmitter.m_callsign = cols[callsignCol];
|
||||
transmitter.m_frequency = cols[frequencyCol].toLongLong();
|
||||
transmitter.m_latitude = cols[latitudeCol].toFloat();
|
||||
transmitter.m_longitude = cols[longitudeCol].toFloat();
|
||||
transmitter.m_power = cols[powerCol].toInt();
|
||||
|
||||
m_transmitters.append(transmitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << filename << "did not contain expected headers.";
|
||||
}
|
||||
}
|
||||
|
||||
// Create hash table for faster searching
|
||||
for (const auto& transmitter : VLFTransmitters::m_transmitters) {
|
||||
VLFTransmitters::m_callsignHash.insert(transmitter.m_callsign, &transmitter);
|
||||
}
|
||||
}
|
56
sdrbase/util/vlftransmitters.h
Normal file
56
sdrbase/util/vlftransmitters.h
Normal file
@ -0,0 +1,56 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Jon Beniston, M7RCE <jon@beniston.com> //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef INCLUDE_VLFTRANSMITTERS_H
|
||||
#define INCLUDE_VLFTRANSMITTERS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
// List of VLF transmitters
|
||||
// Built-in list can be overriden by user supplied vlftransmitters.csv file, that is read at startup, from the app data dir
|
||||
class SDRBASE_API VLFTransmitters
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
struct Transmitter {
|
||||
QString m_callsign;
|
||||
qint64 m_frequency; // In Hz
|
||||
float m_latitude;
|
||||
float m_longitude;
|
||||
int m_power; // In kW
|
||||
};
|
||||
|
||||
static QList<Transmitter> m_transmitters;
|
||||
|
||||
static QHash<QString, const Transmitter*> m_callsignHash;
|
||||
|
||||
private:
|
||||
|
||||
friend struct Init;
|
||||
struct Init {
|
||||
Init();
|
||||
};
|
||||
static Init m_init;
|
||||
|
||||
};
|
||||
|
||||
#endif /* VLFTransmitters */
|
Loading…
Reference in New Issue
Block a user