ADS-B: Add QT 6 support for map.

This commit is contained in:
srcejon 2024-02-28 12:34:57 +00:00
parent 0d333a910e
commit 7238b48e22
4 changed files with 721 additions and 1 deletions

View File

@ -4875,8 +4875,11 @@ ADSBDemodGUI::ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseb
ui->map->rootContext()->setContextProperty("airportModel", &m_airportModel);
ui->map->rootContext()->setContextProperty("airspaceModel", &m_airspaceModel);
ui->map->rootContext()->setContextProperty("navAidModel", &m_navAidModel);
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map.qml")));
#else
ui->map->setSource(QUrl(QStringLiteral("qrc:/map/map_6.qml")));
#endif
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_adsbDemod = reinterpret_cast<ADSBDemod*>(rxChannel); //new ADSBDemod(m_deviceUISet->m_deviceSourceAPI);

View File

@ -1,6 +1,8 @@
<RCC>
<qresource prefix="/">
<file>map/map.qml</file>
<file>map/map_6.qml</file>
<file>map/ModifiedMapView.qml</file>
<file>map/MapStation.qml</file>
<file>map/aircraft_2engine.png</file>
<file>map/aircraft_2enginesmall.png</file>

View File

@ -0,0 +1,178 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import QtQuick
import QtLocation as QL
import QtPositioning as QP
import Qt.labs.animation
/*!
\qmltype MapView
\inqmlmodule QtLocation
\brief An interactive map viewer component.
MapView wraps a Map and adds the typical interactive features:
changing the zoom level, panning and tilting the map.
The implementation is a QML assembly of smaller building blocks that are
available separately. In case you want to make changes in your own version
of this component, you can copy the QML, which is installed into the
\c qml/QtLocation module directory, and modify it as needed.
\sa Map
*/
Item {
/*!
\qmlproperty Map MapView::map
This property provides access to the underlying Map instance.
*/
property alias map: map
/*!
\qmlproperty real minimumZoomLevel
The minimum zoom level according to the size of the view.
\sa Map::minimumZoomLevel
*/
property real minimumZoomLevel: map.minimumZoomLevel
/*!
\qmlproperty real maximumZoomLevel
The maximum valid zoom level for the map.
\sa Map::maximumZoomLevel
*/
property real maximumZoomLevel: map.maximumZoomLevel
// --------------------------------
// implementation
id: root
Component.onCompleted: map.resetPinchMinMax()
QL.Map {
id: map
width: parent.width
height: parent.height
tilt: tiltHandler.persistentTranslation.y / -5
property bool pinchAdjustingZoom: false
BoundaryRule on zoomLevel {
id: br
minimum: map.minimumZoomLevel
maximum: map.maximumZoomLevel
}
onZoomLevelChanged: {
br.returnToBounds();
if (!pinchAdjustingZoom) resetPinchMinMax()
}
function resetPinchMinMax() {
pinch.persistentScale = 1
pinch.scaleAxis.minimum = Math.pow(2, root.minimumZoomLevel - map.zoomLevel + 1)
pinch.scaleAxis.maximum = Math.pow(2, root.maximumZoomLevel - map.zoomLevel - 1)
}
PinchHandler {
id: pinch
target: null
property real rawBearing: 0
property QP.geoCoordinate startCentroid
onActiveChanged: if (active) {
flickAnimation.stop()
pinch.startCentroid = map.toCoordinate(pinch.centroid.position, false)
} else {
flickAnimation.restart(centroid.velocity)
map.resetPinchMinMax()
}
onScaleChanged: (delta) => {
map.pinchAdjustingZoom = true
map.zoomLevel += Math.log2(delta)
map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
map.pinchAdjustingZoom = false
}
onRotationChanged: (delta) => {
pinch.rawBearing -= delta
// snap to 0° if we're close enough
map.bearing = (Math.abs(pinch.rawBearing) < 5) ? 0 : pinch.rawBearing
map.alignCoordinateToPoint(pinch.startCentroid, pinch.centroid.position)
}
grabPermissions: PointerHandler.TakeOverForbidden
}
WheelHandler {
id: wheel
// workaround for QTBUG-87646 / QTBUG-112394 / QTBUG-112432:
// Magic Mouse pretends to be a trackpad but doesn't work with PinchHandler
// and we don't yet distinguish mice and trackpads on Wayland either
acceptedDevices: Qt.platform.pluginName === "cocoa" || Qt.platform.pluginName === "wayland"
? PointerDevice.Mouse | PointerDevice.TouchPad
: PointerDevice.Mouse
onWheel: (event) => {
const loc = map.toCoordinate(wheel.point.position)
switch (event.modifiers) {
case Qt.NoModifier:
// jonb - Changed to make more like Qt5
//map.zoomLevel += event.angleDelta.y / 120
map.zoomLevel += event.angleDelta.y / 1000
break
case Qt.ShiftModifier:
map.bearing += event.angleDelta.y / 15
break
case Qt.ControlModifier:
map.tilt += event.angleDelta.y / 15
break
}
map.alignCoordinateToPoint(loc, wheel.point.position)
}
}
DragHandler {
id: drag
signal flickStarted // for autotests only
signal flickEnded
target: null
onTranslationChanged: (delta) => map.pan(-delta.x, -delta.y)
onActiveChanged: if (active) {
flickAnimation.stop()
} else {
flickAnimation.restart(centroid.velocity)
}
}
property vector3d animDest
onAnimDestChanged: if (flickAnimation.running) {
const delta = Qt.vector2d(animDest.x - flickAnimation.animDestLast.x, animDest.y - flickAnimation.animDestLast.y)
map.pan(-delta.x, -delta.y)
flickAnimation.animDestLast = animDest
}
Vector3dAnimation on animDest {
id: flickAnimation
property vector3d animDestLast
from: Qt.vector3d(0, 0, 0)
duration: 500
easing.type: Easing.OutQuad
onStarted: drag.flickStarted()
onStopped: drag.flickEnded()
function restart(vel) {
stop()
map.animDest = Qt.vector3d(0, 0, 0)
animDestLast = Qt.vector3d(0, 0, 0)
to = Qt.vector3d(vel.x / duration * 100, vel.y / duration * 100, 0)
start()
}
}
DragHandler {
id: tiltHandler
minimumPointCount: 2
maximumPointCount: 2
target: null
xAxis.enabled: false
grabPermissions: PointerHandler.TakeOverForbidden
onActiveChanged: if (active) flickAnimation.stop()
}
}
}

