Merge pull request #1127 from srcejon/map_3d

Add 3D Map
pull/1128/head
Edouard Griffiths 2022-02-05 23:26:21 +01:00 zatwierdzone przez GitHub
commit 257a1f685e
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
547 zmienionych plików z 13772 dodań i 2406 usunięć

Wyświetl plik

@ -80,7 +80,7 @@ for:
sudo apt-get -y install build-essential cmake git xxd \
devscripts fakeroot debhelper libfftw3-dev qtbase5-dev libqt5opengl5-dev \
qttools5-dev qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins libqt5websockets5-dev \
libqt5quick5 \
libqt5quick5 qtwebengine5-dev qtbase5-private-dev \
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \
qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtgraphicaleffects \
libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \

Wyświetl plik

@ -76,7 +76,7 @@ jobs:
dir: ${{matrix.config.QT_INST_DIR}}
arch: ${{matrix.config.QT_ARCH}}
setup-python: false
modules: 'qtcharts'
modules: 'qtcharts qtwebengine'
- name: build sdrangel on Windows
if: startsWith(matrix.config.os, 'windows')
run: |

Wyświetl plik

@ -338,7 +338,10 @@ if (BUILD_GUI)
Quick
QuickWidgets
Location
TextToSpeech)
TextToSpeech
WebEngine
WebEngineCore
WebEngineWidgets)
endif()
# other requirements
@ -599,6 +602,11 @@ if(WIN32)
windeployqt(${CMAKE_PROJECT_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/sdrgui/resources)
endif()
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${CMAKE_PROJECT_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_BIN_DIR} )
endif()
# install documentation
# TODO maybe install readme for every plugins
install(FILES ${CMAKE_SOURCE_DIR}/Readme.md DESTINATION ${INSTALL_DOC_DIR})

Wyświetl plik

@ -247,7 +247,9 @@ elseif(WIN32 OR MINGW)
if(BUILD_GUI)
install(CODE "
include(BundleUtilities)
fixup_bundle(\"${SDRANGEL_BINARY_BIN_DIR}/sdrangel${CMAKE_EXECUTABLE_SUFFIX}\" \"\" \"${WINDOWS_FIXUP_BUNDLE_LIB_DIRS}\")
# BundleUtilities.cmake verify_app fails unless we ignore QtWebEngineProcess.exe, as it fails if there are any "external" prerequisites
# Should we ignore it? It appears to work OK if we do. Is there a better way?
fixup_bundle(\"${SDRANGEL_BINARY_BIN_DIR}/sdrangel${CMAKE_EXECUTABLE_SUFFIX}\" \"\" \"${WINDOWS_FIXUP_BUNDLE_LIB_DIRS}\" IGNORE_ITEM \"QtWebEngineProcess.exe\")
" COMPONENT Runtime)
elseif(BUILD_SERVER)
install(CODE "
@ -295,6 +297,7 @@ elseif(WIN32 OR MINGW)
install(DIRECTORY "${OPENSSL_DLL_DIR}/" DESTINATION "${INSTALL_LIB_DIR}"
FILES_MATCHING PATTERN "*${CMAKE_SHARED_LIBRARY_SUFFIX}")
install(CODE "
# remove *.lib files
# TODO find a better way

2
debian/control vendored
Wyświetl plik

@ -29,6 +29,8 @@ Build-Depends: debhelper (>= 9),
qtdeclarative5-dev,
qtpositioning5-dev,
qtlocation5-dev,
qtwebengine5-dev,
qtbase5-private-dev,
libusb-1.0-0-dev,
libboost-all-dev,
libasound2-dev,

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Wyświetl plik

@ -26,7 +26,7 @@ if(DAB_FOUND AND ZLIB_FOUND AND FAAD_FOUND)
add_subdirectory(demoddab)
endif()
if(APT_FOUND)
if(APT_FOUND AND SGP4_FOUND)
add_subdirectory(demodapt)
endif()

Wyświetl plik

@ -29,6 +29,7 @@ set(adsb_HEADERS
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS}
${Qt5Gui_PRIVATE_INCLUDE_DIRS}
)
if(NOT SERVER_MODE)
@ -94,3 +95,8 @@ if(WIN32)
include(DeployQt)
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/map)
endif()
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Wyświetl plik

@ -20,36 +20,35 @@
#include "adsbdemoddisplaydialog.h"
ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(
int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
bool autoResizeTableColumns, const QString& apiKey, QStringList airspaces, float airspaceRange,
ADSBDemodSettings::MapType mapType, bool displayNavAids, bool displayPhotos, QWidget* parent) :
ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWidget* parent) :
QDialog(parent),
m_fontName(fontName),
m_fontSize(fontSize),
ui(new Ui::ADSBDemodDisplayDialog)
ui(new Ui::ADSBDemodDisplayDialog),
m_settings(settings),
m_fontName(settings->m_tableFontName),
m_fontSize(settings->m_tableFontSize)
{
ui->setupUi(this);
ui->timeout->setValue(removeTimeout);
ui->airportRange->setValue(airportRange);
ui->airportSize->setCurrentIndex((int)airportMinimumSize);
ui->heliports->setChecked(displayHeliports);
ui->units->setCurrentIndex((int)siUnits);
ui->displayStats->setChecked(displayDemodStats);
ui->autoResizeTableColumns->setChecked(autoResizeTableColumns);
ui->apiKey->setText(apiKey);
for (const auto& airspace: airspaces)
ui->timeout->setValue(settings->m_removeTimeout);
ui->airportRange->setValue(settings->m_airportRange);
ui->airportSize->setCurrentIndex((int)settings->m_airportMinimumSize);
ui->heliports->setChecked(settings->m_displayHeliports);
ui->units->setCurrentIndex((int)settings->m_siUnits);
ui->displayStats->setChecked(settings->m_displayDemodStats);
ui->autoResizeTableColumns->setChecked(settings->m_autoResizeTableColumns);
ui->apiKey->setText(settings->m_apiKey);
for (const auto& airspace: settings->m_airspaces)
{
QList<QListWidgetItem *> items = ui->airspaces->findItems(airspace, Qt::MatchExactly);
for (const auto& item: items) {
item->setCheckState(Qt::Checked);
}
}
ui->airspaceRange->setValue(airspaceRange);
ui->mapType->setCurrentIndex((int)mapType);
ui->navAids->setChecked(displayNavAids);
ui->photos->setChecked(displayPhotos);
ui->airspaceRange->setValue(settings->m_airspaceRange);
ui->mapType->setCurrentIndex((int)settings->m_mapType);
ui->navAids->setChecked(settings->m_displayNavAids);
ui->photos->setChecked(settings->m_displayPhotos);
ui->verboseModelMatching->setChecked(settings->m_verboseModelMatching);
ui->airfieldElevation->setValue(settings->m_airfieldElevation);
}
ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
@ -59,26 +58,30 @@ ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
void ADSBDemodDisplayDialog::accept()
{
m_removeTimeout = ui->timeout->value();
m_airportRange = ui->airportRange->value();
m_airportMinimumSize = (ADSBDemodSettings::AirportType)ui->airportSize->currentIndex();
m_displayHeliports = ui->heliports->isChecked();
m_siUnits = ui->units->currentIndex() == 0 ? false : true;
m_displayDemodStats = ui->displayStats->isChecked();
m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked();
m_apiKey = ui->apiKey->text();
m_airspaces = QStringList();
m_settings->m_removeTimeout = ui->timeout->value();
m_settings->m_airportRange = ui->airportRange->value();
m_settings->m_airportMinimumSize = (ADSBDemodSettings::AirportType)ui->airportSize->currentIndex();
m_settings->m_displayHeliports = ui->heliports->isChecked();
m_settings->m_siUnits = ui->units->currentIndex() == 0 ? false : true;
m_settings->m_displayDemodStats = ui->displayStats->isChecked();
m_settings->m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked();
m_settings->m_apiKey = ui->apiKey->text();
m_settings->m_airspaces = QStringList();
for (int i = 0; i < ui->airspaces->count(); i++)
{
QListWidgetItem *item = ui->airspaces->item(i);
if (item->checkState() == Qt::Checked) {
m_airspaces.append(item->text());
m_settings->m_airspaces.append(item->text());
}
}
m_airspaceRange = ui->airspaceRange->value();
m_mapType = (ADSBDemodSettings::MapType)ui->mapType->currentIndex();
m_displayNavAids = ui->navAids->isChecked();
m_displayPhotos = ui->photos->isChecked();
m_settings->m_airspaceRange = ui->airspaceRange->value();
m_settings->m_mapType = (ADSBDemodSettings::MapType)ui->mapType->currentIndex();
m_settings->m_displayNavAids = ui->navAids->isChecked();
m_settings->m_displayPhotos = ui->photos->isChecked();
m_settings->m_verboseModelMatching = ui->verboseModelMatching->isChecked();
m_settings->m_airfieldElevation = ui->airfieldElevation->value();
m_settings->m_tableFontName = m_fontName;
m_settings->m_tableFontSize = m_fontSize;
QDialog::accept();
}
@ -88,7 +91,6 @@ void ADSBDemodDisplayDialog::on_font_clicked()
QFont font = QFontDialog::getFont(&ok, QFont(m_fontName, m_fontSize), this);
if (ok)
{
qDebug() << font;
m_fontName = font.family();
m_fontSize = font.pointSize();
}

Wyświetl plik

@ -25,34 +25,18 @@ class ADSBDemodDisplayDialog : public QDialog {
Q_OBJECT
public:
explicit ADSBDemodDisplayDialog(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats,
bool autoResizeTableColumns, const QString& apiKey, QStringList airspaces, float airspaceRange,
ADSBDemodSettings::MapType mapType, bool displayNavAids, bool displayPhotos, QWidget* parent = 0);
explicit ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWidget* parent = 0);
~ADSBDemodDisplayDialog();
int m_removeTimeout;
float m_airportRange;
ADSBDemodSettings::AirportType m_airportMinimumSize;
bool m_displayHeliports;
bool m_siUnits;
QString m_fontName;
int m_fontSize;
bool m_displayDemodStats;
bool m_autoResizeTableColumns;
QString m_apiKey;
QStringList m_airspaces;
float m_airspaceRange;
ADSBDemodSettings::MapType m_mapType;
bool m_displayNavAids;
bool m_displayPhotos;
private slots:
void accept();
void on_font_clicked();
private:
Ui::ADSBDemodDisplayDialog* ui;
ADSBDemodSettings *m_settings;
QString m_fontName;
int m_fontSize;
};
#endif // INCLUDE_ADSBDEMODDISPLAYDIALOG_H

Wyświetl plik

@ -7,12 +7,11 @@
<x>0</x>
<y>0</y>
<width>417</width>
<height>638</height>
<height>692</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -23,20 +22,57 @@
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="14" column="0">
<widget class="QLabel" name="displayStatsLabel">
<property name="text">
<item row="14" column="1">
<widget class="QCheckBox" name="displayStats">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Display demodulator statistics</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="airportRange">
<property name="toolTip">
<string>Displays airports within the specified distance in kilometres from My Position</string>
<item row="7" column="0">
<widget class="QLabel" name="displayNavAids">
<property name="text">
<string>Display NAVAIDs</string>
</property>
<property name="maximum">
<number>20000</number>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="units">
<property name="toolTip">
<string>The units to use for altitude, speed and climb rate</string>
</property>
<item>
<property name="text">
<string>ft, kn, ft/min</string>
</property>
</item>
<item>
<property name="text">
<string>m, kph, m/s</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="airspacesLabel">
<property name="text">
<string>Airspaces to display</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="apiKeyLabel">
<property name="text">
<string>avaitionstack API key</string>
</property>
</widget>
</item>
@ -50,6 +86,47 @@
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="heliportsLabel">
<property name="text">
<string>Display heliports</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="airspaceRange">
<property name="toolTip">
<string>Displays airspace within the specified distance in kilometres from My Position</string>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="verboseModelMatchingLabel">
<property name="text">
<string>Log 3D model matching information</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="autoResizeTableColumnsLabel">
<property name="text">
<string>Resize columns after adding aircraft</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="navAids">
<property name="toolTip">
<string>Display NAVAIDs such as VORs and NDBs</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mapTypeLabel">
<property name="text">
@ -57,20 +134,109 @@
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="airspacesLabel">
<item row="4" column="0">
<widget class="QLabel" name="airportRangeLabel">
<property name="text">
<string>Airspaces to display</string>
<string>Airport display distance (km)</string>
</property>
</widget>
</item>
<item row="15" column="1">
<item row="4" column="1">
<widget class="QSpinBox" name="airportRange">
<property name="toolTip">
<string>Displays airports within the specified distance in kilometres from My Position</string>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="displayStatsLabel">
<property name="text">
<string>Display demodulator statistics</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="airportSizeLabel">
<property name="text">
<string>Display airports with size</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QLineEdit" name="apiKey">
<property name="toolTip">
<string>aviationstack.com API key for accessing flight information</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="photosLabel">
<property name="text">
<string>Display aircraft photos</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QCheckBox" name="verboseModelMatching">
<property name="toolTip">
<string>Log information about how aircraft are matched to 3D models</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="text">
<string>Aircraft timeout (s)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="unitsLabel">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="fontLabel">
<property name="text">
<string>Table font</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="mapType">
<property name="toolTip">
<string>Type of map to display</string>
</property>
<item>
<property name="text">
<string>Aviation</string>
</property>
</item>
<item>
<property name="text">
<string>Aviation (Dark)</string>
</property>
</item>
<item>
<property name="text">
<string>Street</string>
</property>
</item>
<item>
<property name="text">
<string>Satellite</string>
</property>
</item>
</widget>
</item>
<item row="5" column="1">
<widget class="QListWidget" name="airspaces">
<property name="toolTip">
@ -228,27 +394,13 @@
</item>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="autoResizeTableColumns">
<item row="10" column="1">
<widget class="QPushButton" name="font">
<property name="toolTip">
<string>Resize the columns in the table after an aircraft is added to it</string>
<string>Select a font for the table</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="displayNavAids">
<property name="text">
<string>Display NAVAIDs</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="autoResizeTableColumnsLabel">
<property name="text">
<string>Resize columns after adding aircraft</string>
<string>Select...</string>
</property>
</widget>
</item>
@ -274,128 +426,16 @@
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="heliportsLabel">
<property name="text">
<string>Display heliports</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QCheckBox" name="displayStats">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item row="8" column="1">
<widget class="QCheckBox" name="photos">
<property name="toolTip">
<string>Display demodulator statistics</string>
<string>Download and display photos of highlighted aircraft</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="airportSizeLabel">
<property name="text">
<string>Display airports with size</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="apiKeyLabel">
<property name="text">
<string>avaitionstack API key</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="text">
<string>Aircraft timeout (s)</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="airspaceRange">
<property name="toolTip">
<string>Displays airspace within the specified distance in kilometres from My Position</string>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="units">
<property name="toolTip">
<string>The units to use for altitude, speed and climb rate</string>
</property>
<item>
<property name="text">
<string>ft, kn, ft/min</string>
</property>
</item>
<item>
<property name="text">
<string>m, kph, m/s</string>
</property>
</item>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="unitsLabel">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QPushButton" name="font">
<property name="toolTip">
<string>Select a font for the table</string>
</property>
<property name="text">
<string>Select...</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="airportRangeLabel">
<property name="text">
<string>Airport display distance (km)</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="mapType">
<property name="toolTip">
<string>Type of map to display</string>
</property>
<item>
<property name="text">
<string>Aviation</string>
</property>
</item>
<item>
<property name="text">
<string>Aviation (Dark)</string>
</property>
</item>
<item>
<property name="text">
<string>Street</string>
</property>
</item>
<item>
<property name="text">
<string>Satellite</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="airspaceRangeLabel">
<property name="text">
@ -403,6 +443,16 @@
</property>
</widget>
</item>
<item row="13" column="1">
<widget class="QCheckBox" name="autoResizeTableColumns">
<property name="toolTip">
<string>Resize the columns in the table after an aircraft is added to it</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="heliports">
<property name="toolTip">
@ -413,37 +463,26 @@
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="fontLabel">
<item row="17" column="0">
<widget class="QLabel" name="airfieldElevationLabel">
<property name="text">
<string>Table font</string>
<string>Airfield barometric altitude (ft)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="navAids">
<item row="17" column="1">
<widget class="QSpinBox" name="airfieldElevation">
<property name="toolTip">
<string>Display NAVAIDs such as VORs and NDBs</string>
<string>Barometric altitude reported by aircraft when on airfield surface</string>
</property>
<property name="text">
<string/>
<property name="minimum">
<number>-10000</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="photosLabel">
<property name="text">
<string>Display aircraft photos</string>
<property name="maximum">
<number>30000</number>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QCheckBox" name="photos">
<property name="toolTip">
<string>Download and display photos of highlighted aircraft</string>
</property>
<property name="text">
<string/>
<property name="singleStep">
<number>10</number>
</property>
</widget>
</item>

Wyświetl plik

@ -26,6 +26,7 @@
#include <QAbstractListModel>
#include <QProgressDialog>
#include <QTextToSpeech>
#include <QRandomGenerator>
#include "channel/channelgui.h"
#include "dsp/dsptypes.h"
@ -41,6 +42,8 @@
#include "settings/rollupstate.h"
#include "maincore.h"
#include "SWGMapItem.h"
#include "adsbdemodsettings.h"
#include "ourairportsdb.h"
#include "osndb.h"
@ -83,11 +86,14 @@ public:
// Data about an aircraft extracted from an ADS-B frames
struct Aircraft {
int m_icao; // 24-bit ICAO aircraft address
QString m_icaoHex;
QString m_callsign; // Flight callsign
QString m_flight; // Guess at flight number
Real m_latitude; // Latitude in decimal degrees
Real m_longitude; // Longitude in decimal degrees
int m_altitude; // Altitude in feet
bool m_onSurface; // Indicates if on surface or airbourne
bool m_altitudeGNSS; // Altitude is GNSS HAE (Height above WGS-84 ellipsoid) rather than barometric alitute (relative to 29.92 Hg)
int m_speed; // Speed in knots
enum SpeedType {
GS, // Ground speed
@ -95,14 +101,14 @@ struct Aircraft {
IAS // Indicated air speed
} m_speedType;
static const char *m_speedTypeNames[];
int m_heading; // Heading in degrees
float m_heading; // Heading in degrees
int m_verticalRate; // Vertical climb rate in ft/min
QString m_emitterCategory; // Aircraft type
QString m_status; // Aircraft status
int m_squawk; // Mode-A code
Real m_range; // Distance from station to aircraft
Real m_azimuth; // Azimuth from station to aircraft
Real m_elevation; // Elevation from station to aicraft;
Real m_elevation; // Elevation from station to aicraft
QDateTime m_time; // When last updated
bool m_positionValid; // Indicates if we have valid data for the above fields
@ -130,7 +136,28 @@ struct Aircraft {
QVariantList m_coordinates; // Coordinates we've recorded the aircraft at
AircraftInformation *m_aircraftInfo; // Info about the aircraft from the database
QString m_aircraft3DModel; // 3D model for map based on aircraft type
QString m_aircraftCat3DModel; // 3D model based on aircraft category
float m_modelAltitudeOffset; // Altitude adjustment so aircraft model doesn't go underground
float m_labelAltitudeOffset; // How height to position label above aircraft
ADSBDemodGUI *m_gui;
QString m_flagIconURL;
QString m_airlineIconURL;
// For animation on 3D map
float m_runwayAltitude;
bool m_runwayAltitudeValid;
bool m_gearDown;
float m_flaps; // 0 - no flaps, 1 - full flaps
bool m_rotorStarted; // Rotors started on 'Rotorcraft'
bool m_engineStarted; // Engines started (typically propellors)
QDateTime m_positionDateTime;
QDateTime m_orientationDateTime;
QDateTime m_headingDateTime;
QDateTime m_prevHeadingDateTime;
int m_prevHeading;
float m_pitch; // Estimated pitch based on vertical rate
float m_roll; // Estimated roll based on rate of change in heading
bool m_notified; // Set when a notification has been made for this aircraft, so we don't repeat it
@ -175,7 +202,10 @@ struct Aircraft {
m_latitude(0),
m_longitude(0),
m_altitude(0),
m_onSurface(false),
m_altitudeGNSS(false),
m_speed(0),
m_speedType(GS),
m_heading(0),
m_verticalRate(0),
m_azimuth(0),
@ -193,7 +223,17 @@ struct Aircraft {
m_isHighlighted(false),
m_showAll(false),
m_aircraftInfo(nullptr),
m_modelAltitudeOffset(0.0f),
m_labelAltitudeOffset(5.0f),
m_gui(gui),
m_runwayAltitude(0.0),
m_runwayAltitudeValid(false),
m_gearDown(false),
m_flaps(0.0),
m_rotorStarted(false),
m_engineStarted(false),
m_pitch(0.0),
m_roll(0.0),
m_notified(false)
{
for (int i = 0; i < 2; i++)
@ -237,8 +277,8 @@ struct Aircraft {
m_ataItem = new QTableWidgetItem();
}
QString getImage();
QString getText(bool all=false);
QString getImage() const;
QString getText(bool all=false) const;
// Name to use when selected as a target
QString targetName()
@ -348,6 +388,8 @@ public:
allAircraftUpdated();
}
Q_INVOKABLE void findOnMap(int index);
private:
QList<Aircraft *> m_aircrafts;
bool m_flightPaths;
@ -637,6 +679,67 @@ private:
QList<bool> m_selected;
};
// Match 3D models to Opensky-Network Aircraft database
// The database doesn't use consistent names for aircraft, so we use regexps
class ModelMatch {
public:
ModelMatch(const QString &aircraftRegExp, const QString &model) :
m_aircraftRegExp(aircraftRegExp),
m_model(model)
{
m_aircraftRegExp.optimize();
}
virtual bool match(const QString &aircraft, const QString &manufacturer, QString &model)
{
(void) manufacturer;
QRegularExpressionMatch match = m_aircraftRegExp.match(aircraft);
if (match.hasMatch())
{
model = m_model;
return true;
}
else
{
return false;
}
}
protected:
QRegularExpression m_aircraftRegExp;
QString m_model;
};
// For very generic aircraft names, also match against manufacturer name
class ManufacturerModelMatch : public ModelMatch {
public:
ManufacturerModelMatch(const QString &modelRegExp, const QString &manufacturerRegExp, const QString &model) :
ModelMatch(modelRegExp, model),
m_manufacturerRegExp(manufacturerRegExp)
{
m_manufacturerRegExp.optimize();
}
virtual bool match(const QString &aircraft, const QString &manufacturer, QString &model) override
{
QRegularExpressionMatch matchManufacturer = m_manufacturerRegExp.match(manufacturer);
if (matchManufacturer.hasMatch())
{
QRegularExpressionMatch matchAircraft = m_aircraftRegExp.match(aircraft);
if (matchAircraft.hasMatch())
{
model = m_model;
return true;
}
}
return false;
}
protected:
QRegularExpression m_manufacturerRegExp;
};
class ADSBDemodGUI : public ChannelGUI {
Q_OBJECT
@ -652,8 +755,11 @@ public:
void targetAircraft(Aircraft *aircraft);
void target(const QString& name, float az, float el, float range);
bool setFrequency(float frequency);
bool useSIUints() { return m_settings.m_siUnits; }
bool useSIUints() const { return m_settings.m_siUnits; }
Q_INVOKABLE void clearHighlighted();
QString get3DModel(const QString &aircraft, const QString &operatorICAO) const;
QString get3DModel(const QString &aircraft);
void get3DModel(Aircraft *aircraft);
public slots:
void channelMarkerChangedByCursor();
@ -683,6 +789,7 @@ private:
AirspaceModel m_airspaceModel;
NavAidModel m_navAidModel;
QHash<QString, QIcon *> m_airlineIcons; // Hashed on airline ICAO
QHash<QString, bool> m_airlineMissingIcons; // Hash containing which ICAOs we don't have icons for
QHash<QString, QIcon *> m_flagIcons; // Hashed on country
QHash<QString, QString> *m_prefixMap; // Registration to country (flag name)
QHash<QString, QString> *m_militaryMap; // Operator airforce to military (flag name)
@ -710,6 +817,12 @@ private:
quint16 m_osmPort;
OpenAIP m_openAIP;
ADSBOSMTemplateServer *m_templateServer;
QRandomGenerator m_random;
QHash<QString, QString> m_3DModels; // Hashed aircraft_icao or just aircraft
QHash<QString, QStringList> m_3DModelsByType; // Hashed aircraft to list of all of that type
QList<ModelMatch *> m_3DModelMatch; // Map of database aircraft names to 3D model names
QHash<QString, float> m_modelAltitudeOffset;
QHash<QString, float> m_labelAltitudeOffset;
explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~ADSBDemodGUI();
@ -721,11 +834,19 @@ private:
bool handleMessage(const Message& message);
void updatePosition(Aircraft *aircraft);
bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition);
void sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimation *> *animations);
void handleADSB(
const QByteArray data,
const QDateTime dateTime,
float correlation,
float correlationOnes);
float correlationOnes,
bool updateModel);
QList<SWGSDRangel::SWGMapAnimation *> *animate(QDateTime dateTime, Aircraft *aircraft);
SWGSDRangel::SWGMapAnimation *gearAnimation(QDateTime startDateTime, bool up);
SWGSDRangel::SWGMapAnimation *flapsAnimation(QDateTime startDateTime, float currentFlaps, float flaps);
SWGSDRangel::SWGMapAnimation *slatsAnimation(QDateTime startDateTime, bool retract);
SWGSDRangel::SWGMapAnimation *rotorAnimation(QDateTime startDateTime, bool stop);
SWGSDRangel::SWGMapAnimation *engineAnimation(QDateTime startDateTime, int engine, bool stop);
void checkStaticNotification(Aircraft *aircraft);
void checkDynamicNotification(Aircraft *aircraft);
void speechNotification(Aircraft *aircraft, const QString &speech);
@ -735,6 +856,7 @@ private:
QString getDataDir();
QString getAirportDBFilename();
QString getAirportFrequenciesDBFilename();
QString getOSNDBZipFilename();
QString getOSNDBFilename();
QString getFastDBFilename();
qint64 fileAgeInDays(QString filename);
@ -743,10 +865,13 @@ private:
void readAirportFrequenciesDB(const QString& filename);
bool readOSNDB(const QString& filename);
bool readFastDB(const QString& filename);
void update3DModels();
void updateAirports();
void updateAirspaces();
void updateNavAids();
QString getAirlineIconPath(const QString &operatorICAO);
QIcon *getAirlineIcon(const QString &operatorICAO);
QString getFlagIconPath(const QString &country);
QIcon *getFlagIcon(const QString &country);
void updateDeviceSetList();
QAction *createCheckableItem(QString& text, int idx, bool checked);
@ -756,6 +881,9 @@ private:
void applyMapSettings();
void updatePhotoText(Aircraft *aircraft);
void updatePhotoFlightInformation(Aircraft *aircraft);
void findOnChannelMap(Aircraft *aircraft);
int grayToBinary(int gray, int bits) const;
void redrawMap();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -766,6 +894,7 @@ private slots:
void on_threshold_valueChanged(int value);
void on_phaseSteps_valueChanged(int value);
void on_tapsPerPhase_valueChanged(int value);
void adsbData_customContextMenuRequested(QPoint point);
void on_adsbData_cellClicked(int row, int column);
void on_adsbData_cellDoubleClicked(int row, int column);
void adsbData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
@ -778,6 +907,7 @@ private slots:
void on_feed_clicked(bool checked);
void on_notifications_clicked();
void on_flightInfo_clicked();
void on_findOnMapFeature_clicked();
void on_getOSNDB_clicked();
void on_getAirportDB_clicked();
void on_getAirspacesDB_clicked();
@ -801,6 +931,8 @@ private slots:
void downloadAirspaceFinished();
void downloadNavAidsFinished();
void photoClicked();
virtual void showEvent(QShowEvent *event);
virtual bool eventFilter(QObject *obj, QEvent *event);
signals:
void homePositionChanged();

Wyświetl plik

@ -24,7 +24,6 @@
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -101,7 +100,6 @@
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
@ -190,7 +188,6 @@
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
@ -691,6 +688,23 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="findOnMapFeature">
<property name="toolTip">
<string>Find selected aircraft on feature map</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/gridpolar.png</normaloff>:/gridpolar.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="stats">
<property name="toolTip">

Wyświetl plik

@ -43,10 +43,11 @@ public:
static MsgReportADSB* create(
QByteArray data,
float preambleCorrelation,
float correlationOnes
float correlationOnes,
QDateTime dateTime
)
{
return new MsgReportADSB(data, preambleCorrelation, correlationOnes);
return new MsgReportADSB(data, preambleCorrelation, correlationOnes, dateTime);
}
private:
@ -58,14 +59,15 @@ public:
MsgReportADSB(
QByteArray data,
float preambleCorrelation,
float correlationOnes
float correlationOnes,
QDateTime dateTime
) :
Message(),
m_data(data),
m_dateTime(dateTime),
m_preambleCorrelation(preambleCorrelation),
m_correlationOnes(correlationOnes)
{
m_dateTime = QDateTime::currentDateTime();
}
};

Wyświetl plik

@ -81,6 +81,8 @@ void ADSBDemodSettings::resetToDefaults()
m_mapType = AVIATION_LIGHT;
m_displayNavAids = true;
m_displayPhotos = true;
m_verboseModelMatching = false;
m_airfieldElevation = 0;
}
QByteArray ADSBDemodSettings::serialize() const
@ -141,6 +143,9 @@ QByteArray ADSBDemodSettings::serialize() const
s.writeBlob(43, m_rollupState->serialize());
}
s.writeBool(44, m_verboseModelMatching);
s.writeS32(45, m_airfieldElevation);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
s.writeS32(100 + i, m_columnIndexes[i]);
}
@ -245,6 +250,9 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
m_rollupState->deserialize(bytetmp);
}
d.readBool(44, &m_verboseModelMatching, false);
d.readS32(45, &m_airfieldElevation, 0);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
d.readS32(100 + i, &m_columnIndexes[i], i);
}

