mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-25 17:28:50 -05:00
Map updates.
Add support for taken and predicted ground tracks. Support multiple beacons with same callsign at different locations. Use separate QML for Qt 5.14, as 5.12 doesn't support autoFadeIn, needed to view satellites at min zoom.
This commit is contained in:
parent
cd504da84e
commit
d381568437
@ -34,6 +34,7 @@ if(NOT SERVER_MODE)
|
||||
mapbeacondialog.cpp
|
||||
mapbeacondialog.ui
|
||||
map.qrc
|
||||
icons.qrc
|
||||
)
|
||||
set(map_HEADERS
|
||||
${map_HEADERS}
|
||||
|
@ -74,6 +74,16 @@ struct Beacon {
|
||||
return QString("%1 kHz").arg(m_frequency/1000.0, 0, ',', 3);
|
||||
}
|
||||
|
||||
QString getFrequencyShortText()
|
||||
{
|
||||
if (m_frequency > 1000000000)
|
||||
return QString("%1G").arg(m_frequency/1000000000.0, 0, ',', 1);
|
||||
else if (m_frequency > 1000000)
|
||||
return QString("%1M").arg(std::floor(m_frequency/1000000.0), 0, ',', 0);
|
||||
else
|
||||
return QString("%1k").arg(std::floor(m_frequency/1000.0), 0, ',', 0);
|
||||
}
|
||||
|
||||
// Uses ; rather than ,
|
||||
static QList<Beacon *> *readIARUCSV(const QString &filename)
|
||||
{
|
||||
|
5
plugins/feature/map/icons.qrc
Normal file
5
plugins/feature/map/icons.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/map/">
|
||||
<file>icons/groundtracks.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
plugins/feature/map/icons/groundtracks.png
Normal file
BIN
plugins/feature/map/icons/groundtracks.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
@ -1,6 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/map/">
|
||||
<file>map/map.qml</file>
|
||||
<file>map/map_5_12.qml</file>
|
||||
<file>map/antenna.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtLocation 5.12
|
||||
import QtPositioning 5.12
|
||||
import QtQuick 2.14
|
||||
import QtQuick.Window 2.14
|
||||
import QtQuick.Controls 2.14
|
||||
import QtLocation 5.14
|
||||
import QtPositioning 5.14
|
||||
|
||||
Item {
|
||||
id: qmlMap
|
||||
@ -14,16 +14,16 @@ Item {
|
||||
function createMap(pluginParameters) {
|
||||
var parameters = new Array()
|
||||
for (var prop in pluginParameters) {
|
||||
var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap)
|
||||
var parameter = Qt.createQmlObject('import QtLocation 5.14; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap)
|
||||
parameters.push(parameter)
|
||||
}
|
||||
qmlMap.mapParameters = parameters
|
||||
|
||||
var plugin
|
||||
if (mapParameters && mapParameters.length > 0)
|
||||
plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap)
|
||||
plugin = Qt.createQmlObject ('import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap)
|
||||
else
|
||||
plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"}', qmlMap)
|
||||
plugin = Qt.createQmlObject ('import QtLocation 5.14; Plugin{ name:"' + mapProvider + '"}', qmlMap)
|
||||
if (mapPtr) {
|
||||
// Objects aren't destroyed immediately, so rename the old
|
||||
// map, so any C++ code that calls findChild("map") doesn't find
|
||||
@ -66,6 +66,27 @@ Item {
|
||||
center: QtPositioning.coordinate(51.5, 0.125) // London
|
||||
zoomLevel: 10
|
||||
|
||||
// Tracks first, so drawn under other items
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: groundTrack1Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: groundTrack2Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: predictedGroundTrack1Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: predictedGroundTrack2Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: mapComponent
|
||||
@ -73,12 +94,25 @@ Item {
|
||||
|
||||
onZoomLevelChanged: {
|
||||
mapZoomLevel = zoomLevel
|
||||
}
|
||||
|
||||
// The map displays MapPolyLines in the wrong place (+360 degrees) if
|
||||
// they start to the left of the visible region, so we need to
|
||||
// split them so they don't, each time the visible region is changed. meh.
|
||||
onCenterChanged: {
|
||||
mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function mapRect() {
|
||||
if (mapPtr)
|
||||
return mapPtr.visibleRegion.boundingGeoRectangle();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mapComponent
|
||||
MapQuickItem {
|
||||
@ -87,6 +121,7 @@ Item {
|
||||
anchorPoint.y: image.height/2
|
||||
coordinate: position
|
||||
zoomLevel: mapZoomLevel > mapImageMinZoom ? mapZoomLevel : mapImageMinZoom
|
||||
autoFadeIn: false // not in 5.12
|
||||
|
||||
sourceItem: Grid {
|
||||
id: gridItem
|
||||
@ -185,4 +220,46 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: predictedGroundTrack1Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: predictedGroundTrackColor
|
||||
path: predictedGroundTrack1
|
||||
autoFadeIn: false
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the line that crosses edge of map
|
||||
Component {
|
||||
id: predictedGroundTrack2Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: predictedGroundTrackColor
|
||||
path: predictedGroundTrack2
|
||||
autoFadeIn: false
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: groundTrack1Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: groundTrackColor
|
||||
path: groundTrack1
|
||||
autoFadeIn: false
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the line that crosses edge of map
|
||||
Component {
|
||||
id: groundTrack2Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: groundTrackColor
|
||||
path: groundTrack2
|
||||
autoFadeIn: false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
261
plugins/feature/map/map/map_5_12.qml
Normal file
261
plugins/feature/map/map/map_5_12.qml
Normal file
@ -0,0 +1,261 @@
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Window 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtLocation 5.12
|
||||
import QtPositioning 5.12
|
||||
|
||||
Item {
|
||||
id: qmlMap
|
||||
property int mapZoomLevel: 11
|
||||
property string mapProvider: "osm"
|
||||
property variant mapParameters
|
||||
property variant mapPtr
|
||||
|
||||
function createMap(pluginParameters) {
|
||||
var parameters = new Array()
|
||||
for (var prop in pluginParameters) {
|
||||
var parameter = Qt.createQmlObject('import QtLocation 5.6; PluginParameter{ name: "'+ prop + '"; value: "' + pluginParameters[prop]+'"}', qmlMap)
|
||||
parameters.push(parameter)
|
||||
}
|
||||
qmlMap.mapParameters = parameters
|
||||
|
||||
var plugin
|
||||
if (mapParameters && mapParameters.length > 0)
|
||||
plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"; parameters: qmlMap.mapParameters}', qmlMap)
|
||||
else
|
||||
plugin = Qt.createQmlObject ('import QtLocation 5.12; Plugin{ name:"' + mapProvider + '"}', qmlMap)
|
||||
if (mapPtr) {
|
||||
// Objects aren't destroyed immediately, so rename the old
|
||||
// map, so any C++ code that calls findChild("map") doesn't find
|
||||
// the old map
|
||||
mapPtr.objectName = "oldMap";
|
||||
mapPtr.destroy()
|
||||
}
|
||||
mapPtr = actualMapComponent.createObject(page)
|
||||
mapPtr.plugin = plugin;
|
||||
mapPtr.forceActiveFocus()
|
||||
mapPtr.objectName = "map";
|
||||
}
|
||||
|
||||
function getMapTypes() {
|
||||
var mapTypes = []
|
||||
if (mapPtr) {
|
||||
for (var i = 0; i < mapPtr.supportedMapTypes.length; i++) {
|
||||
mapTypes[i] = mapPtr.supportedMapTypes[i].name
|
||||
}
|
||||
}
|
||||
return mapTypes
|
||||
}
|
||||
|
||||
function setMapType(mapTypeIndex) {
|
||||
if (mapPtr)
|
||||
mapPtr.activeMapType = mapPtr.supportedMapTypes[mapTypeIndex]
|
||||
}
|
||||
|
||||
Item {
|
||||
id: page
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: actualMapComponent
|
||||
|
||||
Map {
|
||||
id: map
|
||||
anchors.fill: parent
|
||||
center: QtPositioning.coordinate(51.5, 0.125) // London
|
||||
zoomLevel: 10
|
||||
|
||||
// Tracks first, so drawn under other items
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: groundTrack1Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: groundTrack2Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: predictedGroundTrack1Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: predictedGroundTrack2Component
|
||||
}
|
||||
|
||||
MapItemView {
|
||||
model: mapModel
|
||||
delegate: mapComponent
|
||||
}
|
||||
|
||||
onZoomLevelChanged: {
|
||||
mapZoomLevel = zoomLevel
|
||||
}
|
||||
|
||||
// The map displays MapPolyLines in the wrong place (+360 degrees) if
|
||||
// they start to the left of the visible region, so we need to
|
||||
// split them so they don't, each time the visible region is changed. meh.
|
||||
onCenterChanged: {
|
||||
mapModel.viewChanged(visibleRegion.boundingGeoRectangle().bottomLeft.longitude, visibleRegion.boundingGeoRectangle().bottomRight.longitude);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function mapRect() {
|
||||
if (mapPtr)
|
||||
return mapPtr.visibleRegion.boundingGeoRectangle();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
Component {
|
||||
id: mapComponent
|
||||
MapQuickItem {
|
||||
id: mapElement
|
||||
anchorPoint.x: image.width/2
|
||||
anchorPoint.y: image.height/2
|
||||
coordinate: position
|
||||
zoomLevel: mapZoomLevel > mapImageMinZoom ? mapZoomLevel : mapImageMinZoom
|
||||
//autoFadeIn: false // not in 5.12
|
||||
|
||||
sourceItem: Grid {
|
||||
id: gridItem
|
||||
columns: 1
|
||||
Grid {
|
||||
horizontalItemAlignment: Grid.AlignHCenter
|
||||
columnSpacing: 5
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
Image {
|
||||
id: image
|
||||
rotation: mapImageRotation
|
||||
source: mapImage
|
||||
visible: mapImageVisible
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
selected = !selected
|
||||
if (selected) {
|
||||
mapModel.moveToFront(index)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (frequency > 0) {
|
||||
freqMenuItem.text = "Set frequency to " + frequencyString
|
||||
freqMenuItem.enabled = true
|
||||
} else {
|
||||
freqMenuItem.text = "No frequency available"
|
||||
freqMenuItem.enabled = false
|
||||
}
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
id: bubble
|
||||
color: bubbleColour
|
||||
border.width: 1
|
||||
width: text.width + 5
|
||||
height: text.height + 5
|
||||
radius: 5
|
||||
visible: mapTextVisible
|
||||
Text {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
text: mapText
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
selected = !selected
|
||||
if (selected) {
|
||||
mapModel.moveToFront(index)
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (frequency > 0) {
|
||||
freqMenuItem.text = "Set frequency to " + frequencyString
|
||||
freqMenuItem.enabled = true
|
||||
} else {
|
||||
freqMenuItem.text = "No frequency available"
|
||||
freqMenuItem.enabled = false
|
||||
}
|
||||
contextMenu.popup()
|
||||
}
|
||||
}
|
||||
Menu {
|
||||
id: contextMenu
|
||||
MenuItem {
|
||||
text: "Set as target"
|
||||
onTriggered: target = true
|
||||
}
|
||||
MenuItem {
|
||||
id: freqMenuItem
|
||||
text: "Not set"
|
||||
onTriggered: mapModel.setFrequency(frequency)
|
||||
}
|
||||
MenuItem {
|
||||
text: "Move to front"
|
||||
onTriggered: mapModel.moveToFront(index)
|
||||
}
|
||||
MenuItem {
|
||||
text: "Move to back"
|
||||
onTriggered: mapModel.moveToBack(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: predictedGroundTrack1Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: predictedGroundTrackColor
|
||||
path: predictedGroundTrack1
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the line that crosses edge of map
|
||||
Component {
|
||||
id: predictedGroundTrack2Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: predictedGroundTrackColor
|
||||
path: predictedGroundTrack2
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: groundTrack1Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: groundTrackColor
|
||||
path: groundTrack1
|
||||
}
|
||||
}
|
||||
|
||||
// Part of the line that crosses edge of map
|
||||
Component {
|
||||
id: groundTrack2Component
|
||||
MapPolyline {
|
||||
line.width: 2
|
||||
line.color: groundTrackColor
|
||||
path: groundTrack2
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -139,6 +139,42 @@ QVariant MapModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue(m_items[row]->m_frequency);
|
||||
else if (role == MapModel::frequencyStringRole)
|
||||
return QVariant::fromValue(m_items[row]->m_frequencyString);
|
||||
else if (role == MapModel::predictedGroundTrack1Role)
|
||||
{
|
||||
if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask))
|
||||
return m_items[row]->m_predictedTrack1;
|
||||
else
|
||||
return QVariantList();
|
||||
}
|
||||
else if (role == MapModel::predictedGroundTrack2Role)
|
||||
{
|
||||
if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask))
|
||||
return m_items[row]->m_predictedTrack2;
|
||||
else
|
||||
return QVariantList();
|
||||
}
|
||||
else if (role == MapModel::groundTrack1Role)
|
||||
{
|
||||
if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask))
|
||||
return m_items[row]->m_takenTrack1;
|
||||
else
|
||||
return QVariantList();
|
||||
}
|
||||
else if (role == MapModel::groundTrack2Role)
|
||||
{
|
||||
if ((m_displayAllGroundTracks || (m_displaySelectedGroundTracks && m_selected[row])) && (m_sources & m_items[row]->m_sourceMask))
|
||||
return m_items[row]->m_takenTrack2;
|
||||
else
|
||||
return QVariantList();
|
||||
}
|
||||
else if (role == groundTrackColorRole)
|
||||
{
|
||||
return m_groundTrackColor;
|
||||
}
|
||||
else if (role == predictedGroundTrackColorRole)
|
||||
{
|
||||
return m_predictedGroundTrackColor;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
@ -193,6 +229,7 @@ void MapModel::update(const PipeEndPoint *sourcePipe, SWGSDRangel::SWGMapItem *s
|
||||
{
|
||||
// Update the item
|
||||
item->update(swgMapItem);
|
||||
splitTracks(item);
|
||||
update(item);
|
||||
}
|
||||
}
|
||||
@ -235,6 +272,311 @@ void MapModel::updateTarget()
|
||||
}
|
||||
}
|
||||
|
||||
void MapModel::splitTracks(MapItem *item)
|
||||
{
|
||||
if (item->m_takenTrackCoords.size() > 1)
|
||||
splitTrack(item->m_takenTrackCoords, item->m_takenTrack, item->m_takenTrack1, item->m_takenTrack2,
|
||||
item->m_takenStart1, item->m_takenStart2, item->m_takenEnd1, item->m_takenEnd2);
|
||||
if (item->m_predictedTrackCoords.size() > 1)
|
||||
splitTrack(item->m_predictedTrackCoords, item->m_predictedTrack, item->m_predictedTrack1, item->m_predictedTrack2,
|
||||
item->m_predictedStart1, item->m_predictedStart2, item->m_predictedEnd1, item->m_predictedEnd2);
|
||||
}
|
||||
|
||||
void MapModel::interpolateEast(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen)
|
||||
{
|
||||
double x1 = c1->longitude();
|
||||
double y1 = c1->latitude();
|
||||
double x2 = c2->longitude();
|
||||
double y2 = c2->latitude();
|
||||
double y;
|
||||
if (x2 < x1)
|
||||
x2 += 360.0;
|
||||
if (x < x1)
|
||||
x += 360.0;
|
||||
y = interpolate(x1, y1, x2, y2, x);
|
||||
if (x > 180)
|
||||
x -= 360.0;
|
||||
if (offScreen)
|
||||
x -= 0.000000001;
|
||||
else
|
||||
x += 0.000000001;
|
||||
ci->setLongitude(x);
|
||||
ci->setLatitude(y);
|
||||
ci->setAltitude(c1->altitude());
|
||||
}
|
||||
|
||||
void MapModel::interpolateWest(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen)
|
||||
{
|
||||
double x1 = c1->longitude();
|
||||
double y1 = c1->latitude();
|
||||
double x2 = c2->longitude();
|
||||
double y2 = c2->latitude();
|
||||
double y;
|
||||
if (x2 > x1)
|
||||
x2 -= 360.0;
|
||||
if (x > x1)
|
||||
x -= 360.0;
|
||||
y = interpolate(x1, y1, x2, y2, x);
|
||||
if (x < -180)
|
||||
x += 360.0;
|
||||
if (offScreen)
|
||||
x += 0.000000001;
|
||||
else
|
||||
x -= 0.000000001;
|
||||
ci->setLongitude(x);
|
||||
ci->setLatitude(y);
|
||||
ci->setAltitude(c1->altitude());
|
||||
}
|
||||
|
||||
static bool isOnScreen(double lon, double bottomLeftLongitude, double bottomRightLongitude, double width, bool antimed)
|
||||
{
|
||||
bool onScreen = false;
|
||||
if (width == 360)
|
||||
onScreen = true;
|
||||
else if (!antimed)
|
||||
onScreen = (lon > bottomLeftLongitude) && (lon <= bottomRightLongitude);
|
||||
else
|
||||
onScreen = (lon > bottomLeftLongitude) || (lon <= bottomRightLongitude);
|
||||
return onScreen;
|
||||
}
|
||||
|
||||
static bool crossesAntimeridian(double prevLon, double lon)
|
||||
{
|
||||
bool crosses = false;
|
||||
if ((prevLon > 90) && (lon < -90))
|
||||
crosses = true; // West to East
|
||||
else if ((prevLon < -90) && (lon > 90))
|
||||
crosses = true; // East to West
|
||||
return crosses;
|
||||
}
|
||||
|
||||
static bool crossesAntimeridianEast(double prevLon, double lon)
|
||||
{
|
||||
bool crosses = false;
|
||||
if ((prevLon > 90) && (lon < -90))
|
||||
crosses = true; // West to East
|
||||
return crosses;
|
||||
}
|
||||
|
||||
static bool crossesAntimeridianWest(double prevLon, double lon)
|
||||
{
|
||||
bool crosses = false;
|
||||
if ((prevLon < -90) && (lon > 90))
|
||||
crosses = true; // East to West
|
||||
return crosses;
|
||||
}
|
||||
|
||||
static bool crossesEdge(double lon, double prevLon, double bottomLeftLongitude, double bottomRightLongitude, double width, bool antimed)
|
||||
{
|
||||
// Determine if antimerdian is between the two points
|
||||
if (!crossesAntimeridian(prevLon, lon))
|
||||
{
|
||||
bool crosses = false;
|
||||
if ((prevLon <= bottomRightLongitude) && (lon > bottomRightLongitude))
|
||||
crosses = true; // Crosses right edge East
|
||||
else if ((prevLon >= bottomRightLongitude) && (lon < bottomRightLongitude))
|
||||
crosses = true; // Crosses right edge West
|
||||
else if ((prevLon >= bottomLeftLongitude) && (lon < bottomLeftLongitude))
|
||||
crosses = true; // Crosses left edge West
|
||||
else if ((prevLon <= bottomLeftLongitude) && (lon > bottomLeftLongitude))
|
||||
crosses = true; // Crosses left edge East
|
||||
return crosses;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine which point and the edge the antimerdian is between
|
||||
bool prevLonToRightCrossesAnti = crossesAntimeridianEast(prevLon, bottomRightLongitude);
|
||||
bool rightToLonCrossesAnti = crossesAntimeridianEast(bottomRightLongitude, lon);
|
||||
bool prevLonToLeftCrossesAnti = crossesAntimeridianWest(prevLon, bottomLeftLongitude);
|
||||
bool leftToLonCrossesAnti = crossesAntimeridianWest(bottomLeftLongitude, lon);
|
||||
|
||||
bool crosses = false;
|
||||
if ( ((prevLon > bottomRightLongitude) && prevLonToRightCrossesAnti && (lon > bottomRightLongitude))
|
||||
|| ((prevLon <= bottomRightLongitude) && (lon <= bottomRightLongitude) && rightToLonCrossesAnti)
|
||||
)
|
||||
crosses = true; // Crosses right edge East
|
||||
else if ( ((prevLon < bottomRightLongitude) && prevLonToRightCrossesAnti && (lon < bottomRightLongitude))
|
||||
|| ((prevLon >= bottomRightLongitude) && (lon >= bottomRightLongitude) && rightToLonCrossesAnti)
|
||||
)
|
||||
crosses = true; // Crosses right edge West
|
||||
else if ( ((prevLon < bottomLeftLongitude) && prevLonToLeftCrossesAnti && (lon < bottomLeftLongitude))
|
||||
|| ((prevLon >= bottomLeftLongitude) && (lon >= bottomLeftLongitude) && leftToLonCrossesAnti)
|
||||
)
|
||||
crosses = true; // Crosses left edge West
|
||||
else if ( ((prevLon > bottomLeftLongitude) && prevLonToLeftCrossesAnti && (lon > bottomLeftLongitude))
|
||||
|| ((prevLon <= bottomLeftLongitude) && (lon <= bottomLeftLongitude) && leftToLonCrossesAnti)
|
||||
)
|
||||
crosses = true; // Crosses left edge East
|
||||
return crosses;
|
||||
}
|
||||
}
|
||||
|
||||
void MapModel::interpolate(QGeoCoordinate *c1, QGeoCoordinate *c2, double bottomLeftLongitude, double bottomRightLongitude, QGeoCoordinate* ci, bool offScreen)
|
||||
{
|
||||
double x1 = c1->longitude();
|
||||
double x2 = c2->longitude();
|
||||
double crossesAnti = crossesAntimeridian(x1, x2);
|
||||
double x;
|
||||
|
||||
// Need to work out which edge we're interpolating too
|
||||
// and whether antimeridian is in the way, as that flips x1<x2 to x1>x2
|
||||
|
||||
if (((x1 < x2) && !crossesAnti) || ((x1 > x2) && crossesAnti))
|
||||
{
|
||||
x = offScreen ? bottomRightLongitude : bottomLeftLongitude;
|
||||
interpolateEast(c1, c2, x, ci, offScreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
x = offScreen ? bottomLeftLongitude : bottomRightLongitude;
|
||||
interpolateWest(c1, c2, x, ci, offScreen);
|
||||
}
|
||||
}
|
||||
|
||||
void MapModel::splitTrack(const QList<QGeoCoordinate *>& coords, const QVariantList& track,
|
||||
QVariantList& track1, QVariantList& track2,
|
||||
QGeoCoordinate& start1, QGeoCoordinate& start2,
|
||||
QGeoCoordinate& end1, QGeoCoordinate& end2)
|
||||
{
|
||||
/*
|
||||
QStringList l;
|
||||
for (int i = 0; i < track.size(); i++)
|
||||
{
|
||||
QGeoCoordinate c = track[i].value<QGeoCoordinate>();
|
||||
l.append(QString("%1").arg((int)c.longitude()));
|
||||
}
|
||||
qDebug() << "Init T: " << l;
|
||||
*/
|
||||
|
||||
QQuickItem* map = m_gui->getMapItem();
|
||||
QVariant rectVariant;
|
||||
QMetaObject::invokeMethod(map, "mapRect", Q_RETURN_ARG(QVariant, rectVariant));
|
||||
QGeoRectangle rect = qvariant_cast<QGeoRectangle>(rectVariant);
|
||||
double bottomLeftLongitude = rect.bottomLeft().longitude();
|
||||
double bottomRightLongitude = rect.bottomRight().longitude();
|
||||
|
||||
int width = round(rect.width());
|
||||
bool antimed = (width == 360) || (bottomLeftLongitude > bottomRightLongitude);
|
||||
|
||||
/*
|
||||
qDebug() << "Anitmed visible: " << antimed;
|
||||
qDebug() << "bottomLeftLongitude: " << bottomLeftLongitude;
|
||||
qDebug() << "bottomRightLongitude: " << bottomRightLongitude;
|
||||
*/
|
||||
|
||||
track1.clear();
|
||||
track2.clear();
|
||||
|
||||
double lon, prevLon;
|
||||
bool onScreen, prevOnScreen;
|
||||
QList<QVariantList *> tracks({&track1, &track2});
|
||||
QList<QGeoCoordinate *> ends({&end1, &end2});
|
||||
QList<QGeoCoordinate *> starts({&start1, &start2});
|
||||
int trackIdx = 0;
|
||||
for (int i = 0; i < coords.size(); i++)
|
||||
{
|
||||
lon = coords[i]->longitude();
|
||||
if (i == 0)
|
||||
{
|
||||
prevLon = lon;
|
||||
prevOnScreen = true; // To avoid interpolation for first point
|
||||
}
|
||||
// Can be onscreen after having crossed edge from other side
|
||||
// Or can be onscreen after previously having been off screen
|
||||
onScreen = isOnScreen(lon, bottomLeftLongitude, bottomRightLongitude, width, antimed);
|
||||
bool crossedEdge = crossesEdge(lon, prevLon, bottomLeftLongitude, bottomRightLongitude, width, antimed);
|
||||
if ((onScreen && !crossedEdge) || (onScreen && !prevOnScreen))
|
||||
{
|
||||
if ((i > 0) && (tracks[trackIdx]->size() == 0)) // Could also use (onScreen && !prevOnScreen)?
|
||||
{
|
||||
if (trackIdx >= starts.size())
|
||||
break;
|
||||
// Interpolate from edge of screen
|
||||
interpolate(coords[i-1], coords[i], bottomLeftLongitude, bottomRightLongitude, starts[trackIdx], false);
|
||||
tracks[trackIdx]->append(QVariant::fromValue(*starts[trackIdx]));
|
||||
}
|
||||
tracks[trackIdx]->append(track[i]);
|
||||
}
|
||||
else if (tracks[trackIdx]->size() > 0)
|
||||
{
|
||||
// Either we've crossed to the other side, or have gone off screen
|
||||
if (trackIdx >= ends.size())
|
||||
break;
|
||||
// Interpolate to edge of screen
|
||||
interpolate(coords[i-1], coords[i], bottomLeftLongitude, bottomRightLongitude, ends[trackIdx], true);
|
||||
tracks[trackIdx]->append(QVariant::fromValue(*ends[trackIdx]));
|
||||
// Start new track
|
||||
trackIdx++;
|
||||
if (trackIdx >= tracks.size())
|
||||
{
|
||||
// This can happen with highly retrograde orbits, where trace 90% of period
|
||||
// will cover more than 360 degrees - delete last point as Map
|
||||
// will not be able to display it properly
|
||||
tracks[trackIdx-1]->removeLast();
|
||||
break;
|
||||
}
|
||||
if (onScreen)
|
||||
{
|
||||
// Interpolate from edge of screen
|
||||
interpolate(coords[i-1], coords[i], bottomLeftLongitude, bottomRightLongitude, starts[trackIdx], false);
|
||||
tracks[trackIdx]->append(QVariant::fromValue(*starts[trackIdx]));
|
||||
tracks[trackIdx]->append(track[i]);
|
||||
}
|
||||
}
|
||||
prevLon = lon;
|
||||
prevOnScreen = onScreen;
|
||||
}
|
||||
|
||||
/*
|
||||
l.clear();
|
||||
for (int i = 0; i < track1.size(); i++)
|
||||
{
|
||||
QGeoCoordinate c = track1[i].value<QGeoCoordinate>();
|
||||
if (!c.isValid())
|
||||
l.append("Invalid!");
|
||||
else
|
||||
l.append(QString("%1").arg(c.longitude(), 0, 'f', 1));
|
||||
}
|
||||
qDebug() << "T1: " << l;
|
||||
|
||||
l.clear();
|
||||
for (int i = 0; i < track2.size(); i++)
|
||||
{
|
||||
QGeoCoordinate c = track2[i].value<QGeoCoordinate>();
|
||||
if (!c.isValid())
|
||||
l.append("Invalid!");
|
||||
else
|
||||
l.append(QString("%1").arg(c.longitude(), 0, 'f', 1));
|
||||
}
|
||||
qDebug() << "T2: " << l;
|
||||
*/
|
||||
}
|
||||
|
||||
void MapModel::viewChanged(double bottomLeftLongitude, double bottomRightLongitude)
|
||||
{
|
||||
if (!isnan(bottomLeftLongitude))
|
||||
{
|
||||
for (int row = 0; row < m_items.size(); row++)
|
||||
{
|
||||
MapItem *item = m_items[row];
|
||||
if (item->m_takenTrackCoords.size() > 1)
|
||||
{
|
||||
splitTrack(item->m_takenTrackCoords, item->m_takenTrack, item->m_takenTrack1, item->m_takenTrack2,
|
||||
item->m_takenStart1, item->m_takenStart2, item->m_takenEnd1, item->m_takenEnd2);
|
||||
QModelIndex idx = index(row);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
if (item->m_predictedTrackCoords.size() > 1)
|
||||
{
|
||||
splitTrack(item->m_predictedTrackCoords, item->m_predictedTrack, item->m_predictedTrack1, item->m_predictedTrack2,
|
||||
item->m_predictedStart1, item->m_predictedStart2, item->m_predictedEnd1, item->m_predictedEnd2);
|
||||
QModelIndex idx = index(row);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MapGUI* MapGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
|
||||
{
|
||||
MapGUI* gui = new MapGUI(pluginAPI, featureUISet, feature);
|
||||
@ -341,7 +683,12 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->map->rootContext()->setContextProperty("mapModel", &m_mapModel);
|
||||
// 5.12 doesn't display map items when fully zoomed out
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map_5_12.qml")));
|
||||
#else
|
||||
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map/map.qml")));
|
||||
#endif
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
setChannelWidget(false);
|
||||
@ -408,7 +755,9 @@ void MapGUI::setBeacons(QList<Beacon *> *beacons)
|
||||
{
|
||||
Beacon *beacon = i.next();
|
||||
SWGSDRangel::SWGMapItem beaconMapItem;
|
||||
beaconMapItem.setName(new QString(beacon->m_callsign));
|
||||
// Need to suffix frequency, as there are multiple becaons with same callsign at different locations
|
||||
QString name = QString("%1-%2").arg(beacon->m_callsign).arg(beacon->getFrequencyShortText());
|
||||
beaconMapItem.setName(new QString(name));
|
||||
beaconMapItem.setLatitude(beacon->m_latitude);
|
||||
beaconMapItem.setLongitude(beacon->m_longitude);
|
||||
beaconMapItem.setAltitude(beacon->m_altitude);
|
||||
@ -487,8 +836,14 @@ void MapGUI::displaySettings()
|
||||
setWindowTitle(m_settings.m_title);
|
||||
blockApplySettings(true);
|
||||
ui->displayNames->setChecked(m_settings.m_displayNames);
|
||||
ui->displaySelectedGroundTracks->setChecked(m_settings.m_displaySelectedGroundTracks);
|
||||
ui->displayAllGroundTracks->setChecked(m_settings.m_displayAllGroundTracks);
|
||||
m_mapModel.setDisplayNames(m_settings.m_displayNames);
|
||||
m_mapModel.setDisplaySelectedGroundTracks(m_settings.m_displaySelectedGroundTracks);
|
||||
m_mapModel.setDisplayAllGroundTracks(m_settings.m_displayAllGroundTracks);
|
||||
m_mapModel.setSources(m_settings.m_sources);
|
||||
m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor);
|
||||
m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor);
|
||||
applyMapSettings();
|
||||
blockApplySettings(false);
|
||||
}
|
||||
@ -555,6 +910,18 @@ void MapGUI::on_displayNames_clicked(bool checked)
|
||||
m_mapModel.setDisplayNames(checked);
|
||||
}
|
||||
|
||||
void MapGUI::on_displaySelectedGroundTracks_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_displaySelectedGroundTracks = checked;
|
||||
m_mapModel.setDisplaySelectedGroundTracks(checked);
|
||||
}
|
||||
|
||||
void MapGUI::on_displayAllGroundTracks_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_displayAllGroundTracks = checked;
|
||||
m_mapModel.setDisplayAllGroundTracks(checked);
|
||||
}
|
||||
|
||||
void MapGUI::on_find_returnPressed()
|
||||
{
|
||||
find(ui->find->text().trimmed());
|
||||
@ -655,6 +1022,8 @@ void MapGUI::on_displaySettings_clicked()
|
||||
applySettings();
|
||||
if (dialog.m_sourcesChanged)
|
||||
m_mapModel.setSources(m_settings.m_sources);
|
||||
m_mapModel.setGroundTrackColor(m_settings.m_groundTrackColor);
|
||||
m_mapModel.setPredictedGroundTrackColor(m_settings.m_predictedGroundTrackColor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -691,3 +1060,7 @@ QString MapGUI::getBeaconFilename()
|
||||
{
|
||||
return MapGUI::getDataDir() + "/iaru_beacons.csv";
|
||||
}
|
||||
|
||||
QQuickItem *MapGUI::getMapItem() {
|
||||
return ui->map->rootObject();
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <QTimer>
|
||||
#include <QAbstractListModel>
|
||||
#include <QGeoCoordinate>
|
||||
#include <QGeoRectangle>
|
||||
|
||||
#include "feature/featuregui.h"
|
||||
#include "util/messagequeue.h"
|
||||
@ -41,6 +42,7 @@ namespace Ui {
|
||||
|
||||
class MapGUI;
|
||||
class MapModel;
|
||||
class QQuickItem;
|
||||
struct Beacon;
|
||||
|
||||
// Information required about each item displayed on the map
|
||||
@ -62,6 +64,8 @@ public:
|
||||
if (text != nullptr)
|
||||
m_text = *text;
|
||||
findFrequency();
|
||||
updateTrack(mapItem->getTrack());
|
||||
updatePredictedTrack(mapItem->getPredictedTrack());
|
||||
}
|
||||
|
||||
void update(SWGSDRangel::SWGMapItem *mapItem)
|
||||
@ -76,6 +80,8 @@ public:
|
||||
if (text != nullptr)
|
||||
m_text = *text;
|
||||
findFrequency();
|
||||
updateTrack(mapItem->getTrack());
|
||||
updatePredictedTrack(mapItem->getPredictedTrack());
|
||||
}
|
||||
|
||||
QGeoCoordinate getCoordinates()
|
||||
@ -90,6 +96,64 @@ private:
|
||||
|
||||
void findFrequency();
|
||||
|
||||
void updateTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
|
||||
{
|
||||
if (track != nullptr)
|
||||
{
|
||||
qDeleteAll(m_takenTrackCoords);
|
||||
m_takenTrackCoords.clear();
|
||||
m_takenTrack.clear();
|
||||
m_takenTrack1.clear();
|
||||
m_takenTrack2.clear();
|
||||
for (int i = 0; i < track->size(); i++)
|
||||
{
|
||||
SWGSDRangel::SWGMapCoordinate* p = track->at(i);
|
||||
QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude());
|
||||
m_takenTrackCoords.push_back(c);
|
||||
m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Automatically create a track
|
||||
if (m_takenTrackCoords.size() == 0)
|
||||
{
|
||||
QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude);
|
||||
m_takenTrackCoords.push_back(c);
|
||||
m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
else
|
||||
{
|
||||
QGeoCoordinate *prev = m_takenTrackCoords.last();
|
||||
if ((prev->latitude() != m_latitude) || (prev->longitude() != m_longitude) || (prev->altitude() != m_altitude))
|
||||
{
|
||||
QGeoCoordinate *c = new QGeoCoordinate(m_latitude, m_longitude, m_altitude);
|
||||
m_takenTrackCoords.push_back(c);
|
||||
m_takenTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updatePredictedTrack(QList<SWGSDRangel::SWGMapCoordinate *> *track)
|
||||
{
|
||||
if (track != nullptr)
|
||||
{
|
||||
qDeleteAll(m_predictedTrackCoords);
|
||||
m_predictedTrackCoords.clear();
|
||||
m_predictedTrack.clear();
|
||||
m_predictedTrack1.clear();
|
||||
m_predictedTrack2.clear();
|
||||
for (int i = 0; i < track->size(); i++)
|
||||
{
|
||||
SWGSDRangel::SWGMapCoordinate* p = track->at(i);
|
||||
QGeoCoordinate *c = new QGeoCoordinate(p->getLatitude(), p->getLongitude(), p->getAltitude());
|
||||
m_predictedTrackCoords.push_back(c);
|
||||
m_predictedTrack.push_back(QVariant::fromValue(*c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
friend MapModel;
|
||||
const PipeEndPoint *m_sourcePipe; // Channel/feature that created the item
|
||||
quint32 m_sourceMask; // Source bitmask as per MapSettings::SOURCE_* constants
|
||||
@ -103,6 +167,22 @@ private:
|
||||
QString m_text;
|
||||
double m_frequency; // Frequency to set
|
||||
QString m_frequencyString;
|
||||
QList<QGeoCoordinate *> m_predictedTrackCoords;
|
||||
QVariantList m_predictedTrack; // Line showing where the object is going
|
||||
QVariantList m_predictedTrack1;
|
||||
QVariantList m_predictedTrack2;
|
||||
QGeoCoordinate m_predictedStart1;
|
||||
QGeoCoordinate m_predictedStart2;
|
||||
QGeoCoordinate m_predictedEnd1;
|
||||
QGeoCoordinate m_predictedEnd2;
|
||||
QList<QGeoCoordinate *> m_takenTrackCoords;
|
||||
QVariantList m_takenTrack; // Line showing where the object has been
|
||||
QVariantList m_takenTrack1;
|
||||
QVariantList m_takenTrack2;
|
||||
QGeoCoordinate m_takenStart1;
|
||||
QGeoCoordinate m_takenStart2;
|
||||
QGeoCoordinate m_takenEnd1;
|
||||
QGeoCoordinate m_takenEnd2;
|
||||
};
|
||||
|
||||
// Model used for each item on the map
|
||||
@ -123,7 +203,13 @@ public:
|
||||
selectedRole = Qt::UserRole + 9,
|
||||
targetRole = Qt::UserRole + 10,
|
||||
frequencyRole = Qt::UserRole + 11,
|
||||
frequencyStringRole = Qt::UserRole + 12
|
||||
frequencyStringRole = Qt::UserRole + 12,
|
||||
predictedGroundTrack1Role = Qt::UserRole + 13,
|
||||
predictedGroundTrack2Role = Qt::UserRole + 14,
|
||||
groundTrack1Role = Qt::UserRole + 15,
|
||||
groundTrack2Role = Qt::UserRole + 16,
|
||||
groundTrackColorRole = Qt::UserRole + 17,
|
||||
predictedGroundTrackColorRole = Qt::UserRole + 18
|
||||
};
|
||||
|
||||
MapModel(MapGUI *gui) :
|
||||
@ -131,6 +217,8 @@ public:
|
||||
m_target(-1),
|
||||
m_sources(-1)
|
||||
{
|
||||
setGroundTrackColor(0);
|
||||
setPredictedGroundTrackColor(0);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void add(MapItem *item)
|
||||
@ -275,8 +363,41 @@ public:
|
||||
allUpdated();
|
||||
}
|
||||
|
||||
void setDisplaySelectedGroundTracks(bool displayGroundTracks)
|
||||
{
|
||||
m_displaySelectedGroundTracks = displayGroundTracks;
|
||||
allUpdated();
|
||||
}
|
||||
|
||||
void setDisplayAllGroundTracks(bool displayGroundTracks)
|
||||
{
|
||||
m_displayAllGroundTracks = displayGroundTracks;
|
||||
allUpdated();
|
||||
}
|
||||
|
||||
void setGroundTrackColor(quint32 color)
|
||||
{
|
||||
m_groundTrackColor = QVariant::fromValue(QColor::fromRgb(color));
|
||||
}
|
||||
|
||||
void setPredictedGroundTrackColor(quint32 color)
|
||||
{
|
||||
m_predictedGroundTrackColor = QVariant::fromValue(QColor::fromRgb(color));
|
||||
}
|
||||
|
||||
Q_INVOKABLE void setFrequency(double frequency);
|
||||
|
||||
void interpolateEast(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen);
|
||||
void interpolateWest(QGeoCoordinate *c1, QGeoCoordinate *c2, double x, QGeoCoordinate *ci, bool offScreen);
|
||||
void interpolate(QGeoCoordinate *c1, QGeoCoordinate *c2, double bottomLeftLongitude, double bottomRightLongitude, QGeoCoordinate* ci, bool offScreen);
|
||||
|
||||
void splitTracks(MapItem *item);
|
||||
void splitTrack(const QList<QGeoCoordinate *>& coords, const QVariantList& track,
|
||||
QVariantList& track1, QVariantList& track2,
|
||||
QGeoCoordinate& start1, QGeoCoordinate& start2,
|
||||
QGeoCoordinate& end1, QGeoCoordinate& end2);
|
||||
Q_INVOKABLE void viewChanged(double bottomLeftLongitude, double bottomRightLongitude);
|
||||
|
||||
QHash<int, QByteArray> roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
@ -292,6 +413,12 @@ public:
|
||||
roles[targetRole] = "target";
|
||||
roles[frequencyRole] = "frequency";
|
||||
roles[frequencyStringRole] = "frequencyString";
|
||||
roles[predictedGroundTrack1Role] = "predictedGroundTrack1";
|
||||
roles[predictedGroundTrack2Role] = "predictedGroundTrack2";
|
||||
roles[groundTrack1Role] = "groundTrack1";
|
||||
roles[groundTrack2Role] = "groundTrack2";
|
||||
roles[groundTrackColorRole] = "groundTrackColor";
|
||||
roles[predictedGroundTrackColorRole] = "predictedGroundTrackColor";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@ -302,13 +429,26 @@ public:
|
||||
allUpdated();
|
||||
}
|
||||
|
||||
// Linear interpolation
|
||||
double interpolate(double x0, double y0, double x1, double y1, double x)
|
||||
{
|
||||
return (y0*(x1-x) + y1*(x-x0)) / (x1-x0);
|
||||
}
|
||||
|
||||
private:
|
||||
MapGUI *m_gui;
|
||||
QList<MapItem *> m_items;
|
||||
QList<bool> m_selected;
|
||||
int m_target; // Row number of current target, or -1 for none
|
||||
bool m_displayNames;
|
||||
bool m_displaySelectedGroundTracks;
|
||||
bool m_displayAllGroundTracks;
|
||||
quint32 m_sources;
|
||||
QVariant m_groundTrackColor;
|
||||
QVariant m_predictedGroundTrackColor;
|
||||
|
||||
double m_bottomLeftLongitude;
|
||||
double m_bottomRightLongitude;
|
||||
};
|
||||
|
||||
class MapGUI : public FeatureGUI {
|
||||
@ -323,6 +463,7 @@ public:
|
||||
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
AzEl *getAzEl() { return &m_azEl; }
|
||||
Map *getMap() { return m_map; }
|
||||
QQuickItem *getMapItem();
|
||||
quint32 getSourceMask(const PipeEndPoint *sourcePipe);
|
||||
static QString getBeaconFilename();
|
||||
QList<Beacon *> *getBeacons() { return m_beacons; }
|
||||
@ -364,6 +505,8 @@ private slots:
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void handleInputMessages();
|
||||
void on_displayNames_clicked(bool checked=false);
|
||||
void on_displayAllGroundTracks_clicked(bool checked=false);
|
||||
void on_displaySelectedGroundTracks_clicked(bool checked=false);
|
||||
void on_find_returnPressed();
|
||||
void on_maidenhead_clicked();
|
||||
void on_deleteAll_clicked();
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>462</width>
|
||||
<height>689</height>
|
||||
<width>481</width>
|
||||
<height>750</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -42,7 +42,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>461</width>
|
||||
<width>471</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -82,9 +82,15 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="find">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
@ -93,19 +99,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="QComboBox" name="mapTypes">
|
||||
<property name="minimumSize">
|
||||
@ -120,7 +113,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="maidenhead">
|
||||
<widget class="QToolButton" name="maidenhead">
|
||||
<property name="toolTip">
|
||||
<string>Maidenhead locator conversion</string>
|
||||
</property>
|
||||
@ -134,7 +127,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="beacons">
|
||||
<widget class="QToolButton" name="beacons">
|
||||
<property name="toolTip">
|
||||
<string>Display Beacon dialog</string>
|
||||
</property>
|
||||
@ -168,7 +161,57 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="deleteAll">
|
||||
<widget class="ButtonSwitch" name="displaySelectedGroundTracks">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Adobe Devanagari</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Display ground tracks for selected item</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/logarithmic.png</normaloff>:/logarithmic.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="displayAllGroundTracks">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Adobe Devanagari</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Display all ground tracks</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/map/icons/groundtracks.png</normaloff>:/map/icons/groundtracks.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="deleteAll">
|
||||
<property name="toolTip">
|
||||
<string>Delete all items on the map</string>
|
||||
</property>
|
||||
@ -182,7 +225,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="displaySettings">
|
||||
<widget class="QToolButton" name="displaySettings">
|
||||
<property name="toolTip">
|
||||
<string>Show settings dialog</string>
|
||||
</property>
|
||||
@ -203,9 +246,9 @@
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>100</y>
|
||||
<width>461</width>
|
||||
<height>581</height>
|
||||
<y>60</y>
|
||||
<width>471</width>
|
||||
<height>681</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -244,7 +287,7 @@
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>500</height>
|
||||
<height>590</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
@ -293,6 +336,7 @@
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
<include location="icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
@ -26,13 +26,15 @@
|
||||
const QStringList MapSettings::m_pipeTypes = {
|
||||
QStringLiteral("ADSBDemod"),
|
||||
QStringLiteral("APRS"),
|
||||
QStringLiteral("StarTracker")
|
||||
QStringLiteral("StarTracker"),
|
||||
QStringLiteral("SatelliteTracker")
|
||||
};
|
||||
|
||||
const QStringList MapSettings::m_pipeURIs = {
|
||||
QStringLiteral("sdrangel.channel.adsbdemod"),
|
||||
QStringLiteral("sdrangel.feature.aprs"),
|
||||
QStringLiteral("sdrangel.feature.startracker")
|
||||
QStringLiteral("sdrangel.feature.startracker"),
|
||||
QStringLiteral("sdrangel.feature.satellitetracker")
|
||||
};
|
||||
|
||||
// GUI combo box should match ordering in this list
|
||||
@ -55,6 +57,10 @@ void MapSettings::resetToDefaults()
|
||||
m_mapBoxApiKey = "";
|
||||
m_mapBoxStyles = "";
|
||||
m_sources = -1;
|
||||
m_displaySelectedGroundTracks = true;
|
||||
m_displayAllGroundTracks = true;
|
||||
m_groundTrackColor = QColor(150, 0, 20).rgb();
|
||||
m_predictedGroundTrackColor = QColor(225, 0, 50).rgb();
|
||||
m_title = "Map";
|
||||
m_rgbColor = QColor(225, 25, 99).rgb();
|
||||
m_useReverseAPI = false;
|
||||
@ -73,6 +79,8 @@ QByteArray MapSettings::serialize() const
|
||||
s.writeString(3, m_mapBoxApiKey);
|
||||
s.writeString(4, m_mapBoxStyles);
|
||||
s.writeU32(5, m_sources);
|
||||
s.writeU32(6, m_groundTrackColor);
|
||||
s.writeU32(7, m_predictedGroundTrackColor);
|
||||
s.writeString(8, m_title);
|
||||
s.writeU32(9, m_rgbColor);
|
||||
s.writeBool(10, m_useReverseAPI);
|
||||
@ -80,6 +88,8 @@ QByteArray MapSettings::serialize() const
|
||||
s.writeU32(12, m_reverseAPIPort);
|
||||
s.writeU32(13, m_reverseAPIFeatureSetIndex);
|
||||
s.writeU32(14, m_reverseAPIFeatureIndex);
|
||||
s.writeBool(15, m_displaySelectedGroundTracks);
|
||||
s.writeBool(16, m_displayAllGroundTracks);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
@ -105,6 +115,8 @@ bool MapSettings::deserialize(const QByteArray& data)
|
||||
d.readString(3, &m_mapBoxApiKey, "");
|
||||
d.readString(4, &m_mapBoxStyles, "");
|
||||
d.readU32(5, &m_sources, -1);
|
||||
d.readU32(6, &m_groundTrackColor, QColor(150, 0, 20).rgb());
|
||||
d.readU32(7, &m_predictedGroundTrackColor, QColor(225, 0, 50).rgb());
|
||||
d.readString(8, &m_title, "Map");
|
||||
d.readU32(9, &m_rgbColor, QColor(225, 25, 99).rgb());
|
||||
d.readBool(10, &m_useReverseAPI, false);
|
||||
@ -121,6 +133,8 @@ bool MapSettings::deserialize(const QByteArray& data)
|
||||
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
|
||||
d.readU32(14, &utmp, 0);
|
||||
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
|
||||
d.readBool(15, &m_displaySelectedGroundTracks, true);
|
||||
d.readBool(16, &m_displayAllGroundTracks, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -34,6 +34,10 @@ struct MapSettings
|
||||
QString m_mapBoxApiKey;
|
||||
QString m_mapBoxStyles;
|
||||
quint32 m_sources; // Bitmask of SOURCE_*
|
||||
bool m_displayAllGroundTracks;
|
||||
bool m_displaySelectedGroundTracks;
|
||||
quint32 m_groundTrackColor;
|
||||
quint32 m_predictedGroundTrackColor;
|
||||
QString m_title;
|
||||
quint32 m_rgbColor;
|
||||
bool m_useReverseAPI;
|
||||
@ -56,8 +60,9 @@ struct MapSettings
|
||||
static const quint32 SOURCE_ADSB = 0x1;
|
||||
static const quint32 SOURCE_APRS = 0x2;
|
||||
static const quint32 SOURCE_STAR_TRACKER = 0x4;
|
||||
static const quint32 SOURCE_BEACONS = 0x8;
|
||||
static const quint32 SOURCE_STATION = 0x10;
|
||||
static const quint32 SOURCE_SATELLITE_TRACKER = 0x8;
|
||||
static const quint32 SOURCE_BEACONS = 0x10;
|
||||
static const quint32 SOURCE_STATION = 0x20;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_MAPSETTINGS_H_
|
||||
|
@ -16,12 +16,25 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
#include <QColorDialog>
|
||||
#include <QColor>
|
||||
|
||||
#include "util/units.h"
|
||||
|
||||
#include "mapsettingsdialog.h"
|
||||
#include "maplocationdialog.h"
|
||||
|
||||
static QString rgbToColor(quint32 rgb)
|
||||
{
|
||||
QColor color = QColor::fromRgb(rgb);
|
||||
return QString("%1,%2,%3").arg(color.red()).arg(color.green()).arg(color.blue());
|
||||
}
|
||||
|
||||
static QString backgroundCSS(quint32 rgb)
|
||||
{
|
||||
return QString("QToolButton { background:rgb(%1); }").arg(rgbToColor(rgb));
|
||||
}
|
||||
|
||||
MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
|
||||
QDialog(parent),
|
||||
m_settings(settings),
|
||||
@ -33,6 +46,8 @@ MapSettingsDialog::MapSettingsDialog(MapSettings *settings, QWidget* parent) :
|
||||
ui->mapBoxStyles->setText(settings->m_mapBoxStyles);
|
||||
for (int i = 0; i < ui->sourceList->count(); i++)
|
||||
ui->sourceList->item(i)->setCheckState((m_settings->m_sources & (1 << i)) ? Qt::Checked : Qt::Unchecked);
|
||||
ui->groundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_groundTrackColor));
|
||||
ui->predictedGroundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_predictedGroundTrackColor));
|
||||
}
|
||||
|
||||
MapSettingsDialog::~MapSettingsDialog()
|
||||
@ -64,3 +79,23 @@ void MapSettingsDialog::accept()
|
||||
m_settings->m_sources = sources;
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void MapSettingsDialog::on_groundTrackColor_clicked()
|
||||
{
|
||||
QColorDialog dialog(QColor::fromRgb(m_settings->m_groundTrackColor), this);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
m_settings->m_groundTrackColor = dialog.selectedColor().rgb();
|
||||
ui->groundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_groundTrackColor));
|
||||
}
|
||||
}
|
||||
|
||||
void MapSettingsDialog::on_predictedGroundTrackColor_clicked()
|
||||
{
|
||||
QColorDialog dialog(QColor::fromRgb(m_settings->m_predictedGroundTrackColor), this);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
m_settings->m_predictedGroundTrackColor = dialog.selectedColor().rgb();
|
||||
ui->predictedGroundTrackColor->setStyleSheet(backgroundCSS(m_settings->m_predictedGroundTrackColor));
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,8 @@ public:
|
||||
|
||||
private slots:
|
||||
void accept();
|
||||
void on_groundTrackColor_clicked();
|
||||
void on_predictedGroundTrackColor_clicked();
|
||||
|
||||
private:
|
||||
Ui::MapSettingsDialog* ui;
|
||||
|
@ -65,6 +65,14 @@
|
||||
<enum>Checked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Satellite Tracker</string>
|
||||
</property>
|
||||
<property name="checkState">
|
||||
<enum>Checked</enum>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Beacons</string>
|
||||
@ -76,7 +84,50 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<widget class="QGroupBox" name="colorsGroupBox">
|
||||
<property name="title">
|
||||
<string>Colours</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="predictedGroundTrackColorLabel">
|
||||
<property name="text">
|
||||
<string>Ground tracks (predicted)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="groundTrackColorLabel">
|
||||
<property name="text">
|
||||
<string>Ground tracks (taken)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QToolButton" name="predictedGroundTrackColor">
|
||||
<property name="toolTip">
|
||||
<string>Select color for predicted ground tracks</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QToolButton" name="groundTrackColor">
|
||||
<property name="toolTip">
|
||||
<string>Select color for taken ground tracks</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="mapProvidersGroupBox">
|
||||
<property name="title">
|
||||
<string>Map Provider Settings</string>
|
||||
</property>
|
||||
|
@ -3,8 +3,15 @@
|
||||
<h2>Introduction</h2>
|
||||
|
||||
The Map Feature plugin displays a world map. It can display street maps, satellite imagery as well as custom map types.
|
||||
On top of this, it can plot data from other plugins, such as APRS symbols from the APRS Feature, aircraft from the ADS-B Demodulator
|
||||
or the Sun, Moon and Stars from the Star Tracker. It can also display beacon locations based on the IARU Region 1 beacon database.
|
||||
On top of this, it can plot data from other plugins, such as:
|
||||
|
||||
* APRS symbols from the APRS Feature,
|
||||
* Aircraft from the ADS-B Demodulator,
|
||||
* Satellites from the Satellite Tracker,
|
||||
* The Sun, Moon and Stars from the Star Tracker,
|
||||
* Beacons based on the IARU Region 1 beacon database.
|
||||
|
||||
It can also create tracks showing the path aircraft and APRS objects have taken, as well as predicted paths for satellites.
|
||||
|
||||
![Map feature](../../../doc/img/Map_plugin_beacons.png)
|
||||
|
||||
@ -44,15 +51,24 @@ The beacons will then be displayed in the table and on the map.
|
||||
|
||||
When checked, names of objects are displayed in a bubble next to each object.
|
||||
|
||||
<h3>6: Delete</h3>
|
||||
<h3>6: Display tracks for selected object</h3>
|
||||
|
||||
When checked, displays the track (taken or predicted) for the selected object.
|
||||
|
||||
<h3>7: Display tracks for all objects</h3>
|
||||
|
||||
When checked, displays the track (taken or predicted) for the all objects.
|
||||
|
||||
<h3>8: Delete</h3>
|
||||
|
||||
When clicked, all items will be deleted from the map.
|
||||
|
||||
<h3>7: Display settings</h3>
|
||||
<h3>9: Display settings</h3>
|
||||
|
||||
When clicked, opens the Map Display Settings dialog, which allows setting:
|
||||
|
||||
* Which data the Map will display.
|
||||
* The colour of the taken and predicted tracks.
|
||||
* Which Map provider will be used to source the map image.
|
||||
|
||||
In order to display Mapbox maps, you will need to enter an API Key. A key can be obtained by registering at: http://www.mapbox.com/
|
||||
|
Loading…
Reference in New Issue
Block a user