View File

@ -0,0 +1,537 @@
import QtQuick 2.14
import QtQuick.Window 2.14
import QtQuick.Controls 2.14
import QtPositioning 6.5
import QtLocation 6.5
import Qt5Compat.GraphicalEffects
Item {
id: qmlMap
property int aircraftZoomLevel: 11
property int aircraftMinZoomLevel: 11
property int airportZoomLevel: 11
property string mapProvider: "osm"
property variant mapPtr
property string requestedMapType
property bool lightIcons
property variant guiPtr
property bool smoothing
function createMap(pluginParameters, requestedMap, gui) {
requestedMapType = requestedMap
guiPtr = gui
var paramString = ""
for (var prop in pluginParameters) {
var parameter = 'PluginParameter { name: "' + prop + '"; value: "' + pluginParameters[prop] + '"}'
paramString = paramString + parameter
}
var pluginString = 'import QtLocation 6.5; Plugin{ name:"' + mapProvider + '"; ' + paramString + '}'
var plugin = Qt.createQmlObject (pluginString, qmlMap)
if (mapPtr) {
// Objects aren't destroyed immediately, so don't call findChild("map")
mapPtr.destroy()
mapPtr = null
}
mapPtr = actualMapComponent.createObject(page)
mapPtr.map.plugin = plugin
mapPtr.map.forceActiveFocus()
return mapPtr
}
Item {
id: page
anchors.fill: parent
}
Component {
id: actualMapComponent
ModifiedMapView {
id: mapView
objectName: "mapView"
anchors.fill: parent
map.center: QtPositioning.coordinate(51.5, 0.125) // London
map.zoomLevel: 10
map.objectName: "map"
// not in 6
//gesture.enabled: true
//gesture.acceptedGestures: MapGestureArea.PinchGesture | MapGestureArea.PanGesture
MouseArea {
anchors.fill: parent
propagateComposedEvents: true
onClicked: {
// Unhighlight current aircraft
guiPtr.clearHighlighted()
mouse.accepted = false
}
}
MapStation {
id: station
objectName: "station"
stationName: "Home"
}
MapItemView {
model: airspaceModel
delegate: airspaceComponent
parent: mapView.map
}
MapItemView {
model: navAidModel
delegate: navAidComponent
parent: mapView.map
}
MapItemView {
model: airspaceModel
delegate: airspaceNameComponent
parent: mapView.map
}
MapItemView {
model: airportModel
delegate: airportComponent
parent: mapView.map
}
// This needs to be before aircraftComponent MapItemView, so it's drawn underneath
MapItemView {
model: aircraftModel
delegate: aircraftPathComponent
parent: mapView.map
}
MapItemView {
model: aircraftModel
delegate: aircraftComponent
parent: mapView.map
}
map.onZoomLevelChanged: {
if (map.zoomLevel > aircraftMinZoomLevel) {
aircraftZoomLevel = map.zoomLevel
} else {
aircraftZoomLevel = aircraftMinZoomLevel
}
if (map.zoomLevel > 11) {
station.zoomLevel = map.zoomLevel
airportZoomLevel = map.zoomLevel
} else {
station.zoomLevel = 11
airportZoomLevel = 11
}
}
map.onSupportedMapTypesChanged : {
for (var i = 0; i < map.supportedMapTypes.length; i++) {
if (requestedMapType == map.supportedMapTypes[i].name) {
map.activeMapType = map.supportedMapTypes[i]
}
}
lightIcons = (requestedMapType == "Night Transit Map") || (requestedMapType == "mapbox://styles/mapbox/dark-v9")
}
}
}
Component {
id: navAidComponent
MapQuickItem {
id: navAid
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: position
zoomLevel: airportZoomLevel
sourceItem: Grid {
columns: 1
Grid {
horizontalItemAlignment: Grid.AlignHCenter
columnSpacing: 5
layer.enabled: smoothing
layer.smooth: smoothing
Image {
id: image
source: navAidImage
visible: !lightIcons
MouseArea {
anchors.fill: parent
onClicked: (mouse) => {
selected = !selected
}
}
}
ColorOverlay {
cached: true
width: image.width
height: image.height
source: image
color: "#c0ffffff"
visible: lightIcons
}
Rectangle {
id: bubble
color: bubbleColour
border.width: 1
width: text.width + 5
height: text.height + 5
radius: 5
Text {
id: text
anchors.centerIn: parent
text: navAidData
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onClicked: (mouse) => {
selected = !selected
}
}
}
}
}
}
}
Component {
id: airspaceComponent
MapPolygon {
border.width: 1
border.color: airspaceBorderColor
color: airspaceFillColor
path: airspacePolygon
}
}
Component {
id: airspaceNameComponent
MapQuickItem {
coordinate: position
anchorPoint.x: airspaceText.width/2
anchorPoint.y: airspaceText.height/2
zoomLevel: airportZoomLevel
sourceItem: Grid {
columns: 1
Grid {
layer.enabled: smoothing
layer.smooth: smoothing
horizontalItemAlignment: Grid.AlignHCenter
Text {
id: airspaceText
text: details
}
}
}
}
}
Component {
id: aircraftPathComponent
MapPolyline {
line.width: 2
line.color: 'gray'
path: aircraftPath
}
}
Component {
id: aircraftComponent
MapQuickItem {
id: aircraft
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: position
zoomLevel: aircraftZoomLevel
sourceItem: Grid {
columns: 1
Grid {
layer.enabled: smoothing
layer.smooth: smoothing
horizontalItemAlignment: Grid.AlignHCenter
Image {
id: image
rotation: heading
source: aircraftImage
visible: !lightIcons
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
highlighted = true
console.log("z=" + aircraft.sourceItem.z)
aircraft.sourceItem.z = aircraft.sourceItem.z + 1
} else if (mouse.button === Qt.RightButton) {
contextMenu.popup()
}
}
onDoubleClicked: {
target = true
}
}
}
ColorOverlay {
cached: true
width: image.width
height: image.height
rotation: heading
source: image
color: "#c0ffffff"
visible: lightIcons
MouseArea {
anchors.fill: parent
onClicked: {
highlighted = true
}
onDoubleClicked: {
target = true
}
}
}
Rectangle {
id: bubble
color: bubbleColour
border.width: 1
width: text.width * 1.1
height: text.height * 1.1
radius: 5
Text {
id: text
anchors.centerIn: parent
text: adsbData
textFormat: TextEdit.RichText
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
showAll = !showAll
} else if (mouse.button === Qt.RightButton) {
contextMenu.popup()
}
}
Menu {
id: contextMenu
MenuItem {
text: "Set as target"
onTriggered: target = true
}
MenuItem {
text: "Find on feature map"
onTriggered: aircraftModel.findOnMap(index)
}
}
}
}
}
}
}
}
Component {
id: airportComponent
MapItemGroup {
MapItemGroup {
property var groupVisible: false
id: rangeGroup
MapCircle {
id: circle5nm
center: position
color: "transparent"
border.color: "gray"
radius: 9260 // 5nm in metres
visible: rangeGroup.groupVisible
}
MapCircle {
id: circle10nm
center: position
color: "transparent"
border.color: "gray"
radius: 18520
visible: rangeGroup.groupVisible
}
MapCircle {
id: circle15nm
center: airport.coordinate
color: "transparent"
border.color: "gray"
radius: 27780
visible: rangeGroup.groupVisible
}
MapQuickItem {
id: text5nm
coordinate {
latitude: position.latitude
longitude: position.longitude + (5/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180)
}
anchorPoint.x: 0
anchorPoint.y: height/2
sourceItem: Text {
color: "grey"
text: "5nm"
}
visible: rangeGroup.groupVisible
}
MapQuickItem {
id: text10nm
coordinate {
latitude: position.latitude
longitude: position.longitude + (10/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180)
}
anchorPoint.x: 0
anchorPoint.y: height/2
sourceItem: Text {
color: "grey"
text: "10nm"
}
visible: rangeGroup.groupVisible
}
MapQuickItem {
id: text15nm
coordinate {
latitude: position.latitude
longitude: position.longitude + (15/60)/Math.cos(Math.abs(position.latitude)*Math.PI/180)
}
anchorPoint.x: 0
anchorPoint.y: height/2
sourceItem: Text {
color: "grey"
text: "15nm"
}
visible: rangeGroup.groupVisible
}
}
MapQuickItem {
id: airport
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: position
zoomLevel: airportZoomLevel
sourceItem: Grid {
columns: 1
Grid {
horizontalItemAlignment: Grid.AlignHCenter
layer.enabled: smoothing
layer.smooth: smoothing
Image {
id: image
source: airportImage
visible: !lightIcons
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
console.log("AIRPORT CLICKED ************************* ");
if (mouse.button === Qt.RightButton) {
showRangeItem.visible = !rangeGroup.groupVisible
hideRangeItem.visible = rangeGroup.groupVisible
menuItems.clear()
var scanners = airportModel.getFreqScanners()
for (var i = 0; i < scanners.length; i++) {
menuItems.append({
text: "Send to Frequency Scanner " + scanners[i],
airport: index,
scanner: scanners[i]
})
}
contextMenu.popup()
}
}
onDoubleClicked: (mouse) => {
rangeGroup.groupVisible = !rangeGroup.groupVisible
}
ListModel {
id: menuItems
}
Menu {
id: contextMenu
MenuItem {
id: showRangeItem
text: "Show range rings"
onTriggered: rangeGroup.groupVisible = true
height: visible ? implicitHeight : 0
}
MenuItem {
id: hideRangeItem
text: "Hide range rings"
onTriggered: rangeGroup.groupVisible = false
height: visible ? implicitHeight : 0
}
Instantiator {
model: menuItems
MenuItem {
text: model.text
onTriggered: airportModel.sendToFreqScanner(model.airport, model.scanner)
}
onObjectAdded: function(index, object) {
contextMenu.insertItem(index, object)
}
onObjectRemoved: function(index, object) {
contextMenu.removeItem(object)
}
}
}
}
}
ColorOverlay {
cached: true
width: image.width
height: image.height
source: image
color: "#c0ffffff"
visible: lightIcons
}
Rectangle {
id: bubble
color: bubbleColour
border.width: 1
width: text.width + 5
height: text.height + 5
radius: 5
Text {
id: text
anchors.centerIn: parent
text: airportData
}
MouseArea {
anchors.fill: parent
onClicked: (mouse) => {
console.log("AIRPORT 2 CLICKED ************************* ");
if (showFreq) {
var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
if (freqIdx == 0) {
showFreq = false
}
} else {
showFreq = true
}
}
onDoubleClicked: (mouse) => {
if (showFreq) {
var freqIdx = Math.floor((mouse.y-5)/((height-10)/airportDataRows))
if (freqIdx != 0) {
selectedFreq = freqIdx - 1
}
}
}
}
}
}
}
}
}
}
}