Wyświetl plik

@ -145,6 +145,8 @@ struct ADSBDemodSettings
bool m_displayNavAids;
bool m_displayPhotos;
Serializable *m_rollupState;
bool m_verboseModelMatching;
int m_airfieldElevation; //!< QFE in ft so aircraft takeoff/land from correct position
ADSBDemodSettings();
void resetToDefaults();

Wyświetl plik

@ -134,6 +134,11 @@ void ADSBDemodSink::processOneSample(Real magsq)
m_magsqCount++;
m_sampleBuffer[m_writeBuffer][m_writeIdx] = magsq;
m_writeIdx++;
if (!m_bufferDateTimeValid[m_writeBuffer])
{
m_bufferFirstSampleDateTime[m_writeBuffer] = QDateTime::currentDateTime();
m_bufferDateTimeValid[m_writeBuffer] = true;
}
if (m_writeIdx >= m_bufferSize)
{
m_bufferRead[m_writeBuffer].release();
@ -152,6 +157,8 @@ void ADSBDemodSink::processOneSample(Real magsq)
m_startPoint = boost::chrono::steady_clock::now();
m_writeIdx = m_samplesPerFrame - 1; // Leave space for copying samples from previous buffer
m_bufferDateTimeValid[m_writeBuffer] = false;
}
}
@ -213,6 +220,7 @@ void ADSBDemodSink::init(int samplesPerBit)
m_samplesPerFrame = samplesPerBit*(ADS_B_PREAMBLE_BITS+ADS_B_ES_BITS);
m_samplesPerChip = samplesPerBit/ADS_B_CHIPS_PER_BIT;
m_writeIdx = m_samplesPerFrame - 1; // Leave space for copying samples from previous buffer
m_bufferDateTimeValid[m_writeBuffer] = false;
for (int i = 0; i < m_buffers; i++)
m_sampleBuffer[i] = new Real[m_bufferSize];

Wyświetl plik

@ -97,6 +97,8 @@ private:
Real *m_sampleBuffer[3]; //!< Each buffer is m_bufferSize samples
QSemaphore m_bufferWrite[3]; //!< Sempahore to control write access to the buffers
QSemaphore m_bufferRead[3]; //!< Sempahore to control read access from the buffers
QDateTime m_bufferFirstSampleDateTime[3]; //!< Time for first sample in the buffer
bool m_bufferDateTimeValid[3];
ADSBDemodSinkWorker m_worker; //!< Worker thread that does the actual demodulation
int m_writeBuffer; //!< Which of the 3 buffers we're writing in to
int m_writeIdx; //!< Index to to current write buffer

Wyświetl plik

@ -140,6 +140,8 @@ void ADSBDemodSinkWorker::run()
if ((preambleCorrelation > m_correlationThresholdLinear) && (preambleCorrelationOnes != 0.0f))
{
int firstIdx = startIdx;
m_demodStats.m_correlatorMatches++;
// Skip over preamble
startIdx += samplesPerBit*ADS_B_PREAMBLE_BITS;
@ -202,7 +204,8 @@ void ADSBDemodSinkWorker::run()
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
preambleCorrelation * m_correlationScale,
preambleCorrelationOnes / samplesPerChip);
preambleCorrelationOnes / samplesPerChip,
rxDateTime(firstIdx, readBuffer));
m_sink->getMessageQueueToGUI()->push(msg);
}
// Pass to worker to feed to other servers
@ -211,7 +214,8 @@ void ADSBDemodSinkWorker::run()
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
preambleCorrelation * m_correlationScale,
preambleCorrelationOnes / samplesPerChip);
preambleCorrelationOnes / samplesPerChip,
rxDateTime(firstIdx, readBuffer));
m_sink->getMessageQueueToWorker()->push(msg);
}
}
@ -245,7 +249,8 @@ void ADSBDemodSinkWorker::run()
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
preambleCorrelation * m_correlationScale,
preambleCorrelationOnes / samplesPerChip);
preambleCorrelationOnes / samplesPerChip,
rxDateTime(firstIdx, readBuffer));
m_sink->getMessageQueueToWorker()->push(msg);
}
}
@ -312,7 +317,6 @@ void ADSBDemodSinkWorker::run()
}
}
}
void ADSBDemodSinkWorker::handleInputMessages()
{
Message* message;
@ -344,3 +348,10 @@ void ADSBDemodSinkWorker::handleInputMessages()
}
}
}
QDateTime ADSBDemodSinkWorker::rxDateTime(int firstIdx, int readBuffer) const
{
const qint64 samplesPerSecondMSec = ADS_B_BITS_PER_SECOND * m_settings.m_samplesPerBit / 1000;
const qint64 offsetMSec = (firstIdx - m_sink->m_samplesPerFrame - 1) / samplesPerSecondMSec;
return m_sink->m_bufferFirstSampleDateTime[readBuffer].addMSecs(offsetMSec);
}

Wyświetl plik

@ -76,6 +76,9 @@ private:
Real m_correlationThresholdLinear;
Real m_correlationScale;
crcadsb m_crc; //!< Have as member to avoid recomputing LUT
QDateTime rxDateTime(int firstIdx, int readBuffer) const;
};
#endif // INCLUDE_ADSBDEMODSINKWORKER_H

Wyświetl plik

@ -1638,6 +1638,30 @@
<file>airlinelogos/YEL.bmp</file>
<file>airlinelogos/YZR.bmp</file>
<file>airlinelogos/ZOM.bmp</file>
<file>airlinelogos/BCS.bmp</file>
<file>airlinelogos/BEA.bmp</file>
<file>airlinelogos/CATa.bmp</file>
<file>airlinelogos/DHA.bmp</file>
<file>airlinelogos/FJO.bmp</file>
<file>airlinelogos/FPYb.bmp</file>
<file>airlinelogos/GGT.bmp</file>
<file>airlinelogos/GPX.bmp</file>
<file>airlinelogos/HGB.bmp</file>
<file>airlinelogos/ITY.bmp</file>
<file>airlinelogos/JOS.bmp</file>
<file>airlinelogos/KRU.bmp</file>
<file>airlinelogos/NDA.bmp</file>
<file>airlinelogos/NVROLD.bmp</file>
<file>airlinelogos/OCN.bmp</file>
<file>airlinelogos/PXT.bmp</file>
<file>airlinelogos/RBS.bmp</file>
<file>airlinelogos/RYS.bmp</file>
<file>airlinelogos/SKYPARK.bmp</file>
<file>airlinelogos/TES.bmp</file>
<file>airlinelogos/TMT.bmp</file>
<file>airlinelogos/WDY.bmp</file>
<file>airlinelogos/SHF.bmp</file>
<file>airlinelogos/AIO.bmp</file>
</qresource>
</RCC>

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Przed

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

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Wyświetl plik

@ -1,5 +1,6 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtLocation 5.12
import QtPositioning 5.12
import QtGraphicalEffects 1.12
@ -246,8 +247,13 @@ Item {
visible: !lightIcons
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
highlighted = true
if (mouse.button === Qt.LeftButton) {
highlighted = true
} else if (mouse.button === Qt.RightButton) {
contextMenu.popup()
}
}
onDoubleClicked: {
target = true
@ -283,11 +289,28 @@ Item {
id: text
anchors.centerIn: parent
text: adsbData
textFormat: TextEdit.RichText
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
showAll = !showAll
if (mouse.button === Qt.LeftButton) {
showAll = !showAll
} else if (mouse.button === Qt.RightButton) {
contextMenu.popup()
}
}
Menu {
id: contextMenu
MenuItem {
text: "Set as target"
onTriggered: target = true
}
MenuItem {
text: "Find on feature map"
onTriggered: aircraftModel.findOnMap(index)
}
}
}
}

Wyświetl plik

