From d381568437027a2d1f15948fe9e6c4ff522eb1f3 Mon Sep 17 00:00:00 2001 From: Jon Beniston Date: Fri, 26 Feb 2021 20:30:59 +0000 Subject: [PATCH] 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. --- plugins/feature/map/CMakeLists.txt | 1 + plugins/feature/map/beacon.h | 10 + plugins/feature/map/icons.qrc | 5 + plugins/feature/map/icons/groundtracks.png | Bin 0 -> 2025 bytes plugins/feature/map/map.qrc | 1 + plugins/feature/map/map/map.qml | 93 ++++- plugins/feature/map/map/map_5_12.qml | 261 ++++++++++++++ plugins/feature/map/mapgui.cpp | 375 ++++++++++++++++++++- plugins/feature/map/mapgui.h | 145 +++++++- plugins/feature/map/mapgui.ui | 94 ++++-- plugins/feature/map/mapsettings.cpp | 18 +- plugins/feature/map/mapsettings.h | 9 +- plugins/feature/map/mapsettingsdialog.cpp | 35 ++ plugins/feature/map/mapsettingsdialog.h | 2 + plugins/feature/map/mapsettingsdialog.ui | 53 ++- plugins/feature/map/readme.md | 24 +- 16 files changed, 1082 insertions(+), 44 deletions(-) create mode 100644 plugins/feature/map/icons.qrc create mode 100644 plugins/feature/map/icons/groundtracks.png create mode 100644 plugins/feature/map/map/map_5_12.qml diff --git a/plugins/feature/map/CMakeLists.txt b/plugins/feature/map/CMakeLists.txt index 4c1ea9dae..21528646a 100644 --- a/plugins/feature/map/CMakeLists.txt +++ b/plugins/feature/map/CMakeLists.txt @@ -34,6 +34,7 @@ if(NOT SERVER_MODE) mapbeacondialog.cpp mapbeacondialog.ui map.qrc + icons.qrc ) set(map_HEADERS ${map_HEADERS} diff --git a/plugins/feature/map/beacon.h b/plugins/feature/map/beacon.h index f4a5be696..8fb430dfe 100644 --- a/plugins/feature/map/beacon.h +++ b/plugins/feature/map/beacon.h @@ -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 *readIARUCSV(const QString &filename) { diff --git a/plugins/feature/map/icons.qrc b/plugins/feature/map/icons.qrc new file mode 100644 index 000000000..f462b389c --- /dev/null +++ b/plugins/feature/map/icons.qrc @@ -0,0 +1,5 @@ + + + icons/groundtracks.png + + diff --git a/plugins/feature/map/icons/groundtracks.png b/plugins/feature/map/icons/groundtracks.png new file mode 100644 index 0000000000000000000000000000000000000000..e75c45a8c0bc7ca6eb48745e87c00fe8b1c43cc3 GIT binary patch literal 2025 zcmbVN2~ZPP7>_WofQEM$? zTeafRidN8iQYx*eEl|Lg@hCWI2OT>V588p&sZhoWqo94^$k+~Tr|#_Td;8w^zW@Gr zYeIa~M3FY=dKjX#a(H=wTBv3eq@Ie-HEUIXfUyuRSpmGVqk7tMqp?V!ez_haGO9VRh9G(C`HVX=besOHxJ-%K~3=SkG|` zh9DM;MPLyNXmcil%49M`C_+RcK0xr7rkFq1e( zn>93T7ztFuh{}*iBtRkm<&;TFTi69J9UzrBN6KLkHIy#|cc2E9Vj>YHk*yd)sc9{x z%Nq?P2H_Qrpg^K%aSs1CoR+|JwAqLQ3n?R>Ng|9X6Nc=5!xXfEHUn`$9r4TSDup7! zOzS8E*kF^QmO!cqg%p)ZrF>x2ppt4erZTY{ZX!sPQVs)e2q;R6p#iu|D3eL}5&{+T z#ZpNiKS+v8`6MpZWd!Jifq_!d$ay7AF9|tgkWhMXTi;6cqVYv zf94nh^hRvOIb;Izck2fCUMl1fIOqO}b57OUg;r`>`XFHgpGX|44tuH}aZ&o38W zFS*xV<>fcA@M*W#i!$fBpycg`rmmU$d0NE$FPI~|!lJOXGoq?I*W70-8akD4YJF5S*2D@v+AYf1N!womqHa42=@;~V0Vy-$!KZ5N&MUSFKh zc(F5PdcAYM;N~Q_$tCp49dX~0oyBc2lb;J_o_`o{W_4qa_UZV$CAarDue`86*r`|8 z72uo|TKQnXdcp400w%J2M_h_-<2c{WZ*ObY`4GQOkr0Uu1&m)^>gL3^+~XRk$L(H` zw|qN`D(|~YI$G&>`b@1Rv*q%EP@gPK`Ofou6wkWntiKSJcy?YO)L{>pg8}QuwiL686ym=3cKW&a@ z&$Zw6?>y&(rC(d|z>kp+`1c;2d+M95Jvua19+4g^a@?IYec6ti<&7QL4abPFDSkb^ zbF4nCk2~sI_h;n0{w}~yE;jD;>`!ukw`q^t-8&_n;r;btIU7no{Bqa8qv%}kqbYB0 zX^#}FTS;WBIp+4lxm)$Mh1gKuwh^fczVhs~m9d-7y0zw2oLX?bIq0|9h1mOVT&}3E_a^9smFU literal 0 HcmV?d00001 diff --git a/plugins/feature/map/map.qrc b/plugins/feature/map/map.qrc index 93f43c7db..0310f0732 100644 --- a/plugins/feature/map/map.qrc +++ b/plugins/feature/map/map.qrc @@ -1,6 +1,7 @@ map/map.qml + map/map_5_12.qml map/antenna.png diff --git a/plugins/feature/map/map/map.qml b/plugins/feature/map/map/map.qml index ba8237983..0b0816009 100644 --- a/plugins/feature/map/map/map.qml +++ b/plugins/feature/map/map/map.qml @@ -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 + } + } + } diff --git a/plugins/feature/map/map/map_5_12.qml b/plugins/feature/map/map/map_5_12.qml new file mode 100644 index 000000000..34c97404c --- /dev/null +++ b/plugins/feature/map/map/map_5_12.qml @@ -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 + } + } + +} diff --git a/plugins/feature/map/mapgui.cpp b/plugins/feature/map/mapgui.cpp index ed5570cbb..2beea68c3 100644 --- a/plugins/feature/map/mapgui.cpp +++ b/plugins/feature/map/mapgui.cpp @@ -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 x1x2 + + 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& 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(); + 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(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 tracks({&track1, &track2}); + QList ends({&end1, &end2}); + QList 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(); + 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(); + 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 *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(); +} diff --git a/plugins/feature/map/mapgui.h b/plugins/feature/map/mapgui.h index 5b8fcfd4a..3bc618ea3 100644 --- a/plugins/feature/map/mapgui.h +++ b/plugins/feature/map/mapgui.h @@ -22,6 +22,7 @@ #include #include #include +#include #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 *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 *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 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 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& 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 roleNames() const { QHash 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 m_items; QList 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 *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(); diff --git a/plugins/feature/map/mapgui.ui b/plugins/feature/map/mapgui.ui index a6a9a9ca2..05bc51f4a 100644 --- a/plugins/feature/map/mapgui.ui +++ b/plugins/feature/map/mapgui.ui @@ -6,8 +6,8 @@ 0 0 - 462 - 689 + 481 + 750 @@ -42,7 +42,7 @@ 0 0 - 461 + 471 41 @@ -82,9 +82,15 @@ + + + 0 + 0 + + - 150 + 100 0 @@ -93,19 +99,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - @@ -120,7 +113,7 @@ - + Maidenhead locator conversion @@ -134,7 +127,7 @@ - + Display Beacon dialog @@ -168,7 +161,57 @@ - + + + + Adobe Devanagari + + + + Display ground tracks for selected item + + + ^ + + + + :/logarithmic.png:/logarithmic.png + + + true + + + true + + + + + + + + Adobe Devanagari + + + + Display all ground tracks + + + ^ + + + + :/map/icons/groundtracks.png:/map/icons/groundtracks.png + + + true + + + true + + + + + Delete all items on the map @@ -182,7 +225,7 @@ - + Show settings dialog @@ -203,9 +246,9 @@ 0 - 100 - 461 - 581 + 60 + 471 + 681 @@ -244,7 +287,7 @@ 100 - 500 + 590 @@ -293,6 +336,7 @@ + diff --git a/plugins/feature/map/mapsettings.cpp b/plugins/feature/map/mapsettings.cpp index aa45e824e..e7bd273b1 100644 --- a/plugins/feature/map/mapsettings.cpp +++ b/plugins/feature/map/mapsettings.cpp @@ -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; } diff --git a/plugins/feature/map/mapsettings.h b/plugins/feature/map/mapsettings.h index ae5436b5c..8a75937fb 100644 --- a/plugins/feature/map/mapsettings.h +++ b/plugins/feature/map/mapsettings.h @@ -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_ diff --git a/plugins/feature/map/mapsettingsdialog.cpp b/plugins/feature/map/mapsettingsdialog.cpp index 1133dd380..992e3b8b1 100644 --- a/plugins/feature/map/mapsettingsdialog.cpp +++ b/plugins/feature/map/mapsettingsdialog.cpp @@ -16,12 +16,25 @@ /////////////////////////////////////////////////////////////////////////////////// #include +#include +#include #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)); + } +} diff --git a/plugins/feature/map/mapsettingsdialog.h b/plugins/feature/map/mapsettingsdialog.h index c866cc432..d3b49a9c7 100644 --- a/plugins/feature/map/mapsettingsdialog.h +++ b/plugins/feature/map/mapsettingsdialog.h @@ -34,6 +34,8 @@ public: private slots: void accept(); + void on_groundTrackColor_clicked(); + void on_predictedGroundTrackColor_clicked(); private: Ui::MapSettingsDialog* ui; diff --git a/plugins/feature/map/mapsettingsdialog.ui b/plugins/feature/map/mapsettingsdialog.ui index da40f01e6..8ac4c5eaa 100644 --- a/plugins/feature/map/mapsettingsdialog.ui +++ b/plugins/feature/map/mapsettingsdialog.ui @@ -65,6 +65,14 @@ Checked + + + Satellite Tracker + + + Checked + + Beacons @@ -76,7 +84,50 @@ - + + + Colours + + + + + + Ground tracks (predicted) + + + + + + + Ground tracks (taken) + + + + + + + Select color for predicted ground tracks + + + + + + + + + + Select color for taken ground tracks + + + + + + + + + + + Map Provider Settings diff --git a/plugins/feature/map/readme.md b/plugins/feature/map/readme.md index 1a4974e52..86efe3203 100644 --- a/plugins/feature/map/readme.md +++ b/plugins/feature/map/readme.md @@ -3,8 +3,15 @@

Introduction

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. -

6: Delete

+

6: Display tracks for selected object

+ +When checked, displays the track (taken or predicted) for the selected object. + +

7: Display tracks for all objects

+ +When checked, displays the track (taken or predicted) for the all objects. + +

8: Delete

When clicked, all items will be deleted from the map. -

7: Display settings

+

9: Display settings

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/