You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
462 lines
16 KiB
462 lines
16 KiB
/**************************************************************************** |
|
* |
|
* (c) 2009-2016 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.3 |
|
import QtQuick.Controls 1.2 |
|
import QtLocation 5.3 |
|
import QtPositioning 5.3 |
|
|
|
import QGroundControl 1.0 |
|
import QGroundControl.ScreenTools 1.0 |
|
import QGroundControl.Palette 1.0 |
|
import QGroundControl.Controls 1.0 |
|
import QGroundControl.FlightMap 1.0 |
|
|
|
/// QGCMapPolygon map visuals |
|
Item { |
|
id: _root |
|
|
|
property var mapControl ///< Map control to place item in |
|
property var mapPolygon ///< QGCMapPolygon object |
|
property bool interactive: mapPolygon.interactive |
|
property color interiorColor: "transparent" |
|
property real interiorOpacity: 1 |
|
property int borderWidth: 0 |
|
property color borderColor: "black" |
|
|
|
property var _polygonComponent |
|
property var _dragHandlesComponent |
|
property var _splitHandlesComponent |
|
property var _centerDragHandleComponent |
|
property bool _circle: false |
|
property real _circleRadius |
|
|
|
property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap |
|
property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2 |
|
property real _zorderCenterHandle: QGroundControl.zOrderMapItems + 1 // Lowest such that drag or split takes precedence |
|
|
|
function addVisuals() { |
|
_polygonComponent = polygonComponent.createObject(mapControl) |
|
mapControl.addMapItem(_polygonComponent) |
|
} |
|
|
|
function removeVisuals() { |
|
_polygonComponent.destroy() |
|
} |
|
|
|
function addHandles() { |
|
if (!_dragHandlesComponent) { |
|
_dragHandlesComponent = dragHandlesComponent.createObject(mapControl) |
|
_splitHandlesComponent = splitHandlesComponent.createObject(mapControl) |
|
_centerDragHandleComponent = centerDragHandleComponent.createObject(mapControl) |
|
} |
|
} |
|
|
|
function removeHandles() { |
|
if (_dragHandlesComponent) { |
|
_dragHandlesComponent.destroy() |
|
_dragHandlesComponent = undefined |
|
} |
|
if (_splitHandlesComponent) { |
|
_splitHandlesComponent.destroy() |
|
_splitHandlesComponent = undefined |
|
} |
|
if (_centerDragHandleComponent) { |
|
_centerDragHandleComponent.destroy() |
|
_centerDragHandleComponent = undefined |
|
} |
|
} |
|
|
|
/// Calculate the default/initial 4 sided polygon |
|
function defaultPolygonVertices() { |
|
// Initial polygon is inset to take 2/3rds space |
|
var rect = Qt.rect(map.centerViewport.x, map.centerViewport.y, map.centerViewport.width, map.centerViewport.height) |
|
rect.x += (rect.width * 0.25) / 2 |
|
rect.y += (rect.height * 0.25) / 2 |
|
rect.width *= 0.75 |
|
rect.height *= 0.75 |
|
|
|
var centerCoord = map.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */) |
|
var topLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */) |
|
var topRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */) |
|
var bottomLeftCoord = map.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */) |
|
var bottomRightCoord = map.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */) |
|
|
|
// Initial polygon has max width and height of 3000 meters |
|
var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), 3000) / 2 |
|
var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), 3000) / 2 |
|
topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0) |
|
topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0) |
|
bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180) |
|
bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180) |
|
|
|
return [ topLeftCoord, topRightCoord, bottomRightCoord, bottomLeftCoord, centerCoord ] |
|
} |
|
|
|
/// Add an initial 4 sided polygon |
|
function addInitialPolygon() { |
|
if (mapPolygon.count < 3) { |
|
initialVertices = defaultPolygonVertices() |
|
mapPolygon.appendVertex(initialVertices[0]) |
|
mapPolygon.appendVertex(initialVertices[1]) |
|
mapPolygon.appendVertex(initialVertices[2]) |
|
mapPolygon.appendVertex(initialVertices[3]) |
|
} |
|
} |
|
|
|
/// Reset polygon back to initial default |
|
function resetPolygon() { |
|
var initialVertices = defaultPolygonVertices() |
|
mapPolygon.clear() |
|
for (var i=0; i<4; i++) { |
|
mapPolygon.appendVertex(initialVertices[i]) |
|
} |
|
_circle = false |
|
} |
|
|
|
/// Reset polygon to a circle which fits within initial polygon |
|
function setCircleRadius(center, radius) { |
|
var unboundCenter = center.atDistanceAndAzimuth(0, 0) |
|
_circleRadius = radius |
|
var segments = 16 |
|
var angleIncrement = 360 / segments |
|
var angle = 0 |
|
mapPolygon.clear() |
|
for (var i=0; i<segments; i++) { |
|
var vertex = unboundCenter.atDistanceAndAzimuth(_circleRadius, angle) |
|
mapPolygon.appendVertex(vertex) |
|
angle += angleIncrement |
|
} |
|
_circle = true |
|
} |
|
|
|
/// Reset polygon to a circle which fits within initial polygon |
|
function resetCircle() { |
|
var initialVertices = defaultPolygonVertices() |
|
var width = initialVertices[0].distanceTo(initialVertices[1]) |
|
var height = initialVertices[1].distanceTo(initialVertices[2]) |
|
var radius = Math.min(width, height) / 2 |
|
var center = initialVertices[4] |
|
setCircleRadius(center, radius) |
|
} |
|
|
|
onInteractiveChanged: { |
|
if (interactive) { |
|
addHandles() |
|
} else { |
|
removeHandles() |
|
} |
|
} |
|
|
|
Component.onCompleted: { |
|
addVisuals() |
|
if (interactive) { |
|
addHandles() |
|
} |
|
} |
|
|
|
Component.onDestruction: { |
|
removeVisuals() |
|
removeHandles() |
|
} |
|
|
|
QGCPalette { id: qgcPal } |
|
|
|
Component { |
|
id: polygonComponent |
|
|
|
MapPolygon { |
|
color: interiorColor |
|
opacity: interiorOpacity |
|
border.color: borderColor |
|
border.width: borderWidth |
|
path: mapPolygon.path |
|
} |
|
} |
|
|
|
Component { |
|
id: splitHandleComponent |
|
|
|
MapQuickItem { |
|
id: mapQuickItem |
|
anchorPoint.x: dragHandle.width / 2 |
|
anchorPoint.y: dragHandle.height / 2 |
|
visible: !_circle |
|
|
|
property int vertexIndex |
|
|
|
sourceItem: Rectangle { |
|
id: dragHandle |
|
width: ScreenTools.defaultFontPixelHeight * 1.5 |
|
height: width |
|
radius: width / 2 |
|
border.color: "white" |
|
color: "transparent" |
|
opacity: .50 |
|
z: _zorderSplitHandle |
|
|
|
QGCLabel { |
|
anchors.horizontalCenter: parent.horizontalCenter |
|
anchors.verticalCenter: parent.verticalCenter |
|
text: "+" |
|
} |
|
|
|
QGCMouseArea { |
|
fillItem: parent |
|
onClicked: mapPolygon.splitPolygonSegment(mapQuickItem.vertexIndex) |
|
} |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: splitHandlesComponent |
|
|
|
Repeater { |
|
model: mapPolygon.path |
|
|
|
delegate: Item { |
|
property var _splitHandle |
|
property var _vertices: mapPolygon.path |
|
|
|
function _setHandlePosition() { |
|
var nextIndex = index + 1 |
|
if (nextIndex > _vertices.length - 1) { |
|
nextIndex = 0 |
|
} |
|
var distance = _vertices[index].distanceTo(_vertices[nextIndex]) |
|
var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex]) |
|
_splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth) |
|
} |
|
|
|
Component.onCompleted: { |
|
_splitHandle = splitHandleComponent.createObject(mapControl) |
|
_splitHandle.vertexIndex = index |
|
_setHandlePosition() |
|
mapControl.addMapItem(_splitHandle) |
|
} |
|
|
|
Component.onDestruction: { |
|
if (_splitHandle) { |
|
_splitHandle.destroy() |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Control which is used to drag polygon vertices |
|
Component { |
|
id: dragAreaComponent |
|
|
|
MissionItemIndicatorDrag { |
|
id: dragArea |
|
z: _zorderDragHandle |
|
visible: !_circle |
|
|
|
property int polygonVertex |
|
|
|
property bool _creationComplete: false |
|
|
|
Component.onCompleted: _creationComplete = true |
|
|
|
onItemCoordinateChanged: { |
|
if (_creationComplete) { |
|
// During component creation some bad coordinate values got through which screws up draw |
|
mapPolygon.adjustVertex(polygonVertex, itemCoordinate) |
|
} |
|
} |
|
|
|
onClicked: mapPolygon.removeVertex(polygonVertex) |
|
} |
|
} |
|
|
|
Component { |
|
id: centerDragHandle |
|
|
|
MapQuickItem { |
|
id: mapQuickItem |
|
anchorPoint.x: dragHandle.width / 2 |
|
anchorPoint.y: dragHandle.height / 2 |
|
z: _zorderDragHandle |
|
|
|
sourceItem: Rectangle { |
|
id: dragHandle |
|
width: ScreenTools.defaultFontPixelHeight * 1.5 |
|
height: width |
|
radius: width / 2 |
|
color: "white" |
|
opacity: .90 |
|
|
|
QGCLabel { |
|
anchors.horizontalCenter: parent.horizontalCenter |
|
anchors.verticalCenter: parent.verticalCenter |
|
text: "..." |
|
color: "black" |
|
} |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: dragHandleComponent |
|
|
|
MapQuickItem { |
|
id: mapQuickItem |
|
anchorPoint.x: dragHandle.width / 2 |
|
anchorPoint.y: dragHandle.height / 2 |
|
z: _zorderDragHandle |
|
visible: !_circle |
|
|
|
property int polygonVertex |
|
|
|
sourceItem: Rectangle { |
|
id: dragHandle |
|
width: ScreenTools.defaultFontPixelHeight * 1.5 |
|
height: width |
|
radius: width / 2 |
|
color: "white" |
|
opacity: .90 |
|
} |
|
} |
|
} |
|
|
|
// Add all polygon vertex drag handles to the map |
|
Component { |
|
id: dragHandlesComponent |
|
|
|
Repeater { |
|
model: mapPolygon.pathModel |
|
|
|
delegate: Item { |
|
property var _visuals: [ ] |
|
|
|
Component.onCompleted: { |
|
var dragHandle = dragHandleComponent.createObject(mapControl) |
|
dragHandle.coordinate = Qt.binding(function() { return object.coordinate }) |
|
dragHandle.polygonVertex = Qt.binding(function() { return index }) |
|
mapControl.addMapItem(dragHandle) |
|
var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate }) |
|
dragArea.polygonVertex = Qt.binding(function() { return index }) |
|
_visuals.push(dragHandle) |
|
_visuals.push(dragArea) |
|
} |
|
|
|
Component.onDestruction: { |
|
for (var i=0; i<_visuals.length; i++) { |
|
_visuals[i].destroy() |
|
} |
|
_visuals = [ ] |
|
} |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: centerDragAreaComponent |
|
|
|
MissionItemIndicatorDrag { |
|
z: _zorderCenterHandle |
|
onItemCoordinateChanged: mapPolygon.center = itemCoordinate |
|
onDragStart: mapPolygon.centerDrag = true |
|
onDragStop: mapPolygon.centerDrag = false |
|
onClicked: menu.popup() |
|
|
|
function setRadiusFromDialog() { |
|
setCircleRadius(mapPolygon.center, radiusField.text) |
|
radiusDialog.visible = false |
|
} |
|
|
|
Menu { |
|
id: menu |
|
|
|
MenuItem { |
|
text: qsTr("Circle" ) |
|
onTriggered: resetCircle() |
|
} |
|
|
|
MenuItem { |
|
text: qsTr("Polygon") |
|
onTriggered: resetPolygon() |
|
} |
|
|
|
MenuItem { |
|
text: qsTr("Set radius..." ) |
|
enabled: _circle |
|
onTriggered: radiusDialog.visible = true |
|
} |
|
|
|
MenuItem { |
|
text: qsTr("Load KML...") |
|
enabled: false |
|
} |
|
} |
|
|
|
Rectangle { |
|
id: radiusDialog |
|
anchors.margins: _margin |
|
anchors.left: parent.right |
|
width: radiusColumn.width + (_margin *2) |
|
height: radiusColumn.height + (_margin *2) |
|
color: qgcPal.window |
|
border.color: qgcPal.text |
|
visible: false |
|
|
|
Column { |
|
id: radiusColumn |
|
anchors.margins: _margin |
|
anchors.left: parent.left |
|
anchors.top: parent.top |
|
spacing: _margin |
|
|
|
QGCLabel { text: qsTr("Radius:") } |
|
|
|
QGCTextField { |
|
id: radiusField |
|
text: _circleRadius.toFixed(2) |
|
onEditingFinished: setRadiusFromDialog() |
|
} |
|
} |
|
|
|
QGCLabel { |
|
anchors.right: radiusColumn.right |
|
anchors.top: radiusColumn.top |
|
text: "X" |
|
|
|
QGCMouseArea { |
|
fillItem: parent |
|
onClicked: setRadiusFromDialog() |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
Component { |
|
id: centerDragHandleComponent |
|
|
|
Item { |
|
property var dragHandle |
|
property var dragArea |
|
|
|
Component.onCompleted: { |
|
dragHandle = centerDragHandle.createObject(mapControl) |
|
dragHandle.coordinate = Qt.binding(function() { return mapPolygon.center }) |
|
mapControl.addMapItem(dragHandle) |
|
dragArea = centerDragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": mapPolygon.center }) |
|
} |
|
|
|
Component.onDestruction: { |
|
dragHandle.destroy() |
|
dragArea.destroy() |
|
} |
|
} |
|
} |
|
} |
|
|
|
|