@ -30,7 +30,7 @@
#include "util/csv.h"
#define OSNDB_URL "https://opensky-network.org/datasets/metadata/aircraftDatabase.csv"
#define OSNDB_URL "https://opensky-network.org/datasets/metadata/aircraftDatabase.zip"
struct AircraftInformation {

Wyświetl plik

@ -8,6 +8,10 @@ As well as displaying information received via ADS-B, the plugin can also combin
![ADS-B Demodulator plugin GUI](../../../doc/img/ADSBDemod_plugin.png)
The ADS-B plugin can send aicraft for display on the [Map Feature](../../feature/map/readme.md) alongside objects from other plugins and in 3D.
![ADS-B on 3D Map](../../../doc/img/ADSBDemod_plugin_map_3d.png)
<h2>Interface</h2>
![ADS-B Demodulator plugin settings](../../../doc/img/ADSBDemod_plugin_settings.png)
@ -262,6 +266,7 @@ The map displays aircraft locations and data geographically. Four types of map c
![ADS-B Demodulator Map](../../../doc/img/ADSBDemod_plugin_map2.png)
The initial antenna location is placed according to My Position set under the Preferences > My Position menu. The position is only updated when the ADS-B demodulator plugin is first opened.
If My Position is not set correctly, the position of aircraft may not be computed correctly.
Aircraft are only placed upon the map when a position can be calculated, which can require several frames to be received.

Wyświetl plik

@ -56,3 +56,8 @@ target_link_libraries(${TARGET_NAME}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Wyświetl plik

@ -6,7 +6,7 @@ This plugin can be used to demodulate AIS (Automatic Identification System) mess
AIS is broadcast globally on 25kHz channels at 161.975MHz and 162.025MHz, with other frequencies being used regionally or for special purposes. This demodulator is single channel, so if you wish to decode multiple channels simulatenously, you will need to add one AIS demodulator per frequency. As most AIS messages are on 161.975MHz and 162.025MHz, you can set the center frequency as 162MHz, with a sample rate of 100k+Sa/s, with one AIS demod with an input offset -25kHz and another at +25kHz.
The AIS demodulators can send received messages to the AIS feature, which displays a table combining the latest data for vessels amalgamated from multiple demodulators and display their position on the Map Feature.
The AIS demodulators can send received messages to the [AIS feature](../../feature/ais/readme.md), which displays a table combining the latest data for vessels amalgamated from multiple demodulators and sends their positiosn to the [Map Feature](../../feature/map/readme.ais) for display in 2D or 3D.
AIS uses GMSK/FM modulation at a baud rate of 9,600, with a modulation index of 0.5. The demodulator works at a sample rate of 57,600Sa/s.

Wyświetl plik

@ -23,6 +23,7 @@ set(demodapt_HEADERS
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${APT_INCLUDE_DIR}
${SGP4_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
@ -32,12 +33,15 @@ if(NOT SERVER_MODE)
aptdemodgui.ui
aptdemodsettingsdialog.cpp
aptdemodsettingsdialog.ui
aptdemodselectdialog.cpp
aptdemodselectdialog.ui
icons.qrc
)
set(demodapt_HEADERS
${demodapt_HEADERS}
aptdemodgui.h
aptdemodsettingsdialog.h
aptdemodselectdialog.h
)
set(TARGET_NAME demodapt)
@ -59,12 +63,22 @@ if(APT_EXTERNAL)
add_dependencies(${TARGET_NAME} apt)
endif()
if(SGP4_EXTERNAL)
add_dependencies(${TARGET_NAME} sgp4)
endif()
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${APT_LIBRARIES}
${SGP4_LIBRARIES}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Wyświetl plik

@ -47,6 +47,7 @@ MESSAGE_CLASS_DEFINITION(APTDemod::MsgConfigureAPTDemod, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgPixels, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgImage, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgLine, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgMapImageName, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgResetDecoder, Message)
const char * const APTDemod::m_channelIdURI = "sdrangel.channel.aptdemod";
@ -62,7 +63,7 @@ APTDemod::APTDemod(DeviceAPI *deviceAPI) :
m_basebandSink = new APTDemodBaseband(this);
m_basebandSink->moveToThread(&m_thread);
m_imageWorker = new APTDemodImageWorker();
m_imageWorker = new APTDemodImageWorker(this);
m_basebandSink->setImagWorkerMessageQueue(m_imageWorker->getInputMessageQueue());
m_imageWorker->moveToThread(&m_imageThread);
@ -286,6 +287,42 @@ void APTDemod::applySettings(const APTDemodSettings& settings, bool force)
if ((settings.m_autoSaveMinScanLines != m_settings.m_autoSaveMinScanLines) || force) {
reverseAPIKeys.append("autoSaveMinScanLines");
}
if ((settings.m_saveCombined != m_settings.m_saveCombined) || force) {
reverseAPIKeys.append("saveCombined");
}
if ((settings.m_saveSeparate != m_settings.m_saveSeparate) || force) {
reverseAPIKeys.append("saveSeparate");
}
if ((settings.m_saveProjection != m_settings.m_saveProjection) || force) {
reverseAPIKeys.append("saveProjection");
}
if ((settings.m_scanlinesPerImageUpdate != m_settings.m_scanlinesPerImageUpdate) || force) {
reverseAPIKeys.append("scanlinesPerImageUpdate");
}
if ((settings.m_transparencyThreshold != m_settings.m_transparencyThreshold) || force) {
reverseAPIKeys.append("transparencyThreshold");
}
if ((settings.m_opacityThreshold != m_settings.m_opacityThreshold) || force) {
reverseAPIKeys.append("opacityThreshold");
}
if ((settings.m_palettes != m_settings.m_palettes) || force) {
reverseAPIKeys.append("palettes");
}
if ((settings.m_palette != m_settings.m_palette) || force) {
reverseAPIKeys.append("palette");
}
if ((settings.m_horizontalPixelsPerDegree != m_settings.m_horizontalPixelsPerDegree) || force) {
reverseAPIKeys.append("horizontalPixelsPerDegree");
}
if ((settings.m_verticalPixelsPerDegree != m_settings.m_verticalPixelsPerDegree) || force) {
reverseAPIKeys.append("verticalPixelsPerDegree");
}
if ((settings.m_satTimeOffset != m_settings.m_satTimeOffset) || force) {
reverseAPIKeys.append("satTimeOffset");
}
if ((settings.m_satYaw != m_settings.m_satYaw) || force) {
reverseAPIKeys.append("satYaw");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
@ -426,6 +463,42 @@ void APTDemod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("autoSaveMinScanLines")) {
settings.m_autoSaveMinScanLines = response.getAptDemodSettings()->getAutoSaveMinScanLines();
}
if (channelSettingsKeys.contains("saveCombined")) {
settings.m_saveCombined = response.getAptDemodSettings()->getSaveCombined();
}
if (channelSettingsKeys.contains("saveSeparate")) {
settings.m_saveSeparate = response.getAptDemodSettings()->getSaveSeparate();
}
if (channelSettingsKeys.contains("saveProjection")) {
settings.m_saveProjection = response.getAptDemodSettings()->getSaveProjection();
}
if (channelSettingsKeys.contains("scanlinesPerImageUpdate")) {
settings.m_scanlinesPerImageUpdate = response.getAptDemodSettings()->getScanlinesPerImageUpdate();
}
if (channelSettingsKeys.contains("transparencyThreshold")) {
settings.m_transparencyThreshold = response.getAptDemodSettings()->getTransparencyThreshold();
}
if (channelSettingsKeys.contains("m_opacityThreshold")) {
settings.m_opacityThreshold = response.getAptDemodSettings()->getOpacityThreshold();
}
if (channelSettingsKeys.contains("palettes")) {
settings.m_palettes = (*response.getAptDemodSettings()->getPalettes()).split(";");
}
if (channelSettingsKeys.contains("palette")) {
settings.m_palette = response.getAptDemodSettings()->getPalette();
}
if (channelSettingsKeys.contains("horizontalPixelsPerDegree")) {
settings.m_horizontalPixelsPerDegree = response.getAptDemodSettings()->getHorizontalPixelsPerDegree();
}
if (channelSettingsKeys.contains("verticalPixelsPerDegree")) {
settings.m_verticalPixelsPerDegree = response.getAptDemodSettings()->getVerticalPixelsPerDegree();
}
if (channelSettingsKeys.contains("satTimeOffset")) {
settings.m_satTimeOffset = response.getAptDemodSettings()->getSatTimeOffset();
}
if (channelSettingsKeys.contains("satYaw")) {
settings.m_satYaw = response.getAptDemodSettings()->getSatYaw();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAptDemodSettings()->getRgbColor();
}
@ -474,6 +547,18 @@ void APTDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getAptDemodSettings()->setAutoSave(settings.m_autoSave);
response.getAptDemodSettings()->setAutoSavePath(new QString(settings.m_autoSavePath));
response.getAptDemodSettings()->setAutoSaveMinScanLines(settings.m_autoSaveMinScanLines);
response.getAptDemodSettings()->setSaveCombined(settings.m_saveCombined);
response.getAptDemodSettings()->setSaveSeparate(settings.m_saveSeparate);
response.getAptDemodSettings()->setSaveProjection(settings.m_saveProjection);
response.getAptDemodSettings()->setScanlinesPerImageUpdate(settings.m_scanlinesPerImageUpdate);
response.getAptDemodSettings()->setTransparencyThreshold(settings.m_transparencyThreshold);
response.getAptDemodSettings()->setOpacityThreshold(settings.m_opacityThreshold);
response.getAptDemodSettings()->setPalettes(new QString(settings.m_palettes.join(";")));
response.getAptDemodSettings()->setPalette(settings.m_palette);
response.getAptDemodSettings()->setHorizontalPixelsPerDegree(settings.m_horizontalPixelsPerDegree);
response.getAptDemodSettings()->setVerticalPixelsPerDegree(settings.m_verticalPixelsPerDegree);
response.getAptDemodSettings()->setSatTimeOffset(settings.m_satTimeOffset);
response.getAptDemodSettings()->setSatYaw(settings.m_satYaw);
response.getAptDemodSettings()->setRgbColor(settings.m_rgbColor);
@ -599,15 +684,51 @@ void APTDemod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("decodeEnabled") || force) {
swgAPTDemodSettings->setDecodeEnabled(settings.m_decodeEnabled);
}
if (channelSettingsKeys.contains("m_autoSave") || force) {
if (channelSettingsKeys.contains("autoSave") || force) {
swgAPTDemodSettings->setAutoSave(settings.m_autoSave);
}
if (channelSettingsKeys.contains("m_autoSavePath") || force) {
if (channelSettingsKeys.contains("autoSavePath") || force) {
swgAPTDemodSettings->setAutoSavePath(new QString(settings.m_autoSavePath));
}
if (channelSettingsKeys.contains("m_autoSaveMinScanLines") || force) {
if (channelSettingsKeys.contains("autoSaveMinScanLines") || force) {
swgAPTDemodSettings->setAutoSaveMinScanLines(settings.m_autoSaveMinScanLines);
}
if (channelSettingsKeys.contains("saveCombined") || force) {
swgAPTDemodSettings->setSaveCombined(settings.m_saveCombined);
}
if (channelSettingsKeys.contains("saveSeparate") || force) {
swgAPTDemodSettings->setSaveSeparate(settings.m_saveSeparate);
}
if (channelSettingsKeys.contains("saveProjection") || force) {
swgAPTDemodSettings->setSaveProjection(settings.m_saveProjection);
}
if (channelSettingsKeys.contains("scanlinesPerImageUpdate") || force) {
swgAPTDemodSettings->setScanlinesPerImageUpdate(settings.m_scanlinesPerImageUpdate);
}
if (channelSettingsKeys.contains("transparencyThreshold") || force) {
swgAPTDemodSettings->setTransparencyThreshold(settings.m_transparencyThreshold);
}
if (channelSettingsKeys.contains("opacityThreshold") || force) {
swgAPTDemodSettings->setOpacityThreshold(settings.m_opacityThreshold);
}
if (channelSettingsKeys.contains("palettes") || force) {
swgAPTDemodSettings->setPalettes(new QString(settings.m_palettes.join(";")));
}
if (channelSettingsKeys.contains("palette") || force) {
swgAPTDemodSettings->setPalette(settings.m_palette);
}
if (channelSettingsKeys.contains("horizontalPixelsPerDegree") || force) {
swgAPTDemodSettings->setHorizontalPixelsPerDegree(settings.m_horizontalPixelsPerDegree);
}
if (channelSettingsKeys.contains("verticalPixelsPerDegree") || force) {
swgAPTDemodSettings->setVerticalPixelsPerDegree(settings.m_verticalPixelsPerDegree);
}
if (channelSettingsKeys.contains("satTimeOffset") || force) {
swgAPTDemodSettings->setSatTimeOffset(settings.m_satTimeOffset);
}
if (channelSettingsKeys.contains("satYaw") || force) {
swgAPTDemodSettings->setSatYaw(settings.m_satYaw);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgAPTDemodSettings->setRgbColor(settings.m_rgbColor);
}
@ -654,6 +775,9 @@ int APTDemod::webapiActionsPost(
// Reset for new pass
m_imageWorker->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
m_basebandSink->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
if (m_guiMessageQueue) {
m_guiMessageQueue->push(APTDemod::MsgResetDecoder::create());
}
// Save satellite name
m_imageWorker->getInputMessageQueue()->push(APTDemodImageWorker::MsgSetSatelliteName::create(*satelliteName));
@ -662,10 +786,14 @@ int APTDemod::webapiActionsPost(
APTDemodSettings settings = m_settings;
settings.m_decodeEnabled = true;
settings.m_flip = !aos->getNorthToSouthPass();
settings.m_tle = *aos->getTle();
settings.m_aosDateTime = QDateTime::fromString(*aos->getDateTime(), Qt::ISODateWithMs);
settings.m_northToSouth = aos->getNorthToSouthPass();
m_inputMessageQueue.push(MsgConfigureAPTDemod::create(settings, false));
if (m_guiMessageQueue)
if (m_guiMessageQueue) {
m_guiMessageQueue->push(MsgConfigureAPTDemod::create(settings, false));
}
}
return 202;

Wyświetl plik

@ -146,6 +146,28 @@ public:
{}
};
// Sent from worker to GUI to indicate name of image on Map
class MsgMapImageName : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getName() const { return m_name; }
static MsgMapImageName* create(const QString &name)
{
return new MsgMapImageName(name);
}
private:
QString m_name;
MsgMapImageName(const QString &name) :
Message(),
m_name(name)
{
}
};
// Sent from GUI to reset decoder
class MsgResetDecoder : public Message {
MESSAGE_CLASS_DECLARATION

Wyświetl plik

@ -25,8 +25,10 @@
#include <QAction>
#include <QRegExp>
#include <QFileDialog>
#include <QFileInfo>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
#include "aptdemodgui.h"
@ -50,6 +52,45 @@
#include "aptdemod.h"
#include "aptdemodsink.h"
#include "aptdemodsettingsdialog.h"
#include "aptdemodselectdialog.h"
#include "SWGMapItem.h"
TempScale::TempScale(QGraphicsItem *parent) :
QGraphicsRectItem(parent)
{
// Temp scale appears to be -100 to +60C
// We just draw -100 to +50C, so it's nicely divides up according to the palette
setRect(30, 30, 25, 240);
m_gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
m_gradient.setStart(0.0, 0.0);
m_gradient.setFinalStop(0.0, 1.0);
for (int i = 0; i < 240; i++)
{
int idx = (240 - i) * 3;
QColor color((unsigned char)apt_TempPalette[idx], (unsigned char)apt_TempPalette[idx+1], (unsigned char)apt_TempPalette[idx+2]);
m_gradient.setColorAt(i/240.0, color);
}
}
void TempScale::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
int left = rect().left() + rect().width() + 10;
painter->setPen(QPen(Qt::black));
painter->setBrush(m_gradient);
painter->drawRect(rect());
painter->drawText(left, rect().top(), "50C");
painter->drawText(left, rect().top() + rect().height() * 1 / 6, "25C");
painter->drawText(left, rect().top() + rect().height() * 2 / 6, "0C");
painter->drawText(left, rect().top() + rect().height() * 3 / 6, "-25C");
painter->drawText(left, rect().top() + rect().height() * 4 / 6, "-50C");
painter->drawText(left, rect().top() + rect().height() * 5 / 6, "-75C");
painter->drawText(left, rect().top() + rect().height(), "-100C");
}
APTDemodGUI* APTDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
@ -76,7 +117,8 @@ QByteArray APTDemodGUI::serialize() const
bool APTDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
if(m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
@ -103,19 +145,29 @@ bool APTDemodGUI::handleMessage(const Message& message)
{
const APTDemod::MsgImage& imageMsg = (APTDemod::MsgImage&) message;
m_image = imageMsg.getImage();
m_pixmap.convertFromImage(m_image);
if (m_pixmapItem != nullptr)
// Display can be corrupted if we try to drawn an image with 0 height
if (m_image.height() > 0)
{
m_pixmapItem->setPixmap(m_pixmap);
if (ui->zoomAll->isChecked()) {
m_pixmap.convertFromImage(m_image);
if (m_pixmapItem != nullptr)
{
m_pixmapItem->setPixmap(m_pixmap);
if (ui->zoomAll->isChecked()) {
ui->image->fitInView(m_pixmapItem, Qt::KeepAspectRatio);
}
}
else
{
m_pixmapItem = m_scene->addPixmap(m_pixmap);
m_pixmapItem->setPos(0, 0);
ui->image->fitInView(m_pixmapItem, Qt::KeepAspectRatio);
}
}
else
{
m_pixmapItem = m_scene->addPixmap(m_pixmap);
m_pixmapItem->setPos(0, 0);
ui->image->fitInView(m_pixmapItem, Qt::KeepAspectRatio);
bool temp = m_settings.m_channels == APTDemodSettings::TEMPERATURE;
m_tempScale->setVisible(temp);
m_tempScaleBG->setVisible(temp);
if (!temp) {
m_tempText->setVisible(false);
}
}
QStringList imageTypes = imageMsg.getImageTypes();
@ -126,20 +178,24 @@ bool APTDemodGUI::handleMessage(const Message& message)
}
else
{
if (imageTypes[0].isEmpty())
if (imageTypes[0].isEmpty()) {
ui->channelALabel->setText("Channel A");
else
} else {
ui->channelALabel->setText(imageTypes[0]);
if (imageTypes[1].isEmpty())
}
if (imageTypes[1].isEmpty()) {
ui->channelBLabel->setText("Channel B");
else
} else {
ui->channelBLabel->setText(imageTypes[1]);
}
}
QString satelliteName = imageMsg.getSatelliteName();
if (!satelliteName.isEmpty())
if (!satelliteName.isEmpty()) {
ui->imageContainer->setWindowTitle("Received image from " + satelliteName);
else
} else {
ui->imageContainer->setWindowTitle("Received image");
}
return true;
}
else if (APTDemod::MsgLine::match(message))
@ -203,6 +259,18 @@ bool APTDemodGUI::handleMessage(const Message& message)
return true;
}
else if (APTDemod::MsgMapImageName::match(message))
{
const APTDemod::MsgMapImageName& mapNameMsg = (APTDemod::MsgMapImageName&) message;
QString name = mapNameMsg.getName();
if (!m_mapImages.contains(name)) {
m_mapImages.append(name);
}
}
else if (APTDemod::MsgResetDecoder::match(message))
{
resetDecoder();
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
@ -261,24 +329,88 @@ void APTDemodGUI::on_fmDev_valueChanged(int value)
applySettings();
}
void APTDemodGUI::on_channels_currentIndexChanged(int index)
void APTDemodGUI::displayLabels()
{
m_settings.m_channels = (APTDemodSettings::ChannelSelection)index;
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS)
{
ui->channelALabel->setVisible(true);
ui->channelBLabel->setVisible(true);
ui->precipitation->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A)
{
ui->channelALabel->setVisible(true);
ui->channelBLabel->setVisible(false);
ui->precipitation->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_B)
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(true);
ui->precipitation->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::TEMPERATURE)
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(false);
ui->precipitation->setVisible(false);
}
else
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(true);
ui->channelBLabel->setVisible(false);
ui->precipitation->setVisible(false);
}
}
void APTDemodGUI::on_channels_currentIndexChanged(int index)
{
if (index <= (int)APTDemodSettings::CHANNEL_B)
{
m_settings.m_channels = (APTDemodSettings::ChannelSelection)index;
}
else if (index == (int)APTDemodSettings::TEMPERATURE)
{
m_settings.m_channels = APTDemodSettings::TEMPERATURE;
m_settings.m_precipitationOverlay = false;
}
else
{
m_settings.m_channels = APTDemodSettings::PALETTE;
m_settings.m_palette = index - (int)APTDemodSettings::PALETTE;
m_settings.m_precipitationOverlay = false;
}
displayLabels();
applySettings();
}
void APTDemodGUI::on_transparencyThreshold_valueChanged(int value)
{
m_settings.m_transparencyThreshold = value;
ui->transparencyThresholdText->setText(QString::number(m_settings.m_transparencyThreshold));
// Don't applySettings while tracking, as processing an image takes a long time
if (!ui->transparencyThreshold->isSliderDown()) {
applySettings();
}
}
void APTDemodGUI::on_transparencyThreshold_sliderReleased()
{
applySettings();
}
void APTDemodGUI::on_opacityThreshold_valueChanged(int value)
{
m_settings.m_opacityThreshold = value;
ui->opacityThresholdText->setText(QString::number(m_settings.m_opacityThreshold));
// Don't applySettings while tracking, as processing an image takes a long time
if (!ui->opacityThreshold->isSliderDown()) {
applySettings();
}
}
void APTDemodGUI::on_opacityThreshold_sliderReleased()
{
applySettings();
}
@ -315,10 +447,11 @@ void APTDemodGUI::on_precipitation_clicked(bool checked)
void APTDemodGUI::on_flip_clicked(bool checked)
{
m_settings.m_flip = checked;
if (m_settings.m_flip)
if (m_settings.m_flip) {
ui->image->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
else
} else {
ui->image->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
}
applySettings();
}
@ -328,13 +461,22 @@ void APTDemodGUI::on_startStop_clicked(bool checked)
applySettings();
}
void APTDemodGUI::on_resetDecoder_clicked()
void APTDemodGUI::resetDecoder()
{
if (m_pixmapItem != nullptr) {
if (m_pixmapItem != nullptr)
{
m_image = QImage();
m_pixmapItem->setPixmap(QPixmap());
}
ui->imageContainer->setWindowTitle("Received image");
// Send message to reset decoder
ui->channelALabel->setText("Channel A");
ui->channelBLabel->setText("Channel B");
}
void APTDemodGUI::on_resetDecoder_clicked()
{
resetDecoder();
// Send message to reset decoder to other parts of demod
m_aptDemod->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
}
@ -342,7 +484,10 @@ void APTDemodGUI::on_showSettings_clicked()
{
APTDemodSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
{
displayPalettes();
applySettings();
}
}
// Save image to disk
@ -356,8 +501,9 @@ void APTDemodGUI::on_saveImage_clicked()
if (fileNames.size() > 0)
{
qDebug() << "APT: Saving image to " << fileNames;
if (!m_image.save(fileNames[0]))
if (!m_image.save(fileNames[0])) {
QMessageBox::critical(this, "APT Demodulator", QString("Failed to save image to %1").arg(fileNames[0]));
}
}
}
}
@ -489,10 +635,28 @@ APTDemodGUI::APTDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_zoom = new GraphicsViewZoom(ui->image); // Deleted automatically when view is deleted
connect(m_zoom, SIGNAL(zoomed()), this, SLOT(on_image_zoomed()));
// Create slightly transparent white background so labels can be seen
m_tempScale = new TempScale();
m_tempScale->setZValue(2.0);
m_tempScale->setVisible(false);
QRectF rect = m_tempScale->rect();
m_tempScaleBG = new QGraphicsRectItem(rect.left()-10, rect.top()-15, rect.width()+60, rect.height()+45);
m_tempScaleBG->setPen(QColor(200, 200, 200, 200));
m_tempScaleBG->setBrush(QColor(200, 200, 200, 200));
m_tempScaleBG->setZValue(1.0);
m_tempScaleBG->setVisible(false);
m_tempText = new QGraphicsSimpleTextItem("");
m_tempText->setZValue(3.0);
m_tempText->setVisible(false);
m_scene = new QGraphicsScene(ui->image);
m_scene->addItem(m_tempScale);
m_scene->addItem(m_tempScaleBG);
m_scene->addItem(m_tempText);
ui->image->setScene(m_scene);
ui->image->show();
m_scene->installEventFilter(this);
displaySettings();
applySettings(true);
}
@ -502,6 +666,55 @@ APTDemodGUI::~APTDemodGUI()
delete ui;
}
bool APTDemodGUI::eventFilter(QObject *obj, QEvent *event)
{
if ((obj == m_scene) && (m_settings.m_channels == APTDemodSettings::TEMPERATURE))
{
if (event->type() == QEvent::GraphicsSceneMouseMove)
{
QGraphicsSceneMouseEvent *mouseEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
// Find temperature under cursor
int x = round(mouseEvent->scenePos().x());
int y = round(mouseEvent->scenePos().y());
if ((x >= 0) && (y >= 0) && (x < m_image.width()) && (y < m_image.height()))
{
// Map from colored temperature pixel back to greyscale level
// This is perhaps a bit slow - might be better to give GUI access to greyscale image as well
QRgb p = m_image.pixel(x, y);
int r = qRed(p);
int g = qGreen(p);
int b = qBlue(p);
int i;
for (i = 0; i < 256; i++)
{
if ( (r == (unsigned char)apt_TempPalette[i*3])
&& (g == (unsigned char)apt_TempPalette[i*3+1])
&& (b == (unsigned char)apt_TempPalette[i*3+2]))
{
// Map from palette index to degrees C
int temp = (i / 255.0) * 160.0 - 100.0;
m_tempText->setText(QString("%1C").arg(temp));
int width = m_tempText->boundingRect().width();
int height = m_tempText->boundingRect().height();
QRectF rect = m_tempScaleBG->rect();
m_tempText->setPos(rect.left()+rect.width()/2-width/2, rect.top()+rect.height()-height-5);
m_tempText->setVisible(true);
break;
}
}
if (i == 256) {
m_tempText->setVisible(false);
}
}
else
{
m_tempText->setVisible(false);
}
}
}
return ChannelGUI::eventFilter(obj, event);
}
void APTDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
@ -538,6 +751,11 @@ void APTDemodGUI::displaySettings()
ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0);
ui->transparencyThreshold->setValue(m_settings.m_transparencyThreshold);
ui->transparencyThresholdText->setText(QString::number(m_settings.m_transparencyThreshold));
ui->opacityThreshold->setValue(m_settings.m_opacityThreshold);
ui->opacityThresholdText->setText(QString::number(m_settings.m_opacityThreshold));
ui->startStop->setChecked(m_settings.m_decodeEnabled);
ui->cropNoise->setChecked(m_settings.m_cropNoise);
ui->denoise->setChecked(m_settings.m_denoise);
@ -552,14 +770,38 @@ void APTDemodGUI::displaySettings()
ui->image->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
}
ui->channels->setCurrentIndex((int)m_settings.m_channels);
displayPalettes();
displayLabels();
displayStreamIndex();
restoreState(m_rollupState);
blockApplySettings(false);
}
void APTDemodGUI::displayPalettes()
{
ui->channels->blockSignals(true);
ui->channels->clear();
ui->channels->addItem("Both");
ui->channels->addItem("A");
ui->channels->addItem("B");
ui->channels->addItem("Temperature");
for (auto palette : m_settings.m_palettes)
{
QFileInfo fi(palette);
ui->channels->addItem(fi.baseName());
}
if (m_settings.m_channels == APTDemodSettings::PALETTE)
{
ui->channels->setCurrentIndex(((int)m_settings.m_channels) + m_settings.m_palette);
}
else
{
ui->channels->setCurrentIndex((int)m_settings.m_channels);
}
ui->channels->blockSignals(false);
}
void APTDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
@ -598,3 +840,47 @@ void APTDemodGUI::tick()
m_tickCount++;
}
void APTDemodGUI::on_deleteImageFromMap_clicked()
{
// If more than one image, pop up a dialog to select which to delete
if (m_mapImages.size() > 1)
{
APTDemodSelectDialog dialog(m_mapImages, this);
if (dialog.exec() == QDialog::Accepted)
{
for (auto name : dialog.getSelected())
{
deleteImageFromMap(name);
m_mapImages.removeAll(name);
}
}
}
else
{
for (auto name : m_mapImages) {
deleteImageFromMap(name);
}
m_mapImages.clear();
}
}
void APTDemodGUI::deleteImageFromMap(const QString &name)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_aptDemod, "mapitems");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setImage(new QString()); // Set image to "" to delete it
swgMapItem->setType(1);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aptDemod, swgMapItem);
(*it)->push(msg);
}
}
}

