Browse Source
It receives mavlink message obstacle_distance and shows distances in a circular array fashion. Added a setting in Safety/Object Detection section to show the overlay. Can be used independently to collision prevention features. There are two overlays: Video and Map overlay In different styles. The styles could be changed in particular qml file. https://github.com/mavlink/qgroundcontrol/issues/7670QGC4.4
12 changed files with 335 additions and 0 deletions
@ -0,0 +1,71 @@ |
|||||||
|
/**************************************************************************** |
||||||
|
* |
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> |
||||||
|
* |
||||||
|
* QGroundControl is licensed according to the terms in the file |
||||||
|
* COPYING.md in the root of the source code directory. |
||||||
|
* |
||||||
|
****************************************************************************/ |
||||||
|
|
||||||
|
import QtQuick 2.11 |
||||||
|
|
||||||
|
import QGroundControl 1.0 |
||||||
|
import QGroundControl.SettingsManager 1.0 |
||||||
|
|
||||||
|
Canvas { |
||||||
|
id: canvas |
||||||
|
anchors.fill: parent |
||||||
|
visible: QGroundControl.settingsManager.flyViewSettings.showObstacleDistanceOverlay.value > 0 && _activeVehicle && _activeVehicle.objectAvoidance.available |
||||||
|
|
||||||
|
property var showText: true |
||||||
|
property var interval: 200 |
||||||
|
property var _ranges: [] |
||||||
|
property var _incrementDeg: 0 |
||||||
|
property var _offsetDeg: 0 |
||||||
|
property var _heading: 0 |
||||||
|
property var _maxRadiusMeters: 0 |
||||||
|
property var _rangesLen: 0 |
||||||
|
property var _degToRangeIdx: function(deg, useHeading) { |
||||||
|
return rangeIdx(deg, _incrementDeg, _offsetDeg, _rangesLen, useHeading ? _heading : 0) |
||||||
|
} |
||||||
|
property var _rangeToShow: function(range) { |
||||||
|
const feets = QGroundControl.settingsManager.unitsSettings.horizontalDistanceUnits.value === UnitsSettings.HorizontalDistanceUnitsFeet |
||||||
|
range = feets ? range * 3.2808399 : range |
||||||
|
return range.toFixed(2) |
||||||
|
} |
||||||
|
|
||||||
|
// Converts degrees to index from ranges |
||||||
|
function rangeIdx(deg, increment, offset, len, heading) { |
||||||
|
const i = (360 - heading + deg - offset) / increment |
||||||
|
return (len + Math.ceil(i)) % len |
||||||
|
} |
||||||
|
|
||||||
|
Timer { |
||||||
|
interval: canvas.interval |
||||||
|
running: true |
||||||
|
repeat: true |
||||||
|
onTriggered: canvas.requestPaint() |
||||||
|
} |
||||||
|
|
||||||
|
onPaint: { |
||||||
|
if (!_activeVehicle) |
||||||
|
return |
||||||
|
|
||||||
|
var ctx = getContext("2d"); |
||||||
|
ctx.reset() |
||||||
|
|
||||||
|
_ranges = _activeVehicle.objectAvoidance.distances |
||||||
|
_incrementDeg = _activeVehicle.objectAvoidance.increment |
||||||
|
if (_ranges.length == 0 || _incrementDeg == 0) |
||||||
|
return |
||||||
|
|
||||||
|
_offsetDeg = _activeVehicle.objectAvoidance.angleOffset |
||||||
|
_heading = _activeVehicle.heading.value |
||||||
|
_maxRadiusMeters = _activeVehicle.objectAvoidance.maxDistance / 100 |
||||||
|
_rangesLen = 360.0 / _incrementDeg |
||||||
|
if (_rangesLen > _ranges.length) |
||||||
|
_rangesLen = _ranges.length |
||||||
|
|
||||||
|
paintObstacleOverlay(ctx) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,114 @@ |
|||||||
|
/**************************************************************************** |
||||||
|
* |
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> |
||||||
|
* |
||||||
|
* QGroundControl is licensed according to the terms in the file |
||||||
|
* COPYING.md in the root of the source code directory. |
||||||
|
* |
||||||
|
****************************************************************************/ |
||||||
|
|
||||||
|
import QtQuick 2.11 |
||||||
|
|
||||||
|
import QGroundControl 1.0 |
||||||
|
import QGroundControl.SettingsManager 1.0 |
||||||
|
|
||||||
|
Item { |
||||||
|
id: root |
||||||
|
anchors.fill: parent |
||||||
|
property var showText: obstacleDistance._showText |
||||||
|
|
||||||
|
function paintObstacleOverlay(ctx) { |
||||||
|
const vehiclePoint = _root.fromCoordinate(_activeVehicleCoordinate, false) |
||||||
|
const centerX = vehiclePoint.x |
||||||
|
const centerY = vehiclePoint.y |
||||||
|
const maxRadiusPixels = 0.9 * root.height / 2 // Max pixels to center |
||||||
|
const minRadiusPixels = maxRadiusPixels * 0.2 |
||||||
|
const metersPerPixelInCycle = (maxRadiusPixels - minRadiusPixels) / obstacleDistance._maxRadiusMeters |
||||||
|
|
||||||
|
const leftCoord = mapControl.toCoordinate(Qt.point(0, root.y), false) |
||||||
|
const rightCoord = mapControl.toCoordinate(Qt.point(100, root.y), false) |
||||||
|
const metersIn100Pixels = leftCoord.distanceTo(rightCoord) |
||||||
|
const metersPerPixel = 100.0 / metersIn100Pixels |
||||||
|
|
||||||
|
var minGradPixels = minRadiusPixels |
||||||
|
var maxGradPixels = maxRadiusPixels |
||||||
|
var metersToPixels = metersPerPixelInCycle |
||||||
|
if (metersIn100Pixels < 4) { |
||||||
|
minGradPixels = 0 |
||||||
|
maxGradPixels = obstacleDistance._maxRadiusMeters * metersPerPixel |
||||||
|
metersToPixels = metersPerPixel |
||||||
|
} |
||||||
|
|
||||||
|
var grad = ctx.createRadialGradient(centerX, centerY, minGradPixels, centerX, centerY, maxGradPixels) |
||||||
|
grad.addColorStop(0, Qt.rgba(1, 0, 0, 1)) |
||||||
|
grad.addColorStop(0.1, Qt.rgba(1, 0, 0, 0.7)) |
||||||
|
grad.addColorStop(0.5, Qt.rgba(1, 0.64, 0, 0.7)) |
||||||
|
grad.addColorStop(0.65, Qt.rgba(1, 0.64, 0, 0.3)) |
||||||
|
grad.addColorStop(0.95, Qt.rgba(0, 1, 0, 0.3)) |
||||||
|
grad.addColorStop(1, Qt.rgba(0, 1, 0, 0)) |
||||||
|
|
||||||
|
var points = [] |
||||||
|
const height = minRadiusPixels / 8 |
||||||
|
for (var i = 0; i < obstacleDistance._rangesLen; ++i) { |
||||||
|
const deg = i * obstacleDistance._incrementDeg |
||||||
|
const rad = deg * Math.PI / 180.0 |
||||||
|
const m = obstacleDistance._ranges[obstacleDistance._degToRangeIdx(deg, true)] / 100.0 |
||||||
|
const pixels = minGradPixels + m * metersToPixels |
||||||
|
const outerX = centerX + pixels * Math.cos(rad) |
||||||
|
const outerY = centerY + pixels * Math.sin(rad) |
||||||
|
const innerX = centerX + (pixels - height) * Math.cos(rad) |
||||||
|
const innerY = centerY + (pixels - height) * Math.sin(rad) |
||||||
|
|
||||||
|
points.push({'outer_x': outerX, 'outer_y': outerY, 'inner_x': innerX, 'inner_y': innerY, 'range': m}) |
||||||
|
} |
||||||
|
|
||||||
|
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.8) |
||||||
|
ctx.font = "bold 22px sans-serif" |
||||||
|
ctx.lineWidth = 2; |
||||||
|
var mPrev = -1 |
||||||
|
for (var i = 0; i < points.length; i += 3) { |
||||||
|
const i3 = (i + 3) % points.length // catch the line from the last to the first point |
||||||
|
|
||||||
|
ctx.beginPath() |
||||||
|
ctx.fillStyle = grad |
||||||
|
ctx.moveTo(points[i].inner_x, points[i].inner_y) |
||||||
|
ctx.lineTo(points[i].outer_x, points[i].outer_y) |
||||||
|
ctx.bezierCurveTo( |
||||||
|
points[i + 1].outer_x, points[i + 1].outer_y, |
||||||
|
points[i + 2].outer_x, points[i + 2].outer_y, |
||||||
|
points[i3].outer_x, points[i3].outer_y) |
||||||
|
ctx.lineTo(points[i3].inner_x, points[i3].inner_y) |
||||||
|
ctx.bezierCurveTo( |
||||||
|
points[i3].inner_x, points[i3].inner_y, |
||||||
|
points[i + 2].inner_x, points[i + 2].inner_y, |
||||||
|
points[i + 1].inner_x, points[i + 1].inner_y) |
||||||
|
ctx.fill() |
||||||
|
|
||||||
|
if (showText) { |
||||||
|
var iMin = i |
||||||
|
for (var k = iMin + 1; k < iMin + 2; ++k) { |
||||||
|
const idx = k % points.length |
||||||
|
if (points[idx].range < points[iMin].range) |
||||||
|
iMin = idx |
||||||
|
} |
||||||
|
|
||||||
|
var m = points[iMin].range |
||||||
|
if (m < obstacleDistance._maxRadiusMeters && Math.abs(m - mPrev) > 2.0) { |
||||||
|
const textX = points[iMin].inner_x |
||||||
|
const textY = points[iMin].inner_y |
||||||
|
|
||||||
|
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.9) |
||||||
|
const text = obstacleDistance._rangeToShow(m) |
||||||
|
ctx.strokeText(text, textX, textY) |
||||||
|
ctx.fillText(text, textX, textY) |
||||||
|
mPrev = m |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ObstacleDistanceOverlay { |
||||||
|
id: obstacleDistance |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,113 @@ |
|||||||
|
/**************************************************************************** |
||||||
|
* |
||||||
|
* (c) 2009-2020 QGROUNDCONTROL PROJECT <http://www.qgroundcontrol.org> |
||||||
|
* |
||||||
|
* QGroundControl is licensed according to the terms in the file |
||||||
|
* COPYING.md in the root of the source code directory. |
||||||
|
* |
||||||
|
****************************************************************************/ |
||||||
|
|
||||||
|
import QtQuick 2.11 |
||||||
|
|
||||||
|
import QGroundControl 1.0 |
||||||
|
import QGroundControl.SettingsManager 1.0 |
||||||
|
|
||||||
|
Item { |
||||||
|
id: root |
||||||
|
anchors.fill: parent |
||||||
|
property var showText: obstacleDistance._showText |
||||||
|
|
||||||
|
function drawSegment(ctx, range, centerX, centerY, lengthFrom, lengthTo, radFrom, radTo, grad) { |
||||||
|
const topSrcX = centerX + lengthFrom * Math.cos(radFrom) |
||||||
|
const topSrcY = centerY + lengthFrom * Math.sin(radFrom) |
||||||
|
const topDstX = centerX + lengthFrom * Math.cos(radTo) |
||||||
|
const topDstY = centerY + lengthFrom * Math.sin(radTo) |
||||||
|
|
||||||
|
ctx.save() |
||||||
|
ctx.setTransform(2, 0, 0, 1, -centerX, 0) |
||||||
|
ctx.beginPath() |
||||||
|
ctx.fillStyle = grad |
||||||
|
ctx.moveTo(topSrcX, topSrcY) |
||||||
|
ctx.arc(centerX, centerY, lengthFrom, radFrom, radTo, false) |
||||||
|
ctx.arc(centerX, centerY, lengthTo, radTo, radFrom, true) |
||||||
|
ctx.lineTo(topSrcX, topSrcY) |
||||||
|
ctx.fill() |
||||||
|
ctx.closePath() |
||||||
|
ctx.restore() |
||||||
|
|
||||||
|
if (range > 0) { |
||||||
|
ctx.save() |
||||||
|
ctx.setTransform(2, 0, 0, 2, -centerX, 0) |
||||||
|
ctx.beginPath() |
||||||
|
ctx.strokeStyle = Qt.rgba(0, 0, 0, 0.8) |
||||||
|
ctx.font = "bold 10px sans-serif" |
||||||
|
ctx.lineWidth = 2 |
||||||
|
ctx.fillStyle = Qt.rgba(1, 1, 1, 0.8) |
||||||
|
const text = obstacleDistance._rangeToShow(range) |
||||||
|
const textX = topSrcX + (topDstX - topSrcX) / 2 |
||||||
|
const textY = topSrcY + (topDstY - topSrcY) / 2 |
||||||
|
ctx.strokeText(text, textX, textY / 2) |
||||||
|
ctx.fillText(text, textX, textY / 2) |
||||||
|
ctx.closePath() |
||||||
|
ctx.restore() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function paintObstacleOverlay(ctx) { |
||||||
|
const centerX = root.width / 2 |
||||||
|
const centerY = root.height / 2 |
||||||
|
const maxRadiusPixels = 0.9 * root.height / 2 // Max pixels to center |
||||||
|
const minRadiusPixels = maxRadiusPixels * 0.2 // Min radius in pixels to center |
||||||
|
const minGradPixels = minRadiusPixels |
||||||
|
const maxGradPixels = maxRadiusPixels |
||||||
|
const segmentHeightPixels = minRadiusPixels / 8 |
||||||
|
const levelMeters = 10 |
||||||
|
const levelNum = obstacleDistance._maxRadiusMeters / levelMeters |
||||||
|
|
||||||
|
var grad = ctx.createRadialGradient(centerX, centerY, maxGradPixels - segmentHeightPixels * levelNum * 2, centerX, centerY, maxGradPixels) |
||||||
|
grad.addColorStop(0, Qt.rgba(1, 0, 0, 0.9)) |
||||||
|
grad.addColorStop(0.1, Qt.rgba(1, 0, 0, 0.3)) |
||||||
|
grad.addColorStop(0.5, Qt.rgba(1, 0.64, 0, 0.3)) |
||||||
|
grad.addColorStop(0.65, Qt.rgba(1, 0.64, 0, 0.2)) |
||||||
|
grad.addColorStop(0.95, Qt.rgba(0, 1, 0, 0.1)) |
||||||
|
grad.addColorStop(1, Qt.rgba(0, 1, 0, 0)) |
||||||
|
|
||||||
|
const segNum = 16 |
||||||
|
const incDeg = 360 / segNum |
||||||
|
for (var s = 0; s < segNum; ++s) { |
||||||
|
const deg = s * 360.0 / segNum |
||||||
|
const rad = deg * Math.PI / 180.0 |
||||||
|
const i = obstacleDistance._degToRangeIdx(deg, false) |
||||||
|
const iNext = obstacleDistance._degToRangeIdx(deg + incDeg, false) |
||||||
|
var rangeMin = obstacleDistance._maxRadiusMeters |
||||||
|
|
||||||
|
const end = i < iNext ? iNext : obstacleDistance._rangesLen + iNext |
||||||
|
for (var ii = i; ii < end; ++ii) { |
||||||
|
const r = obstacleDistance._ranges[ii % obstacleDistance._rangesLen] / 100 |
||||||
|
if (r < rangeMin) |
||||||
|
rangeMin = r |
||||||
|
} |
||||||
|
|
||||||
|
const lengthFrom = maxRadiusPixels |
||||||
|
const radFrom = rad |
||||||
|
const radTo = radFrom + incDeg * Math.PI / 180.0 - 0.03 |
||||||
|
var range = obstacleDistance._maxRadiusMeters |
||||||
|
for (var ii = 0; ii < levelNum; ++ii) { |
||||||
|
const from = lengthFrom - ii * segmentHeightPixels * 2 |
||||||
|
const to = from - segmentHeightPixels |
||||||
|
const rangeInLevel = obstacleDistance._maxRadiusMeters - (ii + 1) * levelMeters |
||||||
|
const isLast = ii >= levelNum - 1 |
||||||
|
if (rangeMin > rangeInLevel || isLast) |
||||||
|
range = rangeMin |
||||||
|
const rangeToShow = showText && range < obstacleDistance._maxRadiusMeters ? range : 0 |
||||||
|
drawSegment(ctx, rangeToShow, centerX, centerY, from, to, radFrom, radTo, grad) |
||||||
|
if (range == rangeMin) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
ObstacleDistanceOverlay { |
||||||
|
id: obstacleDistance |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue