1/****************************************************************************
3 * (c) 2009-2024 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org>
5 * QGroundControl is licensed according to the terms in the file
6 * COPYING.md in the root of the source code directory.
8 ****************************************************************************/
15import QGroundControl.Controls
16import QGroundControl.FlyView
17import QGroundControl.GeoMap
19/// Proximity sensor sector arcs around a vehicle: GeoMap-engine port of
20/// ProximityRadarMapView. The meters-to-pixels ratio is sampled through the
21/// camera pick ray at the vehicle's screen position instead of QtLocation's
22/// map-wide zoom-level sampling.
27 property double heading: vehicle ? vehicle.heading.value : Number.NaN
29 coordinate: vehicle ? vehicle.coordinate : QtPositioning.coordinate()
30 visible: proximityValues.telemetryAvailable
31 width: detectionLimitCircle.width
32 height: detectionLimitCircle.height
33 anchorPoint: Qt.point(width / 2, height / 2)
35 property real _ratio: 1
37 // Density-independent stroke width for the sensor arcs and limit circle
38 readonly property real _strokeWidth: ScreenTools.defaultFontPixelHeight / 4
40 // Each of the 8 sensor sectors covers 45 degrees, centered on its rotation direction.
41 // Sector 0 is vehicle-forward, which is -90 degrees in PathAngleArc coordinates.
42 readonly property real _sectorSweepAngle: 360 / 8
43 readonly property real _firstSectorStartAngle: -90 - (_sectorSweepAngle / 2)
45 readonly property var _camera: scene ? scene.camera : null
47 // Cap the rendered circle size to keep item geometry sane when zoomed way in. The clamp only
48 // engages once the circle's rim is far outside the viewport, so the clamped (wrong-sized)
49 // portion is offscreen and never visible.
50 readonly property real _maximumDiameter: _camera ? Math.max(_camera.viewportSize.width, _camera.viewportSize.height) * 4 : 4096
57 // Ground meters per pixel at this item's own screen position: two pick
58 // rays 100 px apart (stays accurate under 3D camera tilt, where the
59 // scale varies across the viewport)
60 const samplePixelLength = 100
61 const screenPos = Qt.point(root.x + root.anchorPoint.x, root.y + root.anchorPoint.y)
62 const leftCoord = root._camera.coordinateAtScreenPoint(screenPos)
63 const rightCoord = root._camera.coordinateAtScreenPoint(Qt.point(screenPos.x + samplePixelLength, screenPos.y))
64 if (!leftCoord.isValid || !rightCoord.isValid) {
68 const sampleMeters = leftCoord.distanceTo(rightCoord)
69 if (!isFinite(sampleMeters) || sampleMeters <= 0) {
73 root._ratio = samplePixelLength / sampleMeters
76 function _clampedDiameter(requestedDiameter) {
77 if (!isFinite(requestedDiameter) || requestedDiameter < 0) {
80 return Math.min(requestedDiameter, _maximumDiameter)
83 function _sectorRadius(sectorIndex) {
84 const sectorDistance = proximityValues.rgRotationValues[sectorIndex]
85 // Clamp for the same reason as _clampedDiameter: keep geometry coordinates sane at deep zoom
86 return isNaN(sectorDistance) ? 0 : _clampedDiameter(sectorDistance * _ratio * 2) / 2
89 function _sectorColor(sectorIndex) {
90 return isNaN(proximityValues.rgRotationValues[sectorIndex]) ? Qt.rgba(0, 0, 0, 0) : Qt.rgba(1, 0, 0, 1)
93 function _sectorStartAngle(sectorIndex) {
94 return _firstSectorStartAngle + (sectorIndex * _sectorSweepAngle)
97 ProximityRadarValues {
104 function onDistanceChanged() { scaleTimer.restart() }
105 function onCenterChanged() { scaleTimer.restart() }
106 function onTiltChanged() { scaleTimer.restart() }
107 function onHeadingChanged() { scaleTimer.restart() }
108 function onViewportSizeChanged() { scaleTimer.restart() }
111 // Under 3D tilt the scale varies across the viewport, so vehicle motion
112 // alone can invalidate the ratio even with a stationary camera
115 function onCoordinateChanged() { scaleTimer.restart() }
123 onTriggered: root.calcSize()
128 width: detectionLimitCircle.width
129 height: detectionLimitCircle.height
130 opacity: 0.5 * root.contentOpacity2D
132 Component.onCompleted: root.calcSize()
134 // Sensor arcs are drawn with Shape rather than Canvas since Shape renders as scene graph
135 // geometry and doesn't require a backing store allocation which scales with item size.
136 // Each 45 degree sector is centered on its rotation direction: sector 0 is vehicle-forward.
139 anchors.fill: detectionLimitCircle
141 transform: Rotation {
142 origin.x: detectionLimitCircle.width / 2
143 origin.y: detectionLimitCircle.height / 2
144 // Camera heading rotates map north on screen; the sectors follow
145 angle: (isNaN(root.heading) ? 0 : root.heading) + (root._camera ? root._camera.heading : 0)
148 // ShapePath is not an Item so a Repeater can't be used; an Instantiator which appends
149 // to the Shape's data list creates the equivalent of one ShapePath per sensor sector.
153 delegate: ShapePath {
154 required property int index
156 strokeColor: root._sectorColor(index)
157 strokeWidth: root._strokeWidth
158 fillColor: "transparent"
161 centerX: vehicleSensors.width / 2
162 centerY: vehicleSensors.height / 2
163 radiusX: root._sectorRadius(index)
165 startAngle: root._sectorStartAngle(index)
166 sweepAngle: root._sectorSweepAngle
170 onObjectAdded: (index, object) => vehicleSensors.data.push(object)
175 id: detectionLimitCircle
176 width: root._clampedDiameter(proximityValues.maxDistance * 2 * root._ratio)
178 color: Qt.rgba(1, 1, 1, 0)
179 border.color: Qt.rgba(1, 1, 1, 1)
180 border.width: root._strokeWidth