Wyświetl plik

@ -30,6 +30,7 @@
#include <QMenu>
#include <QImage>
#include <QPixmap>
#include <QGraphicsRectItem>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
@ -52,6 +53,16 @@ namespace Ui {
}
class APTDemodGUI;
// Temperature scale
class TempScale : public QObject, public QGraphicsRectItem {
Q_OBJECT
public:
TempScale(QGraphicsItem *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
private:
QLinearGradient m_gradient;
};
class APTDemodGUI : public ChannelGUI {
Q_OBJECT
@ -63,6 +74,7 @@ public:
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual bool eventFilter(QObject *watched, QEvent *event) override;
public slots:
void channelMarkerChangedByCursor();
@ -87,6 +99,11 @@ private:
QGraphicsScene* m_scene;
QGraphicsPixmapItem* m_pixmapItem;
GraphicsViewZoom* m_zoom;
TempScale *m_tempScale;
QGraphicsRectItem *m_tempScaleBG;
QGraphicsSimpleTextItem *m_tempText;
QList<QString> m_mapImages;
explicit APTDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~APTDemodGUI();
@ -94,8 +111,12 @@ private:
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayPalettes();
void displayLabels();
void displayStreamIndex();
bool handleMessage(const Message& message);
void deleteImageFromMap(const QString &name);
void resetDecoder();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -105,6 +126,11 @@ private slots:
void on_rfBW_valueChanged(int index);
void on_fmDev_valueChanged(int value);
void on_channels_currentIndexChanged(int index);
void on_transparencyThreshold_valueChanged(int value);
void on_transparencyThreshold_sliderReleased();
void on_opacityThreshold_valueChanged(int value);
void on_opacityThreshold_sliderReleased();
void on_deleteImageFromMap_clicked();
void on_cropNoise_clicked(bool checked=false);
void on_denoise_clicked(bool checked=false);
void on_linear_clicked(bool checked=false);

Wyświetl plik

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>451</width>
<width>440</width>
<height>569</height>
</rect>
</property>
@ -18,13 +18,12 @@
</property>
<property name="minimumSize">
<size>
<width>352</width>
<width>440</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -105,7 +104,6 @@
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
@ -198,7 +196,6 @@
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
@ -275,26 +272,6 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
@ -352,6 +329,132 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="transparencyThresholdLabel">
<property name="text">
<string>T&lt;sub&gt;TH&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="transparencyThreshold">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Transparency threshold for image on map</string>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="transparencyThresholdText">
<property name="minimumSize">
<size>
<width>22</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>255</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="opacityThresholdLabel">
<property name="text">
<string>O&lt;sub&gt;TH&lt;/sub&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="opacityThreshold">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Opacity threshold for image on map</string>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="opacityThresholdText">
<property name="minimumSize">
<size>
<width>22</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>255</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="deleteImageFromMap">
<property name="toolTip">
<string>Delete images from map</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -729,7 +832,6 @@
<tabstops>
<tabstop>deltaFrequency</tabstop>
<tabstop>rfBW</tabstop>
<tabstop>fmDev</tabstop>
<tabstop>startStop</tabstop>
<tabstop>showSettings</tabstop>
<tabstop>resetDecoder</tabstop>

Wyświetl plik

@ -19,17 +19,25 @@
#include <algorithm>
#include <QTime>
#include <QBuffer>
#include <QDebug>
#include "maincore.h"
#include "util/units.h"
#include "aptdemod.h"
#include "aptdemodimageworker.h"
#include "SWGMapItem.h"
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgConfigureAPTDemodImageWorker, Message)
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgSaveImageToDisk, Message)
MESSAGE_CLASS_DEFINITION(APTDemodImageWorker::MsgSetSatelliteName, Message)
APTDemodImageWorker::APTDemodImageWorker() :
APTDemodImageWorker::APTDemodImageWorker(APTDemod *aptDemod) :
m_messageQueueToGUI(nullptr),
m_aptDemod(aptDemod),
m_sgp4(nullptr),
m_running(false),
m_mutex(QMutex::Recursive)
{
@ -51,6 +59,8 @@ APTDemodImageWorker::~APTDemodImageWorker()
delete[] m_image.prow[y];
delete[] m_tempImage.prow[y];
}
delete m_sgp4;
}
void APTDemodImageWorker::reset()
@ -129,6 +139,8 @@ bool APTDemodImageWorker::handleMessage(const Message& cmd)
void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool force)
{
(void) force;
bool callRecalcCoords = false;
bool callProcessImage = false;
if ((settings.m_cropNoise != m_settings.m_cropNoise) ||
@ -137,14 +149,52 @@ void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool f
(settings.m_histogramEqualise != m_settings.m_histogramEqualise) ||
(settings.m_precipitationOverlay != m_settings.m_precipitationOverlay) ||
(settings.m_flip != m_settings.m_flip) ||
(settings.m_channels != m_settings.m_channels))
(settings.m_channels != m_settings.m_channels) ||
(settings.m_transparencyThreshold != m_settings.m_transparencyThreshold) ||
(settings.m_opacityThreshold != m_settings.m_opacityThreshold) ||
(settings.m_palettes != m_settings.m_palettes) ||
(settings.m_palette != m_settings.m_palette) ||
(settings.m_horizontalPixelsPerDegree != m_settings.m_horizontalPixelsPerDegree) ||
(settings.m_verticalPixelsPerDegree != m_settings.m_verticalPixelsPerDegree))
{
// Call after settings have been applied
callProcessImage = true;
}
if ((settings.m_satTimeOffset != m_settings.m_satTimeOffset) ||
(settings.m_satYaw != m_settings.m_satYaw))
{
callRecalcCoords = true;
callProcessImage = true;
}
if (!settings.m_decodeEnabled && m_settings.m_decodeEnabled)
{
// Decode complete - make sure we do a full image update
// so we aren't left we unprocessed lines
callProcessImage = true;
}
if (settings.m_palettes != m_settings.m_palettes)
{
// Load colour palettes
m_palettes.clear();
for (auto palette : settings.m_palettes)
{
QImage img;
img.load(palette);
if ((img.width() != 256) || (img.height() != 256)) {
qWarning() << "APT colour palette " << palette << " is not 256x256 pixels - " << img.width() << "x" << img.height();
}
m_palettes.append(img);
}
}
m_settings = settings;
if (callRecalcCoords) {
recalcCoords();
}
if (callProcessImage) {
sendImageToGUI();
}
@ -153,25 +203,234 @@ void APTDemodImageWorker::applySettings(const APTDemodSettings& settings, bool f
void APTDemodImageWorker::resetDecoder()
{
m_image.nrow = 0;
m_image.zenith = 0;
m_tempImage.nrow = 0;
m_tempImage.zenith = 0;
m_greyImage = QImage(APT_IMG_WIDTH, APT_MAX_HEIGHT, QImage::Format_Grayscale8);
m_greyImage.fill(0);
m_colourImage = QImage(APT_IMG_WIDTH, APT_MAX_HEIGHT, QImage::Format_RGB888);
m_colourImage.fill(0);
m_satelliteName = "";
m_satCoords.clear();
m_pixelCoords.clear();
delete m_sgp4;
m_sgp4 = nullptr;
}
// Convert Qt QDataTime to QGP4 DateTime
static DateTime qDateTimeToDateTime(QDateTime qdt)
{
QDateTime utc = qdt.toUTC();
QDate date = utc.date();
QTime time = utc.time();
DateTime dt;
dt.Initialise(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second(), time.msec() * 1000);
return dt;
}
// Get heading in range [0,360)
static double normaliseHeading(double heading)
{
return fmod(heading + 360.0, 360.0);
}
// Get longitude in range -180,180
static double normaliseLongitude(double lon)
{
return fmod(lon + 540.0, 360.0) - 180.0;
}
// Calculate heading (azimuth) in degrees
double APTDemodImageWorker::calcHeading(CoordGeodetic from, CoordGeodetic to) const
{
// From https://en.wikipedia.org/wiki/Azimuth Section In Geodesy
double flattening = 1.0 / 298.257223563; // For WGS84 ellipsoid
double eSq = flattening * (2.0 - flattening);
double oneMinusESq = (1.0 - flattening) * (1.0 - flattening);
double tl1 = tan(from.latitude);
double tl2 = tan(to.latitude);
double n1 = 1.0 + oneMinusESq * tl2 * tl2;
double d1 = 1.0 + oneMinusESq * tl1 * tl1;
double l = to.longitude - from.longitude;
double alpha;
if (from.latitude == 0.0)
{
alpha = atan2(sin(l), (oneMinusESq * tan(to.latitude)));
}
else
{
double lambda = oneMinusESq * tan(to.latitude) / tan(from.latitude) + eSq * sqrt(n1/d1);
alpha = atan2(sin(l), ((lambda - cos(l)) * sin(from.latitude)));
}
double deg = Units::radiansToDegrees(alpha);
if (!m_settings.m_northToSouth) {
deg += 180.0;
}
deg = normaliseHeading(deg);
return deg;
}
// CoordGeodetic are in radians. Distance in metres. Bearing in radians.
// https://www.movable-type.co.uk/scripts/latlong.html
// This approximates Earth as spherical. If we need more accurate algorithm, see:
// https://www.movable-type.co.uk/scripts/latlong-vincenty.html
static void calcRadialEndPoint(CoordGeodetic start, double distance, double bearing, CoordGeodetic &end)
{
double earthRadius = 6378137.0; // At equator
double delta = distance/earthRadius;
end.latitude = std::asin(sin(start.latitude)*cos(delta) + cos(start.latitude)*sin(delta)*cos(bearing));
end.longitude = start.longitude + std::atan2(sin(bearing)*sin(delta)*cos(start.latitude), cos(delta) - sin(start.latitude)*sin(end.latitude));
end.longitude = normaliseLongitude(end.longitude);
}
void APTDemodImageWorker::calcPixelCoords(CoordGeodetic centreCoord, double heading)
{
// Calculate coordinates of each pixel in a row (swath)
// Assume satellite is at centre pixel, and project +-90 degrees from satellite heading
// https://www.ncei.noaa.gov/pub/data/satellite/publications/podguides/N-15%20thru%20N-19/pdf/APPENDIX%20J%20Instrument%20Scan%20Properties.pdf
// Swath for AVHRR/3 of 2926.6km at 833km altitude over spherical Earth
// Some docs say resolution is 4.0km, but it varies as per fig 4.2.3-1 in:
// https://www.ncei.noaa.gov/pub/data/satellite/publications/podguides/N-15%20thru%20N-19/pdf/2.1%20Section%204.0%20Real%20Time%20Data%20Systems%20for%20Local%20Users%20.pdf
// TODO: Could try to adjust for altitude
QVector<CoordGeodetic> pixelCoords(APT_CH_WIDTH);
pixelCoords[APT_CH_WIDTH/2] = centreCoord;
double heading1 = Units::degreesToRadians(heading + m_settings.m_satYaw + 90.0);
double heading2 = Units::degreesToRadians(heading + m_settings.m_satYaw - 90.0);
for (int i = 1; i <= APT_CH_WIDTH/2; i++)
{
double distance = i * 2926600.0/APT_CH_WIDTH;
calcRadialEndPoint(centreCoord, distance, heading1, pixelCoords[APT_CH_WIDTH/2-i]);
calcRadialEndPoint(centreCoord, distance, heading2, pixelCoords[APT_CH_WIDTH/2+i]);
}
if (m_settings.m_northToSouth) {
m_pixelCoords.append(pixelCoords);
} else {
m_pixelCoords.prepend(pixelCoords);
}
}
// Recalculate all pixel coordiantes as satTimeOffset or satYaw has changed
void APTDemodImageWorker::recalcCoords()
{
if (m_sgp4)
{
m_satCoords.clear();
m_pixelCoords.clear();
for (int row = 0; row < m_image.nrow; row++)
{
QDateTime qdt = m_settings.m_aosDateTime.addMSecs(m_settings.m_satTimeOffset * 1000.0f + row * 500);
calcCoords(qdt, row);
}
}
}
// Calculate pixel coordinates for a single row at the given date and time
void APTDemodImageWorker::calcCoords(QDateTime qdt, int row)
{
try
{
DateTime dt = qDateTimeToDateTime(qdt);
// Calculate satellite position
Eci eci = m_sgp4->FindPosition(dt);
// Convert satellite position to geodetic coordinates (lat and long)
CoordGeodetic geo = eci.ToGeodetic();
m_satCoords.append(geo);
// Calculate satellite heading (Could convert eci.Velocity() instead)
double heading;
if (m_satCoords.size() == 2)
{
heading = calcHeading(m_satCoords[0], m_satCoords[1]);
calcPixelCoords(m_satCoords[0], heading);
calcPixelCoords(m_satCoords[1], heading);
}
else if (m_satCoords.size() > 2)
{
heading = calcHeading(m_satCoords[row-1], m_satCoords[row]);
calcPixelCoords(geo, heading);
}
}
catch (SatelliteException& se)
{
qDebug() << "APTDemodImageWorker::calcCoord: " << se.what();
}
catch (DecayedException& de)
{
qDebug() << "APTDemodImageWorker::calcCoord: " << de.what();
}
catch (TleException& tlee)
{
qDebug() << "APTDemodImageWorker::calcCoord: " << tlee.what();
}
}
// Calculate satellite's geodetic coordinates and heading
void APTDemodImageWorker::calcCoord(int row)
{
if (row == 0)
{
QStringList elements = m_settings.m_tle.trimmed().split("\n");
if (elements.size() == 3)
{
// Initalise SGP4
Tle tle(elements[0].toStdString(), elements[1].toStdString(), elements[2].toStdString());
m_sgp4 = new SGP4(tle);
// Output time so we can check time offset from when AOS is signalled
qDebug() << "APTDemod: Processing row 0 at " << QDateTime::currentDateTime();
calcCoords(m_settings.m_aosDateTime, row);
}
else
{
qDebug() << "APTDemodImageWorker::calcCoord: No TLE for satellite. Is Satellite Tracker running?";
return;
}
}
else if (m_sgp4 == nullptr)
{
return;
}
else
{
// Calculate time at which
// Don't try to use QDateTime::currentDateTime() as processing & scheduling delays mean
// it's not constant and can sometimes even be 0
// Lines should be transmitted at 2 per second, so just use number of rows since AOS
// We add a user-defined delay to account for delays in transferring SDR data and demodulation
QDateTime qdt = m_settings.m_aosDateTime.addMSecs(m_settings.m_satTimeOffset * 1000.0f + row * 500);
calcCoords(qdt, row);
}
}
void APTDemodImageWorker::processPixels(const float *pixels)
{
std::copy(pixels, pixels + APT_PROW_WIDTH, m_image.prow[m_image.nrow]);
if (m_image.nrow < APT_MAX_HEIGHT)
{
// Calculate lat and lon of centre of row
calcCoord(m_image.nrow);
if (m_image.nrow % 20 == 0) { // send full image only every 20 lines
sendImageToGUI();
} else { // else send unprocessed line just to show stg is moving
sendLineToGUI();
std::copy(pixels, pixels + APT_PROW_WIDTH, m_image.prow[m_image.nrow]);
m_image.nrow++;
if (m_image.nrow % m_settings.m_scanlinesPerImageUpdate == 0) { // send full image only every N lines
sendImageToGUI();
} else { // else send unprocessed line just to show stg is moving
sendLineToGUI();
}
}
m_image.nrow++;
}
void APTDemodImageWorker::sendImageToGUI()
@ -180,8 +439,278 @@ void APTDemodImageWorker::sendImageToGUI()
if (m_messageQueueToGUI)
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
QImage image = processImage(imageTypes, m_settings.m_channels);
m_messageQueueToGUI->push(APTDemod::MsgImage::create(image, imageTypes, m_satelliteName));
if (m_sgp4) {
sendImageToMap(image);
}
}
}
// Find the value of the pixel closest to the given coordinates
// If we have previously found a pixel, we constrain the search to be nearby, in order to speed up the search
QRgb APTDemodImageWorker::findNearest(const QImage &image, double latitude, double longitude, int xPrevious, int yPrevious, int &xNearest, int &yNearest) const
{
double dmin = 360.0 * 360.0 + 90.0 * 90.0;
xNearest = -1;
yNearest = -1;
QRgb p = qRgba(0, 0, 0, 0); // Transparent
int xMin, xMax;
int yMin, yMax;
int yStartPostCrop;
int yEndPostCrop;
if (m_settings.m_northToSouth)
{
yStartPostCrop = abs(m_tempImage.zenith);
yEndPostCrop = yStartPostCrop + image.height();
}
else
{
yStartPostCrop = m_image.nrow - m_tempImage.nrow - abs(m_tempImage.zenith);
yEndPostCrop = yStartPostCrop + image.height();
}
if (xPrevious == -1)
{
yMin = yStartPostCrop;
yMax = yEndPostCrop;
xMin = 0;
xMax = m_pixelCoords[0].size();
}
else
{
int searchRadius = 4;
yMin = yPrevious - searchRadius;
yMax = yPrevious + searchRadius + 1;
xMin = xPrevious - searchRadius;
xMax = xPrevious + searchRadius + 1;
yMin = std::max(yMin, yStartPostCrop);
yMax = std::min(yMax, yEndPostCrop);
xMin = std::max(xMin, 0);
xMax = std::min(xMax, m_pixelCoords[0].size());
}
const int ySize = yEndPostCrop-1;
const int xSize = m_pixelCoords[0].size()-1;
for (int y = yMin; y < yMax; y++)
{
for (int x = xMin; x < xMax; x++)
{
CoordGeodetic coord = m_pixelCoords[y][x];
double dlat = coord.latitude - latitude;
double dlon = coord.longitude - longitude;
double d = dlat * dlat + dlon * dlon;
if (d < dmin)
{
dmin = d;
xNearest = x;
yNearest = y;
// Only use color of pixel if we're inside the source image
if ( ((y != yStartPostCrop) || ((y == yStartPostCrop) && (latitude <= coord.latitude)))
&& ((y != ySize) || ((y == ySize) && (latitude >= coord.latitude)))
&& ((x != 0) || ((x == 0) && (longitude >= coord.longitude)))
&& ((x != xSize) || ((x == xSize) && (longitude <= coord.longitude)))
)
{
p = image.pixel(x, y - yStartPostCrop);
}
else
{
p = qRgba(0, 0, 0, 0); // Transparent
}
}
}
}
return p;
}
// Calculate bounding box for projected image in terms of latitude and longitude
// TODO: Handle crossing of anti-meridian
void APTDemodImageWorker::calcBoundingBox(double &east, double &south, double &west, double &north, const QImage &image)
{
int start;
if (m_settings.m_northToSouth) {
start = abs(m_tempImage.zenith);
} else {
start = m_image.nrow - m_tempImage.nrow - abs(m_tempImage.zenith);
}
int stop = start + image.height();
east = -M_PI;
west = M_PI;
north = -M_PI/2.0;
south = M_PI/2.0;
//FILE *f = fopen("coords.txt", "w");
for (int y = start; y < stop; y++)
{
for (int x = 0; x < m_pixelCoords[y].size(); x++)
{
double latitude = m_pixelCoords[y][x].latitude;
double longitude = m_pixelCoords[y][x].longitude;
//fprintf(f, "%f,%f ", Units::radiansToDegrees(m_pixelCoords[y][x].latitude), Units::radiansToDegrees(m_pixelCoords[y][x].longitude));
south = std::min(latitude, south);
north = std::max(latitude, north);
east = std::max(longitude, east);
west = std::min(longitude, west);
}
//fprintf(f, "\n");
}
//fclose(f);
}
// Project satellite image to equidistant cyclindrical projection (Plate Carree) for use on 3D Map
// We've previously computed lat and lon for each pixel in satellite image
// so we just work through coords in projected image, trying to find closest pixel in satellite image
// FIXME: How do we handle sat going over the poles?
QImage APTDemodImageWorker::projectImage(const QImage &image)
{
double east, south, west, north;
// Calculate bounding box for image tile
calcBoundingBox(east, south, west, north, image);
m_tileEast = ceil(Units::radiansToDegrees(east));
m_tileWest = floor(Units::radiansToDegrees(west));
m_tileNorth = ceil(Units::radiansToDegrees(north));
m_tileSouth = floor(Units::radiansToDegrees(south));
double widthDeg = m_tileEast - m_tileWest;
double heightDeg = m_tileNorth - m_tileSouth;
int width = widthDeg * m_settings.m_horizontalPixelsPerDegree;
int height = heightDeg * m_settings.m_verticalPixelsPerDegree;
//image.save("source.png");
//FILE *f = fopen("mapping.txt", "w");
QImage projection(width, height, QImage::Format_ARGB32);
int xNearest, yNearest, xPrevious, yPrevious;
xPrevious = -1;
yPrevious = -1;
for (int y = 0; y < height; y++)
{
// Calculate geodetic coords of pixel in projected image
double lat = m_tileNorth - (y / (double)m_settings.m_verticalPixelsPerDegree);
// Reverse search direction in alternate rows, so we are always seaching
// close to previously found pixel
if ((y & 1) == 0)
{
for (int x = 0; x < width; x++)
{
double lon = m_tileWest + (x / (double)m_settings.m_horizontalPixelsPerDegree);
// Find closest pixel in source image
QRgb pixel = findNearest(image, Units::degreesToRadians(lat), Units::degreesToRadians(lon), xPrevious, yPrevious, xNearest, yNearest);
xPrevious = xNearest;
yPrevious = yNearest;
projection.setPixel(x, y, pixel);
//fprintf(f, "%f,%f,%d,%d,%d ", lat, lon, xNearest, yNearest, pixel==0);
}
}
else
{
for (int x = width - 1; x >= 0; x--)
{
double lon = m_tileWest + (x / (double)m_settings.m_horizontalPixelsPerDegree);
// Find closest pixel in source image
QRgb pixel = findNearest(image, Units::degreesToRadians(lat), Units::degreesToRadians(lon), xPrevious, yPrevious, xNearest, yNearest);
xPrevious = xNearest;
yPrevious = yNearest;
projection.setPixel(x, y, pixel);
//fprintf(f, "%f,%f,%d,%d,%d ", lat, lon, xNearest, yNearest, pixel==0);
}
}
//fprintf(f, "\n");
}
//fclose(f);
return projection;
}
// Make an image transparent, so when overlaid on 3D map, we can see the underlying terrain
// Image is full transparent below m_transparencyThreshold and fully opaque above m_opacityThreshold
void APTDemodImageWorker::makeTransparent(QImage &image)
{
for (int y = 0; y < image.height(); y++)
{
for (int x = 0; x < image.width(); x++)
{
QRgb pixel = image.pixel(x, y);
int grey = qGray(pixel);
if (grey < m_settings.m_transparencyThreshold)
{
// Make fully transparent
pixel = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), 0);
image.setPixel(x, y, pixel);
}
else if (grey < m_settings.m_opacityThreshold)
{
// Make slightly transparent
float opacity = 1.0f - ((m_settings.m_opacityThreshold - grey) / (float)(m_settings.m_opacityThreshold - m_settings.m_transparencyThreshold));
opacity = opacity * 255.0f;
opacity = std::min(255.0f, opacity);
opacity = std::max(0.0f, opacity);
pixel = qRgba(qRed(pixel), qGreen(pixel), qBlue(pixel), (int)std::round(opacity));
image.setPixel(x, y, pixel);
}
}
}
}
void APTDemodImageWorker::sendImageToMap(QImage image)
{
// Send to Map feature
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_aptDemod, "mapitems");
if (mapMessageQueues)
{
// Only display one channel on map
QImage selectedChannel;
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS) {
selectedChannel = extractImage(image, APTDemodSettings::CHANNEL_B);
} else {
selectedChannel = image;
}
// Project image to geodetic coords (lat & lon)
selectedChannel = projectImage(selectedChannel);
//selectedChannel.save("projected.png");
// Use alpha channel to remove land & sea
makeTransparent(selectedChannel);
// Encode image as base64 PNG
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
selectedChannel.save(&buffer, "PNG");
QByteArray data = ba.toBase64();
// Create name for the image
QString satName = m_satelliteName;
satName.replace(" ", "_");
QString name = QString("apt_%1_%2").arg(satName).arg(m_settings.m_aosDateTime.toString("yyyyMMdd_hhmmss"));
// Send name to GUI
m_messageQueueToGUI->push(APTDemod::MsgMapImageName::create(name));
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setImage(new QString(data));
swgMapItem->setAltitude(3000.0); // Typical cloud height - So it appears above objects on the ground
swgMapItem->setType(1);
swgMapItem->setImageTileEast(m_tileEast);
swgMapItem->setImageTileWest(m_tileWest);
swgMapItem->setImageTileNorth(m_tileNorth);
swgMapItem->setImageTileSouth(m_tileSouth);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aptDemod, swgMapItem);
(*it)->push(msg);
}
}
}
@ -189,7 +718,7 @@ void APTDemodImageWorker::sendLineToGUI()
{
if (m_messageQueueToGUI)
{
float *pixels = m_image.prow[m_image.nrow];
float *pixels = m_image.prow[m_image.nrow-1];
uchar *line;
APTDemod::MsgLine *msg = APTDemod::MsgLine::create(&line);
@ -219,12 +748,12 @@ void APTDemodImageWorker::sendLineToGUI()
}
}
QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
QImage APTDemodImageWorker::processImage(QStringList& imageTypes, APTDemodSettings::ChannelSelection channels)
{
copyImage(&m_tempImage, &m_image);
// Calibrate channels according to wavelength
if (m_tempImage.nrow >= APT_CALIBRATION_ROWS)
// Calibrate channels according to wavelength (1.7x to stop flickering)
if (m_tempImage.nrow >= 1.7 * APT_CALIBRATION_ROWS)
{
m_tempImage.chA = apt_calibrate(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
m_tempImage.chB = apt_calibrate(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
@ -233,9 +762,9 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
"Visible (0.58-0.68 um)",
"Near-IR (0.725-1.0 um)",
"Near-IR (1.58-1.64 um)",
"Mid-infrared (3.55-3.93 um)",
"Thermal-infrared (10.3-11.3 um)",
"Thermal-infrared (11.5-12.5 um)"
"Thermal-infrared (11.5-12.5 um)",
"Mid-infrared (3.55-3.93 um)"
});
imageTypes.append(channelTypes[m_tempImage.chA]);
@ -243,8 +772,9 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
}
// Crop noise due to low elevation at top and bottom of image
if (m_settings.m_cropNoise)
if (m_settings.m_cropNoise) {
m_tempImage.zenith -= apt_cropNoise(&m_tempImage);
}
// Denoise filter
if (m_settings.m_denoise)
@ -305,10 +835,65 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
}
}
}
return extractImage(m_colourImage);
return extractImage(m_colourImage, channels);
}
else if (channels == APTDemodSettings::TEMPERATURE)
{
// Temperature calibration
int satnum = 15;
if (m_satelliteName == "NOAA 18") {
satnum = 18;
} else if (m_satelliteName == "NOAA 19") {
satnum = 19;
}
apt_temperature(satnum, &m_tempImage, APT_CHB_OFFSET, APT_CH_WIDTH);
// Apply colour palette
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_colourImage.scanLine(r);
for (int i = 0; i < APT_CH_WIDTH; i++)
{
float p = m_tempImage.prow[r][i+APT_CHB_OFFSET];
uchar q = roundAndClip(p);
l[i*3] = apt_TempPalette[q*3];
l[i*3+1] = apt_TempPalette[q*3+1];
l[i*3+2] = apt_TempPalette[q*3+2];
}
}
return m_colourImage.copy(0, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
else if (channels == APTDemodSettings::PALETTE)
{
if ((m_settings.m_palette >= 0) && (m_settings.m_palette < m_palettes.size()))
{
// Apply colour palette
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_colourImage.scanLine(r);
for (int i = 0; i < APT_CH_WIDTH; i++)
{
float pA = m_tempImage.prow[r][i+APT_CHA_OFFSET];
float pB = m_tempImage.prow[r][i+APT_CHB_OFFSET];
uchar qA = roundAndClip(pA);
uchar qB = roundAndClip(pB);
QRgb rgb = m_palettes[m_settings.m_palette].pixel(qA, qB);
l[i*3] = qRed(rgb);
l[i*3+1] = qGreen(rgb);
l[i*3+2] = qBlue(rgb);
}
}
return m_colourImage.copy(0, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
else
{
qDebug() << "APTDemodImageWorker::processImage - Illegal palette number: " << m_settings.m_palette;
return QImage();
}
}
else
{
// Extract grey-scale image
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_greyImage.scanLine(r);
@ -319,43 +904,92 @@ QImage APTDemodImageWorker::processImage(QStringList& imageTypes)
l[i] = roundAndClip(p);
}
}
return extractImage(m_greyImage);
return extractImage(m_greyImage, channels);
}
}
QImage APTDemodImageWorker::extractImage(QImage image)
QImage APTDemodImageWorker::extractImage(QImage image, APTDemodSettings::ChannelSelection channels)
{
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS) {
if (channels == APTDemodSettings::BOTH_CHANNELS) {
return image.copy(0, 0, APT_IMG_WIDTH, m_tempImage.nrow);
} else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A) {
} else if (channels == APTDemodSettings::CHANNEL_A) {
return image.copy(APT_CHA_OFFSET, 0, APT_CH_WIDTH, m_tempImage.nrow);
} else {
return image.copy(APT_CHB_OFFSET, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
}
void APTDemodImageWorker::prependPath(QString &filename)
{
if (!m_settings.m_autoSavePath.isEmpty())
{
if (m_settings.m_autoSavePath.endsWith('/')) {
filename = m_settings.m_autoSavePath + filename;
} else {
filename = m_settings.m_autoSavePath + '/' + filename;
}
}
}
void APTDemodImageWorker::saveImageToDisk()
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
QImage image = processImage(imageTypes, APTDemodSettings::BOTH_CHANNELS);
if (image.height() >= m_settings.m_autoSaveMinScanLines)
{
QString filename;
QDateTime datetime = QDateTime::currentDateTime();
filename = QString("apt_%1_%2.png").arg(m_satelliteName.replace(" ", "_")).arg(datetime.toString("yyyyMMdd_hhmm"));
QDateTime dateTime;
QString dt;
if (m_settings.m_aosDateTime.isValid()) {
dateTime = m_settings.m_aosDateTime;
} else {
dateTime = QDateTime::currentDateTime();
}
dt = dateTime.toString("yyyyMMdd_hhmm");
QString sat = m_satelliteName;
sat.replace(" ", "_");
if (!m_settings.m_autoSavePath.isEmpty())
if (m_settings.m_saveCombined)
{
if (m_settings.m_autoSavePath.endsWith('/')) {
filename = m_settings.m_autoSavePath + filename;
} else {
filename = m_settings.m_autoSavePath + '/' + filename;
filename = QString("apt_%1_%2.png").arg(sat).arg(dt);
prependPath(filename);
if (!image.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
}
if (!image.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
QImage chA = extractImage(image, APTDemodSettings::CHANNEL_A);
QImage chB = extractImage(image, APTDemodSettings::CHANNEL_B);
if (m_settings.m_saveSeparate)
{
filename = QString("apt_%1_%2_cha.png").arg(sat).arg(dt);
prependPath(filename);
if (!chA.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
filename = QString("apt_%1_%2_chb.png").arg(sat).arg(dt);
prependPath(filename);
if (!chB.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
}
if (m_settings.m_saveProjection)
{
filename = QString("apt_%1_%2_cha_eqi_cylindrical.png").arg(sat).arg(dt);
prependPath(filename);
QImage chAProj = projectImage(chA);
if (!chAProj.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
filename = QString("apt_%1_%2_chb_eqi_cylindrical.png").arg(sat).arg(dt);
prependPath(filename);
QImage chBProj = projectImage(chB);
if (!chBProj.save(filename)) {
qCritical() << "Failed to save APT image to: " << filename;
}
}
}
}

Wyświetl plik

@ -25,14 +25,22 @@
#include <apt.h>
#include <CoordTopocentric.h>
#include <CoordGeodetic.h>
#include <Observer.h>
#include <SGP4.h>
#include "util/messagequeue.h"
#include "util/message.h"
#include "aptdemodsettings.h"
class APTDemod;
class APTDemodImageWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureAPTDemodImageWorker : public Message {
MESSAGE_CLASS_DECLARATION
@ -95,7 +103,7 @@ public:
}
};
APTDemodImageWorker();
APTDemodImageWorker(APTDemod *aptDemod);
~APTDemodImageWorker();
void reset();
void startWork();
@ -109,6 +117,7 @@ private:
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_messageQueueToGUI;
APTDemodSettings m_settings;
APTDemod *m_aptDemod;
// Image buffers
apt_image_t m_image; // Received image
@ -117,18 +126,39 @@ private:
QImage m_colourImage;
QString m_satelliteName;
QList<CoordGeodetic> m_satCoords; // Lat,lon for satellite for each image row - in received order for both pass directions
QVector<QVector<CoordGeodetic>> m_pixelCoords; // Coordinates for each pixel - reversed y order for south to north passes, so always highest lat first
SGP4 *m_sgp4; // For calculating satellite position
double m_tileEast; // Bounding box for projected image, in degrees
double m_tileWest;
double m_tileNorth;
double m_tileSouth;
QList<QImage> m_palettes;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void applySettings(const APTDemodSettings& settings, bool force = false);
void resetDecoder();
double calcHeading(CoordGeodetic from, CoordGeodetic to) const;
void calcPixelCoords(CoordGeodetic centreCoord, double heading);
void recalcCoords();
void calcCoords(QDateTime qdt, int row);
void calcCoord(int row);
void processPixels(const float *pixels);
void sendImageToGUI();
QRgb findNearest(const QImage &image, double latitude, double longitude, int xPrevious, int yPrevious, int &xNearest, int &yNearest) const;
void calcBoundingBox(double &east, double &south, double &west, double &north, const QImage &image);
QImage projectImage(const QImage &image);
void makeTransparent(QImage &image);
void sendImageToMap(QImage image);
void sendLineToGUI();
void prependPath(QString &filename);
void saveImageToDisk();
QImage processImage(QStringList& imageTypes);
QImage extractImage(QImage image);
QImage processImage(QStringList& imageTypes, APTDemodSettings::ChannelSelection channels);
QImage extractImage(QImage image, APTDemodSettings::ChannelSelection channels);
static void copyImage(apt_image_t *dst, apt_image_t *src);
static uchar roundAndClip(float p);

Wyświetl plik

@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 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 <QDebug>
#include <QFileDialog>
#include "aptdemodselectdialog.h"
APTDemodSelectDialog::APTDemodSelectDialog(const QStringList &list, QWidget* parent) :
QDialog(parent),
ui(new Ui::APTDemodSelectDialog)
{
ui->setupUi(this);
for (auto item : list) {
ui->list->addItem(item);
}
}
APTDemodSelectDialog::~APTDemodSelectDialog()
{
delete ui;
}
void APTDemodSelectDialog::accept()
{
QList<QListWidgetItem *> items = ui->list->selectedItems();
m_selected.clear();
for (auto item : items)
{
m_selected.append(item->text());
}
QDialog::accept();
}

Wyświetl plik

@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 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_APTDEMODSELECTDIALOG_H
#define INCLUDE_APTDEMODSELECTDIALOG_H
#include "ui_aptdemodselectdialog.h"
#include "aptdemodsettings.h"
class APTDemodSelectDialog : public QDialog {
Q_OBJECT
public:
explicit APTDemodSelectDialog(const QStringList &list, QWidget* parent = 0);
~APTDemodSelectDialog();
QStringList getSelected() const { return m_selected; }
private slots:
void accept();
private:
QStringList m_selected;
Ui::APTDemodSelectDialog* ui;
};
#endif // INCLUDE_APTDEMODSELECTDIALOG_H

Wyświetl plik

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>APTDemodSelectDialog</class>
<widget class="QDialog" name="APTDemodSelectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>304</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>APT Demodulator Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="listLabel">
<property name="text">
<string>Select images to delete from the map</string>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="list">
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>APTDemodSelectDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>APTDemodSelectDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Wyświetl plik

@ -48,6 +48,18 @@ void APTDemodSettings::resetToDefaults()
m_autoSave = false;
m_autoSavePath = "";
m_autoSaveMinScanLines = 200;
m_saveCombined = true;
m_saveSeparate = false;
m_saveProjection = false;
m_scanlinesPerImageUpdate = 20;
m_transparencyThreshold = 100;
m_opacityThreshold = 200;
m_palettes.clear();
m_palette = 0;
m_horizontalPixelsPerDegree = 10;
m_verticalPixelsPerDegree = 20;
m_satTimeOffset = 0.0f;
m_satYaw = 0.0f;
m_rgbColor = QColor(216, 112, 169).rgb();
m_title = "APT Demodulator";
@ -79,6 +91,8 @@ QByteArray APTDemodSettings::serialize() const
s.writeBool(15, m_autoSave);
s.writeString(16, m_autoSavePath);
s.writeS32(17, m_autoSaveMinScanLines);
s.writeBool(18, m_saveProjection);
s.writeS32(19, m_scanlinesPerImageUpdate);
if (m_channelMarker) {
s.writeBlob(20, m_channelMarker->serialize());
@ -96,6 +110,17 @@ QByteArray APTDemodSettings::serialize() const
s.writeBlob(28, m_rollupState->serialize());
}
s.writeBool(29, m_saveCombined);
s.writeBool(30, m_saveSeparate);
s.writeS32(31, m_transparencyThreshold);
s.writeS32(32, m_opacityThreshold);
s.writeString(33, m_palettes.join(";"));
s.writeS32(34, m_palette);
s.writeS32(35, m_horizontalPixelsPerDegree);
s.writeS32(36, m_verticalPixelsPerDegree);
s.writeFloat(37, m_satTimeOffset);
s.writeFloat(38, m_satYaw);
return s.final();
}
@ -132,6 +157,8 @@ bool APTDemodSettings::deserialize(const QByteArray& data)
d.readBool(15, &m_autoSave, false);
d.readString(16, &m_autoSavePath, "");
d.readS32(17, &m_autoSaveMinScanLines, 200);
d.readBool(18, &m_saveProjection, false);
d.readS32(19, &m_scanlinesPerImageUpdate, 20);
if (m_channelMarker)
{
@ -162,6 +189,19 @@ bool APTDemodSettings::deserialize(const QByteArray& data)
m_rollupState->deserialize(bytetmp);
}
d.readBool(29, &m_saveCombined, true);
d.readBool(30, &m_saveSeparate, false);
d.readS32(31, &m_transparencyThreshold, 100);
d.readS32(32, &m_opacityThreshold, 200);
d.readString(33, &strtmp);
m_palettes = strtmp.split(";");
m_palettes.removeAll("");
d.readS32(34, &m_palette, 0);
d.readS32(35, &m_horizontalPixelsPerDegree, 10);
d.readS32(36, &m_verticalPixelsPerDegree, 20);
d.readFloat(37, &m_satTimeOffset, 0.0f);
d.readFloat(38, &m_satYaw, 0.0f);
return true;
}
else

Wyświetl plik

@ -21,6 +21,7 @@
#include <QByteArray>
#include <QHash>
#include <QDateTime>
class Serializable;
@ -35,18 +36,29 @@ struct APTDemodSettings
bool m_histogramEqualise;
bool m_precipitationOverlay;
bool m_flip;
enum ChannelSelection {BOTH_CHANNELS, CHANNEL_A, CHANNEL_B} m_channels;
enum ChannelSelection {BOTH_CHANNELS, CHANNEL_A, CHANNEL_B, TEMPERATURE, PALETTE} m_channels;
bool m_decodeEnabled;
bool m_satelliteTrackerControl; //! Whether Sat Tracker can set direction of pass
QString m_satelliteName; //!< All, NOAA 15, NOAA 18 or NOAA 19
bool m_autoSave;
QString m_autoSavePath;
int m_autoSaveMinScanLines;
bool m_saveCombined;
bool m_saveSeparate;
bool m_saveProjection;
int m_scanlinesPerImageUpdate;
int m_transparencyThreshold;
int m_opacityThreshold;
QStringList m_palettes; // List of 256x256 images to use a colour palette
int m_palette; // Index in to m_palettes - only if m_channels==PALETTE
int m_horizontalPixelsPerDegree; // Resolution for projected image
int m_verticalPixelsPerDegree;
float m_satTimeOffset;
float m_satYaw;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
@ -55,6 +67,11 @@ struct APTDemodSettings
uint16_t m_reverseAPIChannelIndex;
Serializable *m_rollupState;
// The following are really working state, rather than settings
QString m_tle; // Satelite two-line elements, from satellite tracker
QDateTime m_aosDateTime; // When decoder was started (may not be current time, if replaying old file)
bool m_northToSouth; // Separate from flip, in case user changes it
APTDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }

Wyświetl plik

@ -25,12 +25,27 @@ APTDemodSettingsDialog::APTDemodSettingsDialog(APTDemodSettings *settings, QWidg
m_settings(settings),
ui(new Ui::APTDemodSettingsDialog)
{
int idx;
ui->setupUi(this);
ui->satelliteTrackerControl->setChecked(settings->m_satelliteTrackerControl);
ui->satellite->setCurrentText(settings->m_satelliteName);
ui->autoSave->setChecked(settings->m_autoSave);
ui->saveCombined->setChecked(settings->m_saveCombined);
ui->saveSeparate->setChecked(settings->m_saveSeparate);
ui->saveProjection->setChecked(settings->m_saveProjection);
ui->autoSavePath->setText(settings->m_autoSavePath);
ui->minScanlines->setValue(settings->m_autoSaveMinScanLines);
ui->scanlinesPerImageUpdate->setValue(settings->m_scanlinesPerImageUpdate);
idx = ui->horizontalPixelsPerDegree->findText(QString::number(settings->m_horizontalPixelsPerDegree));
ui->horizontalPixelsPerDegree->setCurrentIndex(idx);
idx = ui->verticalPixelsPerDegree->findText(QString::number(settings->m_verticalPixelsPerDegree));
ui->verticalPixelsPerDegree->setCurrentIndex(idx);
ui->satTimeOffset->setValue(settings->m_satTimeOffset);
ui->satYaw->setValue(settings->m_satYaw);
for (auto file : settings->m_palettes) {
ui->palettes->addItem(file);
}
on_autoSave_clicked(settings->m_autoSave);
}
APTDemodSettingsDialog::~APTDemodSettingsDialog()
@ -43,8 +58,20 @@ void APTDemodSettingsDialog::accept()
m_settings->m_satelliteTrackerControl = ui->satelliteTrackerControl->isChecked();
m_settings->m_satelliteName = ui->satellite->currentText();
m_settings->m_autoSave = ui->autoSave->isChecked();
m_settings->m_saveCombined = ui->saveCombined->isChecked();
m_settings->m_saveSeparate = ui->saveSeparate->isChecked();
m_settings->m_saveProjection = ui->saveProjection->isChecked();
m_settings->m_autoSavePath = ui->autoSavePath->text();
m_settings->m_autoSaveMinScanLines = ui->minScanlines->value();
m_settings->m_scanlinesPerImageUpdate = ui->scanlinesPerImageUpdate->value();
m_settings->m_palettes.clear();
m_settings->m_horizontalPixelsPerDegree = ui->horizontalPixelsPerDegree->currentText().toInt();
m_settings->m_verticalPixelsPerDegree = ui->verticalPixelsPerDegree->currentText().toInt();
m_settings->m_satTimeOffset = ui->satTimeOffset->value();
m_settings->m_satYaw = ui->satYaw->value();
for (int i = 0; i < ui->palettes->count(); i++) {
m_settings->m_palettes.append(ui->palettes->item(i)->text());
}
QDialog::accept();
}
@ -54,3 +81,42 @@ void APTDemodSettingsDialog::on_autoSavePathBrowse_clicked()
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
ui->autoSavePath->setText(dir);
}
void APTDemodSettingsDialog::on_autoSave_clicked(bool checked)
{
(void) checked;
/* Commented out until theme greys out disabled widgets
ui->saveProjectionLabel->setEnabled(checked);
ui->saveCombined->setEnabled(checked);
ui->saveSeparate->setEnabled(checked);
ui->saveProjection->setEnabled(checked);
ui->autoSavePathLabel->setEnabled(checked);
ui->autoSavePath->setEnabled(checked);
ui->autoSavePathBrowse->setEnabled(checked);
ui->minScanlinesLabel->setEnabled(checked);
ui->minScanlines->setEnabled(checked);
*/
}
void APTDemodSettingsDialog::on_addPalette_clicked()
{
QFileDialog fileDialog(nullptr, "Select palette files", "", "*.png;*.bmp");
fileDialog.setFileMode(QFileDialog::ExistingFiles);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
for (auto fileName : fileNames) {
ui->palettes->addItem(fileName);
}
}
}
void APTDemodSettingsDialog::on_removePalette_clicked()
{
QList<QListWidgetItem *> items = ui->palettes->selectedItems();
for (auto item : items)
{
ui->palettes->removeItemWidget(item);
delete item;
}
}

Wyświetl plik

@ -33,6 +33,9 @@ public:
private slots:
void accept();
void on_autoSavePathBrowse_clicked();
void on_autoSave_clicked(bool checked);
void on_addPalette_clicked();
void on_removePalette_clicked();
private:
Ui::APTDemodSettingsDialog* ui;

Wyświetl plik

@ -6,13 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>212</height>
<width>600</width>
<height>576</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -29,69 +28,31 @@
</sizepolicy>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="5" column="0">
<widget class="QLabel" name="autoSavePathLabel">
<item row="0" column="0">
<widget class="QLabel" name="satelliteTrackerControlLabel">
<property name="text">
<string>Path to save image</string>
<string>Enable Satellite Tracker control</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="autoSavePath">
<property name="toolTip">
<string>Path to save images to</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="autoSavePathBrowse">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/load.png</normaloff>:/load.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="minScanlinesLabel">
<property name="text">
<string>Minimum scanlines</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="minScanlines">
<item row="0" column="1">
<widget class="QCheckBox" name="satelliteTrackerControl">
<property name="toolTip">
<string>Enter the minimum number of scanlines in an image (after cropping) for it to be automatically saved</string>
<string>Check to enable control by Satellite Tracker feature</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>200</number>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QLabel" name="satelliteLabel">
<property name="text">
<string>Satellite</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QComboBox" name="satellite">
<property name="toolTip">
<string>Select which satellite this channel will be used for</string>
@ -121,23 +82,316 @@
</item>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="satelliteTrackerControl">
<property name="toolTip">
<string>Check to enable control by Satellite Tracker feature</string>
</property>
<item row="4" column="0">
<widget class="QLabel" name="autoSaveLabel">
<property name="text">
<string>Enable Satellite Tracker control</string>
<string>Auto save images</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="4" column="1">
<widget class="QCheckBox" name="autoSave">
<property name="toolTip">
<string>Check to automatically save images when acquisition is stopped or LOS</string>
</property>
<property name="text">
<string>Auto save image</string>
<string/>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="saveCombinedLabel">
<property name="text">
<string>Save combined image</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="saveCombined">
<property name="toolTip">
<string>Save a combined image of both channel A and B</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="saveSeparateLabel">
<property name="text">
<string>Save separate images</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QCheckBox" name="saveSeparate">
<property name="toolTip">
<string>Save images from channels A and B to separate files</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="saveProjectionLabel">
<property name="text">
<string>Save projected images</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="saveProjection">
<property name="toolTip">
<string>Saves the equidistant cylindrical projected image</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="autoSavePathLabel">
<property name="text">
<string>Path to save images</string>
</property>
</widget>
</item>
<item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="autoSavePath">
<property name="toolTip">
<string>Path to save images to</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="autoSavePathBrowse">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/load.png</normaloff>:/load.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="minScanlinesLabel">
<property name="text">
<string>Minimum scanlines for auto save</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="minScanlines">
<property name="toolTip">
<string>Enter the minimum number of scanlines in an image (after cropping) for it to be automatically saved</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>200</number>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="scanlinesPerImageUpdateLabel">
<property name="text">
<string>Scanlines per image update</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QSpinBox" name="scanlinesPerImageUpdate">
<property name="toolTip">
<string>How often the image processing functions are applied to the image and how often it is sent to the map</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="palettesLabel">
<property name="text">
<string>Colour palettes</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QListWidget" name="palettes">
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
</widget>
</item>
<item row="18" column="1">
<layout class="QHBoxLayout" name="buttonsHhorizontalLayout">
<item>
<widget class="QPushButton" name="addPalette">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removePalette">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonsHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="13" column="1">
<widget class="QComboBox" name="horizontalPixelsPerDegree">
<property name="toolTip">
<string>Number of pixels per degree longitude in projected image</string>
</property>
<item>
<property name="text">
<string>10</string>
</property>
</item>
<item>
<property name="text">
<string>15</string>
</property>
</item>
<item>
<property name="text">
<string>20</string>
</property>
</item>
</widget>
</item>
<item row="14" column="1">
<widget class="QComboBox" name="verticalPixelsPerDegree">
<property name="toolTip">
<string>Number of pixels per degree latitude in projected image</string>
</property>
<item>
<property name="text">
<string>20</string>
</property>
</item>
<item>
<property name="text">
<string>30</string>
</property>
</item>
<item>
<property name="text">
<string>40</string>
</property>
</item>
<item>
<property name="text">
<string>45</string>
</property>
</item>
<item>
<property name="text">
<string>50</string>
</property>
</item>
<item>
<property name="text">
<string>55</string>
</property>
</item>
<item>
<property name="text">
<string>60</string>
</property>
</item>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="horizontalPixelsPerDegreeLabel">
<property name="text">
<string>Horizontal pixels per degree</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="verticalPixelsPerDegreeLabel">
<property name="text">
<string>Vertical pixels per degree</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="satTimeOffsetLabel">
<property name="text">
<string>Satellite position time offset (s)</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QDoubleSpinBox" name="satTimeOffset">
<property name="toolTip">
<string>Time offset in seconds to add when calculating satellites position.
This may be used to help align images on the map.</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>-100.000000000000000</double>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="satYawLabel">
<property name="text">
<string>Satellite yaw correction (°)</string>
</property>
</widget>
</item>
<item row="16" column="1">
<widget class="QDoubleSpinBox" name="satYaw">
<property name="toolTip">
<string>Add yaw offset to help with aligning images on the map.</string>
</property>
<property name="decimals">
<number>2</number>
</property>
<property name="minimum">
<double>-10.000000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.250000000000000</double>
</property>
</widget>
</item>
@ -160,9 +414,16 @@
<tabstop>satelliteTrackerControl</tabstop>
<tabstop>satellite</tabstop>
<tabstop>autoSave</tabstop>
<tabstop>saveCombined</tabstop>
<tabstop>saveSeparate</tabstop>
<tabstop>saveProjection</tabstop>
<tabstop>autoSavePath</tabstop>
<tabstop>autoSavePathBrowse</tabstop>
<tabstop>minScanlines</tabstop>
<tabstop>scanlinesPerImageUpdate</tabstop>
<tabstop>palettes</tabstop>
<tabstop>addPalette</tabstop>
<tabstop>removePalette</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>

Wyświetl plik

@ -2,14 +2,20 @@
<h2>Introduction</h2>
This plugin can be used to demodulate APT (Automatic Picture Transmission) signals transmitted by NOAA weather satellites. These images are at a 4km/pixel resolution in either the visible, near-IR, mid-IR or thermal-IR bands.
This plugin can be used to demodulate APT (Automatic Picture Transmission) signals transmitted by NOAA POES weather satellites. These images are at a 4km/pixel resolution in either the visible, near-IR, mid-IR or thermal-IR bands.
![APT Demodulator plugin GUI](../../../doc/img/APTDemod_plugin.png)
The received images can be sent to the Map feature to be projected on to the 3D globe (this requires the Satellite Tracker feature to be running and start the decode):
![APT Image on 3D Map](../../../doc/img/APTDemod_plugin_map.png)
* NOAA 15 transmits on 137.620 MHz.
* NOAA 18 transmits on 137.912 MHz.
* NOAA 19 transmits on 137.100 MHz.
The status of the NOAA POES satellites is available at: https://www.ospo.noaa.gov/Operations/POES/status.html The instrument used to generate the APT images is the AVHRR (Advanced Very High Resolution Radiometer).
<h2>Interface</h2>
![APT Demodulator plugin GUI](../../../doc/img/APTDemod_plugin_settings.png)
@ -36,11 +42,21 @@ This specifies the bandwidth of a LPF that is applied to the input signal to lim
Adjusts the expected frequency deviation in 0.1 kHz steps from 10 to 25 kHz. The typical value for APT is 17 kHz.
<h3>6: Start/stop decoding</h3>
<h3>6: Transparency and Opacity Thresholds</h3>
Starts or stops decoding. A maximum of 3000 scanlines can be decoded, after which, the Reset Decoder (7) button needs to be pressed, to start a new image.
For images displayed on the Map, these dials set the greyscale levels below which a pixel will be fully transparent and above which will be fully opaque. In between these values, pixels will be partially transparent. The opacity threshold should be greater or equal to the transparency threshold.
<h3>7: Show settings dialog</h3>
These settings can be used to remove land and sea from the APT image, which are typically at lower greyscale levels than cloud, allowing the 3D map surface to be visible, underneath the cloud.
<h3>7: Delete Images from Map</h3>
When clicked deletes any images added to the map by this demodulator.
<h3>8: Start/stop decoding</h3>
Starts or stops decoding. A maximum of 3000 scanlines can be decoded, after which, the Reset Decoder (10) button needs to be pressed, to start a new image.
<h3>9: Show settings dialog</h3>
When clicked, shows additional APT Demodulator settings.
@ -50,61 +66,77 @@ This includes:
- Whether the APT demodulator can be controlled by the Satellite Tracker feature. When checked, the image decoder will be enabled and reset on AOS and the satellite pass direction will be used to control image rotation. The decoder will be stopped on LOS.
- Which satellites the APT demodulator will respond to AOS and LOS indications from the Satellite Tracker. This can be used to simulataneously decode images from multiple satellites, by having multiple instances of the APT Demodulator and setting a unique satellite name for each demodulator.
- Whether to automatically save the image on LOS.
- Whether to automatically save images on LOS.
- Whether a combined image including telemetry should be saved.
- Whether separate images of channel A and B, without telemetry, should be saved.
- Whether equidistant cylindrical (plate carrée) project images used for the map, should be saved.
- Path to save automatically saved images in.
- The minimum number of scanlines required to be in an image, after noise cropping, for it to be automatically saved.
- After how many scanlines image processing is applied and updates sent to the map. Lower values require more CPU power.
- The number of pixels per degree longitude and latitude (i.e. resolution) for the equidistant cylindrical projected images.
- Satellite time and yaw offsets that can be used to more precisely align the image projected on the map.
- A list of colour palettes. Colour palettes are 256x256 image files that are used to enhance various features within an image. Some examples are here: https://github.com/Xerbo/aptdec/tree/master/palettes
<h3>8: Reset decoder</h3>
<h3>10: Reset decoder</h3>
Clears the current image and restarts the decoder. The decoder must be reset between passes of different satellites.
<h3>9: Save image to disk</h3>
<h3>11: Save image to disk</h3>
Saves the current image to disk. Images can be saved in PNG, JPEG, BMP, PPM, XBM or XPM formats.
<h3>10: Zoom in</h3>
<h3>12: Zoom in</h3>
Zooms in to the image. You can also zoom in with the scroll wheel.
<h3>11: Zoom out</h3>
<h3>13: Zoom out</h3>
Zooms out from the image. You can also zoom out with the scroll wheel.
<h3>12: Zoom image to fit</h3>
<h3>14: Zoom image to fit</h3>
Zooms so that the image fits in to the available space.
<h3>13: Channel selection</h3>
<h3>15: Image selection</h3>
Selects whether:
- both channels are displayed
- only channel A is displayed
- only channel B is displayed
- a temperature map is displayed
- colour palettized images are displayed
<h3>14: Crop noise</h3>
Temperature maps require enough scanlines to have been received in order to determine calibration data. Until then, the image will appear blank.
The temperature range of the image is from -100C to 60C. Moving the cursor over image will display the temperature under the cursor at the bottom of the legend.
![Temperature map] (../../../doc/img/APTDemod_plugin_temperature.png)
The colour palettes that are available are set in the APT Demodulator settings dialog (9).
<h3>16: Crop noise</h3>
When checked, noise is cropped from the top and bottom of the image. This is noise that is typically the result of the satellite being at a low elevation.
<h3>15: Apply denoise filter</h3>
<h3>17: Apply denoise filter</h3>
When checked, a denoise filter is applied to the received image.
<h3>16: Apply linear equalisation</h3>
<h3>18: Apply linear equalisation</h3>
When checked, linear equalisation is performed, which can enhance the contrast. The equalisation is performed separately on each channel.
<h3>17: Apply histogram equalisation</h3>
<h3>19: Apply histogram equalisation</h3>
When checked, histogram equalisation is performed, which can enhance the contrast. The equalisation is performed separately on each channel.
<h3>18: Overlay precipitation</h3>
<h3>20: Overlay precipitation</h3>
When checked, precipitation is detected from the IR channel and overlayed on both channels using a colour palette.
This option will not work if linear or histogram equalisation has been applied.
This option will not work if linear (18) or histogram equalisation (19) has been applied.
<h3>19: Pass direction</h3>
<h3>21: Pass direction</h3>
The pass direction check button should be set to match the direction of the satellite pass.
i.e. select down arrow for satellite passing from the North to the South and the up arrow for the satellite passing from the South to the North.

Wyświetl plik

@ -4,7 +4,7 @@ if (Qt5SerialPort_FOUND)
add_subdirectory(gs232controller)
endif()
if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND AND Qt5Location_FOUND)
if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND AND Qt5Location_FOUND AND Qt5WebEngine_FOUND)
add_subdirectory(map)
endif()
if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND)

Wyświetl plik

@ -53,3 +53,8 @@ target_link_libraries(${TARGET_NAME}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Wyświetl plik

@ -17,10 +17,12 @@
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QMessageBox>
#include <QLineEdit>
#include <QDesktopServices>
#include <QAction>
#include <QClipboard>
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
@ -34,6 +36,55 @@
#include "SWGMapItem.h"
// Models to use for ships when type is unknown
// Use as many as possibly, so it doesn't look too samey, but don't use
// the massive ships
QStringList AISGUI::m_shipModels = {
"ship_27m.glbe", "ship_65m.glbe",
"tug_20m.glbe", "tug_30m_1.glbe", "tug_30m_2.glbe", "tug_30m_3.glbe",
"cargo_75m.glbe", "tanker_50m.glbe", "dredger_53m.glbe",
"trawler_22m.glbe",
"speedboat_8m.glbe", "yacht_10m.glbe", "yacht_20m.glbe", "yacht_42m.glbe"
};
QStringList AISGUI::m_sailboatModels = {
"sailboat_8m.glbe", "sailboat_17m.glbe"
};
QHash<QString, float> AISGUI::m_labelOffset = {
{"helicopter.glb", 4.0f},
{"antenna.glb", 4.5f},
{"buoy.glb", 1.5f},
{"ship_27m.glbe", 13.0f},
{"dredger_53m.glbe", 19.0f},
{"ship_65m.glbe", 26.0f},
{"tug_20m.glbe", 10.0f},
{"tug_30m_1.glbe", 17.0f},
{"tug_30m_2.glbe", 17.0f},
{"tug_30m_3.glbe", 17.0f},
{"coastguard.glbe", 4.0},
{"cargo_75m.glbe", 22.0f},
{"cargo_190m.glbe", 42.0f},
{"cargo_230m.glbe", 42.0f},
{"tanker_50m.glbe", 12.0f},
{"tanker_180m.glbe", 35.0f},
{"tanker_245m_1.glbe", 30.0f},
{"tanker_380m_1.glbe", 42.0f},
{"passenger_100m.glbe", 34.0f},
{"dredger_53m.glbe", 19.0f},
{"trawler_22m.glbe", 15.0f},
{"sailboat_8m.glbe", 11.0f},
{"sailboat_17m.glbe", 24.0f},
{"speedboat_8m.glbe", 3.0f},
{"yacht_10m.glbe", 3.0f},
{"yacht_20m.glbe", 7.5f},
{"yacht_42m.glbe", 10.0f},
};
QHash<QString, float> AISGUI::m_modelOffset = {
{"helicopter.glb", 4.0f},
};
AISGUI* AISGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
AISGUI* gui = new AISGUI(pluginAPI, featureUISet, feature);
@ -92,7 +143,7 @@ bool AISGUI::handleMessage(const Message& message)
// Decode the message
AISMessage *ais = AISMessage::decode(report.getPacket());
// Update table
updateVessels(ais);
updateVessels(ais, report.getDateTime());
}
return false;
@ -140,8 +191,9 @@ AISGUI::AISGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(1000);
// Timer to remove vessels we haven't heard from in a while
connect(&m_timer, SIGNAL(timeout()), this, SLOT(removeOldVessels()));
m_timer.start(60*1000);
// Resize the table using dummy data
resizeTable();
@ -161,6 +213,9 @@ AISGUI::AISGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
// Get signals when columns change
connect(ui->vessels->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(vessels_sectionMoved(int, int, int)));
connect(ui->vessels->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(vessels_sectionResized(int, int, int)));
// Context menu
ui->vessels->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->vessels, SIGNAL(customContextMenuRequested(QPoint)), SLOT(vessels_customContextMenuRequested(QPoint)));
m_settings.setRollupState(&m_rollupState);
@ -170,6 +225,7 @@ AISGUI::AISGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
AISGUI::~AISGUI()
{
qDeleteAll(m_vessels);
delete ui;
}
@ -243,8 +299,32 @@ void AISGUI::onMenuDialogCalled(const QPoint &p)
resetContextMenuType();
}
void AISGUI::updateStatus()
void AISGUI::removeOldVessels()
{
// Remove if we haven't received a message in 10 minutes
QDateTime currentDateTime = QDateTime::currentDateTime();
for (int row = ui->vessels->rowCount() - 1; row >= 0; row--)
{
QDateTime lastDateTime = ui->vessels->item(row, VESSEL_COL_LAST_UPDATE)->data(Qt::DisplayRole).toDateTime();
if (lastDateTime.isValid())
{
qint64 diff = lastDateTime.secsTo(currentDateTime);
if (diff > 10*60)
{
QString mmsi = ui->vessels->item(row, VESSEL_COL_MMSI)->text();
// Remove from map
sendToMap(mmsi, "",
"", "",
"", 0.0f, 0.0f,
0.0f, 0.0f, QDateTime(),
0.0f);
// Remove from table
ui->vessels->removeRow(row);
// Remove from hash
m_vessels.remove(mmsi);
}
}
}
}
void AISGUI::applySettings(bool force)
@ -273,7 +353,11 @@ void AISGUI::resizeTable()
ui->vessels->setItem(row, VESSEL_COL_NAME, new QTableWidgetItem("12345678901234567890"));
ui->vessels->setItem(row, VESSEL_COL_CALLSIGN, new QTableWidgetItem("1234567"));
ui->vessels->setItem(row, VESSEL_COL_SHIP_TYPE, new QTableWidgetItem("Passenger"));
ui->vessels->setItem(row, VESSEL_COL_LENGTH, new QTableWidgetItem("400"));
ui->vessels->setItem(row, VESSEL_COL_DESTINATION, new QTableWidgetItem("12345678901234567890"));
ui->vessels->setItem(row, VESSEL_COL_POSITION_UPDATE, new QTableWidgetItem("12/12/2022 12:00"));
ui->vessels->setItem(row, VESSEL_COL_LAST_UPDATE, new QTableWidgetItem("12/12/2022 12:00"));
ui->vessels->setItem(row, VESSEL_COL_MESSAGES, new QTableWidgetItem("1000"));
ui->vessels->resizeColumnsToContents();
ui->vessels->removeRow(row);
}
@ -324,7 +408,59 @@ QAction *AISGUI::createCheckableItem(QString &text, int idx, bool checked, const
return action;
}
void AISGUI::updateVessels(AISMessage *ais)
// Send to Map feature
void AISGUI::sendToMap(const QString &name, const QString &label,
const QString &image, const QString &text,
const QString &model, float modelOffset, float labelOffset,
float latitude, float longitude, QDateTime positionDateTime,
float heading
)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_ais, "mapitems");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setLatitude(latitude);
swgMapItem->setLongitude(longitude);
swgMapItem->setAltitude(0);
swgMapItem->setAltitudeReference(1); // CLAMP_TO_GROUND
if (positionDateTime.isValid()) {
swgMapItem->setPositionDateTime(new QString(positionDateTime.toString(Qt::ISODateWithMs)));
}
swgMapItem->setImageRotation(heading);
swgMapItem->setText(new QString(text));
if (image.isEmpty()) {
swgMapItem->setImage(new QString(""));
} else {
swgMapItem->setImage(new QString(QString("qrc:///ais/map/%1").arg(image)));
}
swgMapItem->setModel(new QString(model));
swgMapItem->setModelAltitudeOffset(modelOffset);
swgMapItem->setLabel(new QString(label));
swgMapItem->setLabelAltitudeOffset(labelOffset);
swgMapItem->setFixedPosition(false);
swgMapItem->setOrientation(1);
swgMapItem->setHeading(heading);
swgMapItem->setPitch(0.0);
swgMapItem->setRoll(0.0);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_ais, swgMapItem);
(*it)->push(msg);
}
}
}
// Update table with received message
void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
{
QTableWidgetItem *mmsiItem;
QTableWidgetItem *typeItem;
@ -338,7 +474,15 @@ void AISGUI::updateVessels(AISMessage *ais)
QTableWidgetItem *nameItem;
QTableWidgetItem *callsignItem;
QTableWidgetItem *shipTypeItem;
QTableWidgetItem *lengthItem;
QTableWidgetItem *destinationItem;
QTableWidgetItem *positionUpdateItem;
QTableWidgetItem *lastUpdateItem;
QTableWidgetItem *messagesItem;
QString previousType;
QString previousShipType;
Vessel *vessel;
// See if vessel is already in table
QString messageMMSI = QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0'));
@ -361,7 +505,12 @@ void AISGUI::updateVessels(AISMessage *ais)
nameItem = ui->vessels->item(row, VESSEL_COL_NAME);
callsignItem = ui->vessels->item(row, VESSEL_COL_CALLSIGN);
shipTypeItem = ui->vessels->item(row, VESSEL_COL_SHIP_TYPE);
lengthItem = ui->vessels->item(row, VESSEL_COL_LENGTH);
destinationItem = ui->vessels->item(row, VESSEL_COL_DESTINATION);
positionUpdateItem = ui->vessels->item(row, VESSEL_COL_POSITION_UPDATE);
lastUpdateItem = ui->vessels->item(row, VESSEL_COL_LAST_UPDATE);
messagesItem = ui->vessels->item(row, VESSEL_COL_MESSAGES);
vessel = m_vessels.value(messageMMSI);
found = true;
break;
}
@ -385,7 +534,11 @@ void AISGUI::updateVessels(AISMessage *ais)
nameItem = new QTableWidgetItem();
callsignItem = new QTableWidgetItem();
shipTypeItem = new QTableWidgetItem();
lengthItem = new QTableWidgetItem();
destinationItem = new QTableWidgetItem();
positionUpdateItem = new QTableWidgetItem();
lastUpdateItem = new QTableWidgetItem();
messagesItem = new QTableWidgetItem();
ui->vessels->setItem(row, VESSEL_COL_MMSI, mmsiItem);
ui->vessels->setItem(row, VESSEL_COL_TYPE, typeItem);
ui->vessels->setItem(row, VESSEL_COL_LATITUDE, latitudeItem);
@ -398,10 +551,24 @@ void AISGUI::updateVessels(AISMessage *ais)
ui->vessels->setItem(row, VESSEL_COL_NAME, nameItem);
ui->vessels->setItem(row, VESSEL_COL_CALLSIGN, callsignItem);
ui->vessels->setItem(row, VESSEL_COL_SHIP_TYPE, shipTypeItem);
ui->vessels->setItem(row, VESSEL_COL_LENGTH, lengthItem);
ui->vessels->setItem(row, VESSEL_COL_DESTINATION, destinationItem);
ui->vessels->setItem(row, VESSEL_COL_POSITION_UPDATE, positionUpdateItem);
ui->vessels->setItem(row, VESSEL_COL_LAST_UPDATE, lastUpdateItem);
ui->vessels->setItem(row, VESSEL_COL_MESSAGES, messagesItem);
messagesItem->setData(Qt::DisplayRole, 0);
vessel = new Vessel();
m_vessels.insert(messageMMSI, vessel);
}
previousType = typeItem->text();
previousShipType = shipTypeItem->text();
mmsiItem->setText(QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0')));
lastUpdateItem->setData(Qt::DisplayRole, dateTime);
messagesItem->setData(Qt::DisplayRole, messagesItem->data(Qt::DisplayRole).toInt() + 1);
if ((ais->m_id <= 3) || (ais->m_id == 5) || (ais->m_id == 18) || (ais->m_id == 19)) {
typeItem->setText("Vessel");
} else if (ais->m_id == 4) {
@ -429,6 +596,7 @@ void AISGUI::updateVessels(AISMessage *ais)
nameItem->setText(vd->m_name);
callsignItem->setText(vd->m_callsign);
shipTypeItem->setText(AISMessage::typeToString(vd->m_type));
lengthItem->setData(Qt::DisplayRole, vd->m_a + vd->m_b);
destinationItem->setText(vd->m_destination);
}
}
@ -438,6 +606,7 @@ void AISGUI::updateVessels(AISMessage *ais)
{
latitudeItem->setData(Qt::DisplayRole, ais->getLatitude());
longitudeItem->setData(Qt::DisplayRole, ais->getLongitude());
positionUpdateItem->setData(Qt::DisplayRole, dateTime);
}
if (ais->hasCourse()) {
courseItem->setData(Qt::DisplayRole, ais->getCourse());
@ -488,88 +657,211 @@ void AISGUI::updateVessels(AISMessage *ais)
if (!latitudeV.isNull() && !longitudeV.isNull() && !type.isEmpty())
{
// Send to Map feature
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_ais, "mapitems");
if (mapMessageQueues)
// Image and model to use on map
QString shipType = shipTypeItem->text();
int length = lengthItem->data(Qt::DisplayRole).toInt();
QString status = statusItem->text();
// Only update model if change in type - so we don't keeping picking new
// random models
if ((previousType != type) || (previousShipType != shipType)) {
getImageAndModel(type, shipType, length, status, vessel);
}
float labelOffset = m_labelOffset.value(vessel->m_model);
float modelOffset = 0.0f;
if (m_modelOffset.contains(vessel->m_model)) {
modelOffset = m_modelOffset.value(vessel->m_model);
}
// Text to display in info box
QStringList text;
QVariant courseV = courseItem->data(Qt::DisplayRole);
QVariant speedV = speedItem->data(Qt::DisplayRole);
QVariant headingV = headingItem->data(Qt::DisplayRole);
QString name = nameItem->text();
QString callsign = callsignItem->text();
QString destination = destinationItem->text();
float heading = 0.0f;
if (!name.isEmpty()) {
text.append(QString("Name: %1").arg(name));
}
if (!callsign.isEmpty()) {
text.append(QString("Callsign: %1").arg(callsign));
}
if (!destination.isEmpty()) {
text.append(QString("Destination: %1").arg(destination));
}
if (!courseV.isNull())
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
float course = courseV.toFloat();
text.append(QString("Course: %1%2").arg(course).arg(QChar(0xb0)));
heading = course;
}
if (!speedV.isNull()) {
text.append(QString("Speed: %1 knts").arg(speedV.toFloat()));
}
if (!headingV.isNull())
{
heading = headingV.toFloat();
text.append(QString("Heading: %1%2").arg(heading).arg(QChar(0xb0)));
}
if (!shipType.isEmpty()) {
text.append(QString("Ship type: %1").arg(shipType));
}
if (!status.isEmpty()) {
text.append(QString("Status: %1").arg(status));
}
for (; it != mapMessageQueues->end(); ++it)
// Send to map feature
sendToMap(mmsiItem->text(), callsign,
vessel->m_image, text.join("<br>"),
vessel->m_model, modelOffset, labelOffset,
latitudeV.toFloat(), longitudeV.toFloat(), positionUpdateItem->data(Qt::DisplayRole).toDateTime(),
heading);
}
}
void AISGUI::getImageAndModel(const QString &type, const QString &shipType, int length, const QString &status, Vessel *vessel)
{
if (type == "Aircraft")
{
// I presume search and rescue aircraft are more likely to be helicopters
vessel->m_image = "helicopter.png";
vessel->m_model = "helicopter.glb";
}
else if (type == "Base station")
{
vessel->m_image = "anchor.png";
vessel->m_model = "antenna.glb";
}
else if (type == "Aid-to-nav")
{
vessel->m_image = "bouy.png";
vessel->m_model = "buoy.glb";
}
else
{
vessel->m_image = "ship.png";
if (status == "Under way sailing") {
vessel->m_model = m_sailboatModels[m_random.bounded(m_sailboatModels.size())];
} else {
vessel->m_model = m_shipModels[m_random.bounded(m_shipModels.size())];
}
if (!shipType.isEmpty())
{
if (shipType == "Ship")
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(QString("%1").arg(mmsiItem->text())));
swgMapItem->setLatitude(latitudeV.toFloat());
swgMapItem->setLongitude(longitudeV.toFloat());
swgMapItem->setAltitude(0);
QString image;
if (type == "Aircraft") {
// I presume search and rescue aircraft are more likely to be helicopters
image = "helicopter.png";
} else if (type == "Base station") {
image = "anchor.png";
} else if (type == "Aid-to-nav") {
image = "bouy.png";
} else {
image = "ship.png";
QString shipType = shipTypeItem->text();
if (!shipType.isEmpty())
{
if (shipType == "Tug") {
image = "tug.png";
} else if (shipType == "Cargo") {
image = "cargo.png";
} else if (shipType == "Tanker") {
image = "tanker.png";
}
}
}
swgMapItem->setImage(new QString(QString("qrc:///ais/map/%1").arg(image)));
swgMapItem->setImageMinZoom(11);
QStringList text;
QVariant courseV = courseItem->data(Qt::DisplayRole);
QVariant speedV = speedItem->data(Qt::DisplayRole);
QVariant headingV = headingItem->data(Qt::DisplayRole);
QString name = nameItem->text();
QString callsign = callsignItem->text();
QString destination = destinationItem->text();
QString shipType = shipTypeItem->text();
QString status = statusItem->text();
if (!name.isEmpty()) {
text.append(QString("Name: %1").arg(name));
}
if (!callsign.isEmpty()) {
text.append(QString("Callsign: %1").arg(callsign));
}
if (!destination.isEmpty()) {
text.append(QString("Destination: %1").arg(destination));
}
if (!courseV.isNull())
if (length < 40)
{
float course = courseV.toFloat();
text.append(QString("Course: %1%2").arg(course).arg(QChar(0xb0)));
swgMapItem->setImageRotation(course);
vessel->m_model = "ship_27m.glbe";
}
if (!speedV.isNull()) {
text.append(QString("Speed: %1 knts").arg(speedV.toFloat()));
}
if (!headingV.isNull())
else if (length < 60)
{
float heading = headingV.toFloat();
text.append(QString("Heading: %1%2").arg(heading).arg(QChar(0xb0)));
swgMapItem->setImageRotation(heading); // heading takes precedence over course
vessel->m_model = "dredger_53m.glbe";
}
if (!shipType.isEmpty()) {
text.append(QString("Ship type: %1").arg(shipType));
else
{
vessel->m_model = "ship_65m.glbe";
}
if (!status.isEmpty()) {
text.append(QString("Status: %1").arg(status));
}
else if ((shipType == "Tug") || (shipType == "Port tender") || (shipType == "Pilot vessel"))
{
vessel->m_image = "tug.png";
if (length < 25)
{
vessel->m_model = "tug_20m.glbe";
}
else
{
int rand = m_random.bounded(1, 3);
vessel->m_model = QString("tug_30m_%1.glbe").arg(rand);
}
}
else if ((shipType == "Law enforcement vessel") || (shipType == "Search and rescue vessel"))
{
vessel->m_model = "coastguard.glbe";
}
else if (shipType == "Cargo")
{
vessel->m_image = "cargo.png";
if (length < 120)
{
vessel->m_model = "cargo_75m.glbe";
}
else if (length < 200)
{
vessel->m_model = "cargo_190m.glbe";
}
else
{
vessel->m_model = "cargo_230m.glbe";
}
}
else if (shipType == "Tanker")
{
vessel->m_image = "tanker.png";
if (length < 120)
{
vessel->m_model = "tanker_50m.glbe";
}
else if (length < 210)
{
vessel->m_model = "tanker_180m.glbe";
}
else if (length < 300)
{
int rand = m_random.bounded(1, 4);
vessel->m_model = QString("tanker_245m_%1.glbe").arg(rand);
}
else
{
int rand = m_random.bounded(1, 3);
vessel->m_model = QString("tanker_380m_%1.glbe").arg(rand);
}
}
else if (shipType == "Passenger")
{
vessel->m_model = "passenger_100m.glbe";
}
else if (shipType == "Vessel - Dredging or underwater operations")
{
vessel->m_model = "dredger_53m.glbe";
}
else if (shipType == "Vessel - Fishing")
{
vessel->m_model = "trawler_22m.glbe";
}
else if (shipType == "Vessel - Sailing")
{
if (length < 13)
{
vessel->m_model = "sailboat_8m.glbe";
}
else
{
vessel->m_model = "sailboat_17m.glbe";
}
}
else if (shipType.contains("Pleasure craft"))
{
if (length < 9)
{
vessel->m_model = "speedboat_8m.glbe";
}
else if (length < 18)
{
vessel->m_model = "yacht_10m.glbe";
}
else if (length < 32)
{
vessel->m_model = "yacht_20m.glbe";
}
else
{
vessel->m_model = "yacht_42m.glbe";
}
swgMapItem->setText(new QString(text.join("\n")));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_ais, swgMapItem);
(*it)->push(msg);
}
}
}
@ -584,7 +876,7 @@ void AISGUI::on_vessels_cellDoubleClicked(int row, int column)
// Search for MMSI on www.vesselfinder.com
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(mmsi)));
}
else if ((column == VESSEL_COL_LATITUDE) || (column == VESSEL_COL_LONGITUDE))
else if ((column == VESSEL_COL_LATITUDE) || (column == VESSEL_COL_LONGITUDE) || (column == VESSEL_COL_SHIP_TYPE))
{
// Get MMSI of vessel in row double clicked
QString mmsi = ui->vessels->item(row, VESSEL_COL_MMSI)->text();
@ -619,3 +911,90 @@ void AISGUI::on_vessels_cellDoubleClicked(int row, int column)
}
}
}
// Table cells context menu
void AISGUI::vessels_customContextMenuRequested(QPoint pos)
{
QTableWidgetItem *item = ui->vessels->itemAt(pos);
if (item)
{
int row = item->row();
QString mmsi = ui->vessels->item(row, VESSEL_COL_MMSI)->text();
QString imo = ui->vessels->item(row, VESSEL_COL_IMO)->text();
QString name = ui->vessels->item(row, VESSEL_COL_NAME)->text();
QVariant latitudeV = ui->vessels->item(row, VESSEL_COL_LATITUDE)->data(Qt::DisplayRole);
QVariant longitudeV = ui->vessels->item(row, VESSEL_COL_LONGITUDE)->data(Qt::DisplayRole);
QString destination = ui->vessels->item(row, VESSEL_COL_DESTINATION)->text();
QMenu* tableContextMenu = new QMenu(ui->vessels);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
// Copy current cell
QAction* copyAction = new QAction("Copy", tableContextMenu);
const QString text = item->text();
connect(copyAction, &QAction::triggered, this, [text]()->void {
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(text);
});
tableContextMenu->addAction(copyAction);
tableContextMenu->addSeparator();
// View vessel on various websites
QAction* mmsiAISHubAction = new QAction(QString("View MMSI %1 on aishub.net...").arg(mmsi), tableContextMenu);
connect(mmsiAISHubAction, &QAction::triggered, this, [mmsi]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.aishub.net/vessels?Ship%5Bmmsi%5D=%1&mmsi=%1").arg(mmsi)));
});
tableContextMenu->addAction(mmsiAISHubAction);
QAction* mmsiAction = new QAction(QString("View MMSI %1 on vesselfinder.com...").arg(mmsi), tableContextMenu);
connect(mmsiAction, &QAction::triggered, this, [mmsi]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.net/vessels?name=%1").arg(mmsi)));
});
tableContextMenu->addAction(mmsiAction);
if (!imo.isEmpty())
{
QAction* imoAction = new QAction(QString("View IMO %1 on vesselfinder.net...").arg(imo), tableContextMenu);
connect(imoAction, &QAction::triggered, this, [imo]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.net/vessels?name=%1").arg(imo)));
});
tableContextMenu->addAction(imoAction);
}
if (!name.isEmpty())
{
QAction* nameAction = new QAction(QString("View %1 on vesselfinder.net...").arg(name), tableContextMenu);
connect(nameAction, &QAction::triggered, this, [name]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.net/vessels?name=%1").arg(name)));
});
tableContextMenu->addAction(nameAction);
}
// Find on Map
if (!latitudeV.isNull())
{
tableContextMenu->addSeparator();
QAction* findMapFeatureAction = new QAction(QString("Find MMSI %1 on map").arg(mmsi), tableContextMenu);
connect(findMapFeatureAction, &QAction::triggered, this, [mmsi]()->void {
FeatureWebAPIUtils::mapFind(mmsi);
});
tableContextMenu->addAction(findMapFeatureAction);
}
if (!destination.isEmpty())
{
tableContextMenu->addSeparator();
QAction* findDestinationFeatureAction = new QAction(QString("Find %1 on map").arg(destination), tableContextMenu);
connect(findDestinationFeatureAction, &QAction::triggered, this, [destination]()->void {
FeatureWebAPIUtils::mapFind(destination);
});
tableContextMenu->addAction(findDestinationFeatureAction);
}
tableContextMenu->popup(ui->vessels->viewport()->mapToGlobal(pos));
}
}

Wyświetl plik

@ -21,6 +21,9 @@
#include <QTimer>
#include <QMenu>
#include <QDateTime>
#include <QHash>
#include <QRandomGenerator>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
@ -40,6 +43,13 @@ namespace Ui {
class AISGUI : public FeatureGUI {
Q_OBJECT
// Holds information not in the table
struct Vessel {
QString m_image;
QString m_model;
};
public:
static AISGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
@ -59,9 +69,16 @@ private:
AIS* m_ais;
MessageQueue m_inputMessageQueue;
QTimer m_statusTimer;
QTimer m_timer;
int m_lastFeatureState;
QRandomGenerator m_random;
QHash<QString, Vessel *> m_vessels; // Hash of mmsi to vessels
static QStringList m_shipModels;
static QStringList m_sailboatModels;
static QHash<QString, float> m_labelOffset;
static QHash<QString, float> m_modelOffset;
QMenu *vesselsMenu; // Column select context menu
explicit AISGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
@ -75,7 +92,14 @@ private:
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void updateVessels(AISMessage *ais);
void sendToMap(const QString &name, const QString &label,
const QString &image, const QString &text,
const QString &model, float modelOffset, float labelOffset,
float latitude, float longitude, QDateTime positionDateTime,
float heading
);
void updateVessels(AISMessage *ais, QDateTime dateTime);
void getImageAndModel(const QString &type, const QString &shipType, int length, const QString &status, Vessel *vessel);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
@ -92,19 +116,24 @@ private:
VESSEL_COL_NAME,
VESSEL_COL_CALLSIGN,
VESSEL_COL_SHIP_TYPE,
VESSEL_COL_DESTINATION
VESSEL_COL_LENGTH,
VESSEL_COL_DESTINATION,
VESSEL_COL_POSITION_UPDATE,
VESSEL_COL_LAST_UPDATE,
VESSEL_COL_MESSAGES
};
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void updateStatus();
void on_vessels_cellDoubleClicked(int row, int column);
void vessels_customContextMenuRequested(QPoint pos);
void vessels_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void vessels_sectionResized(int logicalIndex, int oldSize, int newSize);
void vesselsColumnSelectMenu(QPoint pos);
void vesselsColumnSelectMenuChecked(bool checked = false);
void removeOldVessels();
};
#endif // INCLUDE_FEATURE_AISGUI_H_

Wyświetl plik

@ -30,7 +30,6 @@
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
@ -166,11 +165,40 @@
<string>Ship Type</string>
</property>
</column>
<column>
<property name="text">
<string>Length</string>
</property>
</column>
<column>
<property name="text">
<string>Destination</string>
</property>
</column>
<column>
<property name="text">
<string>Position Updated</string>
</property>
<property name="toolTip">
<string>Time last position was received</string>
</property>
</column>
<column>
<property name="text">
<string>Updated</string>
</property>
<property name="toolTip">
<string>Time last message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Messages</string>
</property>
<property name="toolTip">
<string>Number of messages received</string>
</property>
</column>
</widget>
</item>
</layout>

Wyświetl plik

@ -27,7 +27,7 @@
class Serializable;
// Number of columns in the tables
#define AIS_VESSEL_COLUMNS 13
#define AIS_VESSEL_COLUMNS 16
struct AISSettings
{

Wyświetl plik

@ -2,9 +2,11 @@
<h2>Introduction</h2>
The AIS feature displays a table containing the most recent information about vessels, base-stations and aids-to-navigation, based on messages received via AIS Demodulators.
The AIS feature displays a table containing the most recent information about vessels, base-stations and aids-to-navigation,
based on messages received via [AIS Demodulators](../../channelrx/demodais/readme.md).
Typically the AIS feature would be used with two AIS Demodulators: one at 161.975MHz and 162.025MHz.
The AIS feature can draw corresponding objects on the Map.
The AIS feature can draw corresponding objects on the Map in 2D and 3D.
<h2>Interface</h2>
@ -26,16 +28,26 @@ The vessels table displays the current status for each vessel, base station or a
* Name - Name of the vessel. Double clicking on this column will search for the name on https://www.vesselfinder.com/
* Callsign - Callsign of the vessel.
* Ship Type - Type of ship (E.g. Passenger ship, Cargo ship, Tanker) and activity (Fishing, Towing, Sailing).
* Length - The length of the vessel.
* Destination - Destination the vessel is travelling to. Double clicking on this column will search for this location on the map on this object.
* Position Updated - Gives the date and time the last position was received.
* Updated - Gives the date and time the last message was received.
* Messages - Displays the number of messages received.
Right clicking on the table header allows you to select which columns to show. The columns can be reorderd by left clicking and dragging the column header.
Right clicking on a table cell allows you to copy the cell contents, view the vessel on a variety of websites or find the vessel on the map.
Vessels are removed from the table if a message is not received for 10 minutes.
<h3>Map</h3>
The AIS feature can plot ships, base stations and aids-to-navigation on the Map. To use, simply open a Map feature and the AIS plugin will display objects based upon the messages it receives from that point.
The AIS feature can plot ships, base stations and aids-to-navigation on the [Map](../../feature/map/readme.md). To use, simply open a Map feature and the AIS plugin will display objects based upon the messages it receives from that point.
Selecting an AIS 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.
![AIS map](../../../doc/img/AIS_plugin_map.png)
![AIS 2D map](../../../doc/img/AIS_plugin_map.png)
![AIS 3D map](../../../doc/img/AIS_plugin_map_3d.png)
<h2>Attribution</h2>

Wyświetl plik

@ -355,6 +355,7 @@ bool APRSGUI::handleMessage(const Message& message)
swgMapItem->setLatitude(aprs->m_latitude);
swgMapItem->setLongitude(aprs->m_longitude);
swgMapItem->setAltitude(aprs->m_hasAltitude ? Units::feetToMetres(aprs->m_altitudeFt) : 0);
swgMapItem->setAltitudeReference(1); // CLAMP_TO_GROUND
if (aprs->m_objectKilled)
{
swgMapItem->setImage(new QString(""));
@ -369,7 +370,6 @@ bool APRSGUI::handleMessage(const Message& message)
m_settings.m_temperatureUnits == APRSSettings::CELSIUS,
m_settings.m_rainfallUnits == APRSSettings::MILLIMETRE)));
}
swgMapItem->setImageMinZoom(11);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_aprs, swgMapItem);
(*it)->push(msg);

Wyświetl plik

@ -7,6 +7,7 @@ set(map_SOURCES
mapwebapiadapter.cpp
osmtemplateserver.cpp
ibpbeacon.cpp
webserver.cpp
)
set(map_HEADERS
@ -18,10 +19,12 @@ set(map_HEADERS
osmtemplateserver.h
beacon.h
ibpbeacon.h
webserver.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Qt5Gui_PRIVATE_INCLUDE_DIRS}
)
if(NOT SERVER_MODE)
@ -41,8 +44,14 @@ if(NOT SERVER_MODE)
mapibpbeacondialog.ui
mapradiotimedialog.cpp
mapradiotimedialog.ui
mapcolordialog.cpp
mapmodel.cpp
mapwebsocketserver.cpp
cesiuminterface.cpp
czml.cpp
map.qrc
icons.qrc
cesium.qrc
)
set(map_HEADERS
${map_HEADERS}
@ -53,10 +62,15 @@ if(NOT SERVER_MODE)
mapbeacondialog.h
mapibpbeacon.h
mapradiotimedialog.h
mapcolordialog.h
mapmodel.h
mapwebsocketserver.h
cesiuminterface.h
czml.h
)
set(TARGET_NAME map)
set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning Qt5::Location)
set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning Qt5::Location Qt5::WebEngine Qt5::WebEngineCore Qt5::WebEngineWidgets)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
@ -84,3 +98,8 @@ if(WIN32)
include(DeployQt)
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/map)
endif()
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Some files were not shown because too many files have changed in this diff Show More