Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qgroundcontrol
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Valentin Platzgummer
qgroundcontrol
Commits
887e51f1
Commit
887e51f1
authored
Aug 30, 2019
by
Valentin Platzgummer
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
hello
parent
56d4d813
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
168 additions
and
59 deletions
+168
-59
ToDo.txt
ToDo.txt
+2
-1
qgroundcontrol.qrc
qgroundcontrol.qrc
+1
-0
CircularSurveyItemEditor.qml
src/PlanView/CircularSurveyItemEditor.qml
+4
-4
QGroundControl.Controls.qmldir
src/QmlControls/QGroundControl.Controls.qmldir
+1
-0
CircularSurvey.SettingsGroup.json
src/Wima/CircularSurvey.SettingsGroup.json
+2
-2
CircularSurveyComplexItem.cc
src/Wima/CircularSurveyComplexItem.cc
+37
-34
OptimisationTools.h
src/Wima/OptimisationTools.h
+6
-3
PolygonCalculus.cc
src/Wima/PolygonCalculus.cc
+7
-11
PolygonCalculus.h
src/Wima/PolygonCalculus.h
+1
-1
WimaPlaner.cc
src/Wima/WimaPlaner.cc
+6
-3
WimaJoinedAreaMapVisual.qml
src/WimaView/WimaJoinedAreaMapVisual.qml
+101
-0
No files found.
ToDo.txt
View file @
887e51f1
optimize circular survey
remove Reference artefacts
solve Dijkstra issue (no path found, for some geometries)
solve Dijkstra issue (path not found, or not minimal)
qgroundcontrol.qrc
View file @
887e51f1
...
...
@@ -225,6 +225,7 @@
<file alias="QGroundControl/Controls/DragCoordinate.qml">src/WimaView/DragCoordinate.qml</file>
<file alias="QGroundControl/Controls/CoordinateIndicatorDrag.qml">src/WimaView/CoordinateIndicatorDrag.qml</file>
<file alias="QGroundControl/Controls/CoordinateIndicator.qml">src/WimaView/CoordinateIndicator.qml</file>
<file alias="QGroundControl/Controls/WimaJoinedAreaMapVisual.qml">src/WimaView/WimaJoinedAreaMapVisual.qml</file>
</qresource>
<qresource prefix="/json">
<file alias="APMMavlinkStreamRate.SettingsGroup.json">src/Settings/APMMavlinkStreamRate.SettingsGroup.json</file>
...
...
src/PlanView/CircularSurveyItemEditor.qml
View file @
887e51f1
...
...
@@ -81,15 +81,15 @@ Rectangle {
/*QGCSlider {
id: rSlider
minimumValue: 0.
1
maximumValue:
5
minimumValue: 0.
3
maximumValue:
20
stepSize: 0.1
tickmarksEnabled: false
Layout.fillWidth: true
Layout.columnSpan: 2
Layout.preferredHeight: ScreenTools.defaultFontPixelHeight * 1.5
onValueChanged: missionItem.deltaR.value = value
Component.onCompleted: value = missionItem.deltaR.
v
alue
Component.onCompleted: value = missionItem.deltaR.
defaultV
alue
updateValueWhileDragging: true
}*/
...
...
@@ -110,7 +110,7 @@ Rectangle {
Layout.columnSpan
:
2
Layout.preferredHeight
:
ScreenTools
.
defaultFontPixelHeight
*
1.5
onValueChanged
:
missionItem
.
deltaAlpha
.
value
=
value
Component.onCompleted
:
value
=
missionItem
.
deltaAlpha
.
v
alue
Component.onCompleted
:
value
=
missionItem
.
deltaAlpha
.
defaultV
alue
updateValueWhileDragging
:
true
}
}
...
...
src/QmlControls/QGroundControl.Controls.qmldir
View file @
887e51f1
...
...
@@ -87,6 +87,7 @@ FlyAreaItemEditor 1.0 FlyAreaItemEditor.qml
WimaMapVisual 1.0 WimaMapVisual.qml
WimaMeasurementAreaMapVisual 1.0 WimaMeasurementAreaMapVisual.qml
WimaJoinedAreaMapVisual 1.0 WimaJoinedAreaMapVisual.qml
WimaMeasurementAreaEditor 1.0 WimaMeasurementAreaEditor.qml
WimaServiceAreaMapVisual 1.0 WimaServiceAreaMapVisual.qml
WimaAreaMapVisual 1.0 WimaAreaMapVisual.qml
...
...
src/Wima/CircularSurvey.SettingsGroup.json
View file @
887e51f1
...
...
@@ -6,7 +6,7 @@
"units"
:
"m"
,
"min"
:
0.3
,
"decimalPlaces"
:
1
,
"defaultValue"
:
3
"defaultValue"
:
20
},
{
"name"
:
"DeltaAlpha"
,
...
...
@@ -15,6 +15,6 @@
"units"
:
"Deg"
,
"min"
:
0.3
,
"decimalPlaces"
:
1
,
"defaultValue"
:
1
"defaultValue"
:
5
}
]
src/Wima/CircularSurveyComplexItem.cc
View file @
887e51f1
...
...
@@ -111,8 +111,10 @@ void CircularSurveyComplexItem::_rebuildTransectsPhase1()
return
;
double
dalpha
=
_deltaAlpha
.
rawValue
().
toDouble
()
/
180
*
M_PI
;
// radiants
double
dalpha
=
_deltaAlpha
.
rawValue
().
toDouble
()
/
180
.0
*
M_PI
;
// radiants
double
dr
=
_deltaR
.
rawValue
().
toDouble
();
// meter
// double dalpha = 1.0/180.0*M_PI; // radiants
// double dr = 10.0; // meter
double
r_min
=
dr
;
// meter
double
r_max
=
(
*
std
::
max_element
(
distances
.
begin
(),
distances
.
end
()));
// meter
...
...
@@ -230,42 +232,43 @@ void CircularSurveyComplexItem::_rebuildTransectsPhase1()
}
}
// optimize path to lawn pattern
if
(
fullPath
.
size
()
==
0
)
return
;
QList
<
QPointF
>
currentSection
=
fullPath
.
takeFirst
();
QList
<
QList
<
QPointF
>>
optiPath
;
// optimized path
while
(
!
fullPath
.
empty
()
)
{
optiPath
.
append
(
currentSection
);
QPointF
endVertex
=
currentSection
.
last
();
double
minDist
=
std
::
numeric_limits
<
double
>::
infinity
();
int
index
=
0
;
bool
reversePath
=
false
;
// iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection
for
(
int
i
=
0
;
i
<
fullPath
.
size
();
i
++
)
{
auto
iteratorPath
=
fullPath
[
i
];
double
dist
=
PlanimetryCalculus
::
distance
(
endVertex
,
iteratorPath
.
first
());
if
(
dist
<
minDist
)
{
minDist
=
dist
;
index
=
i
;
}
dist
=
PlanimetryCalculus
::
distance
(
endVertex
,
iteratorPath
.
last
());
if
(
dist
<
minDist
)
{
minDist
=
dist
;
index
=
i
;
reversePath
=
true
;
}
}
currentSection
=
fullPath
.
takeAt
(
index
);
if
(
reversePath
)
{
PolygonCalculus
::
reversePath
(
currentSection
);
}
}
//
// optimize path to lawn pattern
//
if (fullPath.size() == 0)
//
return;
//
QList<QPointF> currentSection = fullPath.takeFirst();
//
QList<QList<QPointF>> optiPath; // optimized path
//
while( !fullPath.empty() ) {
//
optiPath.append(currentSection);
//
QPointF endVertex = currentSection.last();
//
double minDist = std::numeric_limits<double>::infinity();
//
int index = 0;
//
bool reversePath = false;
//
// iterate over all paths in fullPath and assign the one with the shortest distance to endVertex to currentSection
//
for (int i = 0; i < fullPath.size(); i++) {
//
auto iteratorPath = fullPath[i];
//
double dist = PlanimetryCalculus::distance(endVertex, iteratorPath.first());
//
if ( dist < minDist ) {
//
minDist = dist;
//
index = i;
//
}
//
dist = PlanimetryCalculus::distance(endVertex, iteratorPath.last());
//
if (dist < minDist) {
//
minDist = dist;
//
index = i;
//
reversePath = true;
//
}
//
}
//
currentSection = fullPath.takeAt(index);
//
if (reversePath) {
//
PolygonCalculus::reversePath(currentSection);
//
}
//
}
// convert to CoordInfo_t
for
(
const
QList
<
QPointF
>
&
transect
:
optiPath
)
{
// for ( const QList<QPointF> &transect : optiPath) {
for
(
const
QList
<
QPointF
>
&
transect
:
fullPath
)
{
QList
<
QGeoCoordinate
>
geoPath
=
toGeo
(
transect
,
_referencePoint
);
QList
<
CoordInfo_t
>
transectList
;
for
(
const
QGeoCoordinate
&
coordinate
:
geoPath
)
{
...
...
src/Wima/OptimisationTools.h
View file @
887e51f1
...
...
@@ -16,7 +16,7 @@ namespace OptimisationTools {
* \sa QList
*/
template
<
class
T
>
bool
dijkstraAlgorithm
(
const
QList
<
T
>
&
elements
,
int
startIndex
,
int
endIndex
,
QList
<
T
>
&
elementPath
,
std
::
function
<
double
(
const
T
&
,
const
T
&
)
>
distance
)
bool
dijkstraAlgorithm
(
const
QList
<
T
>
&
elements
,
int
startIndex
,
int
endIndex
,
QList
<
T
>
&
elementPath
,
std
::
function
<
double
(
const
T
&
,
const
T
&
)
>
distance
Dij
)
{
if
(
elements
.
isEmpty
()
||
startIndex
<
0
||
startIndex
>=
elements
.
size
()
||
endIndex
<
0
...
...
@@ -53,7 +53,7 @@ namespace OptimisationTools {
while
(
workingSet
.
size
()
>
0
)
{
// serach Node with minimal distance
double
minDist
=
std
::
numeric_limits
<
qreal
>::
infinity
();
int
minDistIndex
=
0
;
int
minDistIndex
=
-
1
;
for
(
int
i
=
0
;
i
<
workingSet
.
size
();
i
++
)
{
Node
*
node
=
workingSet
.
value
(
i
);
double
dist
=
node
->
distance
;
...
...
@@ -62,12 +62,15 @@ namespace OptimisationTools {
minDistIndex
=
i
;
}
}
if
(
minDistIndex
==
-
1
)
return
false
;
Node
*
u
=
workingSet
.
takeAt
(
minDistIndex
);
//update distance
for
(
int
i
=
0
;
i
<
workingSet
.
size
();
i
++
)
{
Node
*
v
=
workingSet
[
i
];
double
dist
=
distance
(
u
->
element
,
v
->
element
);
double
dist
=
distance
Dij
(
u
->
element
,
v
->
element
);
// is ther a alternative path which is shorter?
double
alternative
=
u
->
distance
+
dist
;
if
(
alternative
<
v
->
distance
)
{
...
...
src/Wima/PolygonCalculus.cc
View file @
887e51f1
...
...
@@ -167,7 +167,7 @@ namespace PolygonCalculus {
int
startIndex
=
0
;
bool
crossContainsWalker
=
true
;
for
(
int
i
=
0
;
i
<
walkerPoly
->
size
();
i
++
)
{
if
(
!
c
rossPoly
->
contains
(
walkerPoly
->
value
(
i
))
)
{
if
(
!
c
ontains
(
*
crossPoly
,
walkerPoly
->
at
(
i
))
)
{
crossContainsWalker
=
false
;
startIndex
=
i
;
break
;
...
...
@@ -178,11 +178,10 @@ namespace PolygonCalculus {
joinedPolygon
.
append
(
*
crossPoly
);
return
JoinPolygonError
::
PolygonJoined
;
}
QPointF
lastVertex
=
walkerPoly
->
last
();
QPointF
currentVertex
=
walkerPoly
->
value
(
startIndex
);
QPointF
currentVertex
=
walkerPoly
->
at
(
startIndex
);
QPointF
startVertex
=
currentVertex
;
// possible nextVertex (if no intersection between currentVertex and protoVertex with crossPoly)
int
nextVertexNumber
=
nextVertexIndex
(
walkerPoly
->
size
(),
startIndex
);
int
nextVertexNumber
=
nextVertexIndex
(
walkerPoly
->
size
(),
startIndex
);
QPointF
protoNextVertex
=
walkerPoly
->
value
(
nextVertexNumber
);
while
(
1
)
{
//qDebug("nextVertexNumber: %i", nextVertexNumber);
...
...
@@ -206,14 +205,13 @@ namespace PolygonCalculus {
for
(
int
i
=
0
;
i
<
intersectionList
.
size
();
i
++
)
{
double
currentDist
=
PlanimetryCalculus
::
distance
(
currentVertex
,
intersectionList
[
i
]);
if
(
minDist
>
currentDist
&&
currentVertex
!=
intersectionList
[
i
]
)
{
if
(
minDist
>
currentDist
&&
!
qFuzzyIsNull
(
distance
(
currentVertex
,
intersectionList
[
i
]))
)
{
minDist
=
currentDist
;
minDistIndex
=
i
;
}
}
if
(
minDistIndex
!=
-
1
){
lastVertex
=
currentVertex
;
currentVertex
=
intersectionList
.
value
(
minDistIndex
);
QPair
<
int
,
int
>
neighbours
=
neighbourList
.
value
(
minDistIndex
);
protoNextVertex
=
crossPoly
->
value
(
neighbours
.
second
);
...
...
@@ -224,14 +222,12 @@ namespace PolygonCalculus {
walkerPoly
=
crossPoly
;
crossPoly
=
temp
;
}
else
{
lastVertex
=
currentVertex
;
currentVertex
=
walkerPoly
->
value
(
nextVertexNumber
);
nextVertexNumber
=
nextVertexIndex
(
walkerPoly
->
size
(),
nextVertexNumber
);
protoNextVertex
=
walkerPoly
->
value
(
nextVertexNumber
);
}
}
else
{
lastVertex
=
currentVertex
;
currentVertex
=
walkerPoly
->
value
(
nextVertexNumber
);
nextVertexNumber
=
nextVertexIndex
(
walkerPoly
->
size
(),
nextVertexNumber
);
protoNextVertex
=
walkerPoly
->
value
(
nextVertexNumber
);
...
...
@@ -479,7 +475,7 @@ namespace PolygonCalculus {
return
;
}
bool
shortestPath
(
QPolygonF
polygon
,
QPointF
startVertex
,
const
QPointF
&
endVertex
,
QList
<
QPointF
>
&
shortestPath
)
bool
shortestPath
(
QPolygonF
polygon
,
const
QPointF
&
startVertex
,
const
QPointF
&
endVertex
,
QList
<
QPointF
>
&
shortestPath
)
{
using
namespace
PlanimetryCalculus
;
offsetPolygon
(
polygon
,
0.01
);
// solves numerical errors
...
...
@@ -488,7 +484,7 @@ namespace PolygonCalculus {
// lambda
QPolygonF
polygon2
=
polygon
;
offsetPolygon
(
polygon2
,
0.01
);
// solves numerical errors
std
::
function
<
double
(
const
QPointF
&
,
const
QPointF
&
)
>
distance
=
[
polygon2
](
const
QPointF
&
p1
,
const
QPointF
&
p2
)
->
double
{
std
::
function
<
double
(
const
QPointF
&
,
const
QPointF
&
)
>
distance
Dij
=
[
polygon2
](
const
QPointF
&
p1
,
const
QPointF
&
p2
)
->
double
{
if
(
containsPath
(
polygon2
,
p1
,
p2
)){
double
dx
=
p1
.
x
()
-
p2
.
x
();
double
dy
=
p1
.
y
()
-
p2
.
y
();
...
...
@@ -503,7 +499,7 @@ namespace PolygonCalculus {
for
(
int
i
=
0
;
i
<
polygon
.
size
();
i
++
)
{
elementList
.
append
(
polygon
[
i
]);
}
return
OptimisationTools
::
dijkstraAlgorithm
<
QPointF
>
(
elementList
,
0
,
1
,
shortestPath
,
distance
);
return
OptimisationTools
::
dijkstraAlgorithm
<
QPointF
>
(
elementList
,
0
,
1
,
shortestPath
,
distance
Dij
);
}
else
{
return
false
;
}
...
...
src/Wima/PolygonCalculus.h
View file @
887e51f1
...
...
@@ -26,7 +26,7 @@ namespace PolygonCalculus {
void
offsetPolygon
(
QPolygonF
&
polygon
,
double
offset
);
bool
containsPath
(
QPolygonF
polygon
,
const
QPointF
&
c1
,
const
QPointF
&
c2
);
void
decomposeToConvex
(
const
QPolygonF
&
polygon
,
QList
<
QPolygonF
>
&
convexPolygons
);
bool
shortestPath
(
QPolygonF
polygon
,
QPointF
startVertex
,
const
QPointF
&
endVertex
,
QList
<
QPointF
>
&
shortestPath
);
bool
shortestPath
(
QPolygonF
polygon
,
const
QPointF
&
startVertex
,
const
QPointF
&
endVertex
,
QList
<
QPointF
>
&
shortestPath
);
QPolygonF
toQPolygonF
(
const
QVector3DList
&
polygon
);
QPolygonF
toQPolygonF
(
const
QPointFList
&
polygon
);
...
...
src/Wima/WimaPlaner.cc
View file @
887e51f1
...
...
@@ -263,6 +263,9 @@ bool WimaPlaner::updateMission()
}
QGeoCoordinate
start
=
_serviceArea
.
center
();
QGeoCoordinate
end
=
survey
->
visualTransectPoints
().
first
().
value
<
QGeoCoordinate
>
();
if
(
!
_visualItems
.
contains
(
&
_joinedArea
))
_visualItems
.
append
(
&
_joinedArea
);
QList
<
QGeoCoordinate
>
path
;
if
(
!
calcShortestPath
(
start
,
end
,
path
))
{
qgcApp
()
->
showMessage
(
QString
(
tr
(
"Not able to calculate the path from takeoff position to measurement area."
)).
toLocal8Bit
().
data
());
...
...
@@ -576,10 +579,10 @@ bool WimaPlaner::calcShortestPath(const QGeoCoordinate &start, const QGeoCoordin
using
namespace
PolygonCalculus
;
QList
<
QPointF
>
path2D
;
bool
retVal
=
PolygonCalculus
::
shortestPath
(
toQPolygonF
(
toCartesian2D
(
_joinedArea
.
coordinateList
(),
/*origin*/
start
)),
toQPolygonF
(
toCartesian2D
(
_joinedArea
.
coordinateList
(),
/*origin*/
start
)),
/*start point*/
QPointF
(
0
,
0
),
/*destination*/
toCartesian2D
(
destination
,
start
),
/*shortest path*/
path2D
);
/*destination*/
toCartesian2D
(
destination
,
start
),
/*shortest path*/
path2D
);
path
.
append
(
toGeo
(
path2D
,
/*origin*/
start
));
return
retVal
;
...
...
src/WimaView/WimaJoinedAreaMapVisual.qml
0 → 100644
View file @
887e51f1
/****************************************************************************
*
* (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
/// Wima Global Measurement Area visuals
Item
{
id
:
_root
property
var
map
///< Map control to place item in
property
var
qgcView
///< QGCView to use for popping dialogs
property
var
areaItem
:
object
property
var
_polygon
:
areaItem
//property var _polyline: areaItem.polyline
signal
clicked
(
int
sequenceNumber
)
/// Add an initial 4 sided polygon if there is none
function
_addInitialPolygon
()
{
if
(
_polygon
.
count
<
3
)
{
// 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.25
rect
.
height
*=
0.25
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 */
)
// Adjust polygon to max size
var
maxSize
=
100
var
halfWidthMeters
=
Math
.
min
(
topLeftCoord
.
distanceTo
(
topRightCoord
),
maxSize
)
/
2
var
halfHeightMeters
=
Math
.
min
(
topLeftCoord
.
distanceTo
(
bottomLeftCoord
),
maxSize
)
/
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
)
_polygon
.
appendVertex
(
topLeftCoord
)
_polygon
.
appendVertex
(
topRightCoord
)
_polygon
.
appendVertex
(
bottomRightCoord
)
_polygon
.
appendVertex
(
bottomLeftCoord
)
}
}
/*function _addInitialPolyline(){
_polyline.setStartVertexIndex(0);
_polyline.setEndVertexIndex(1);
}*/
Component.onCompleted
:
{
//_addInitialPolygon()
//_addInitialPolyline()
}
Component.onDestruction
:
{
}
WimaMapPolygonVisuals
{
qgcView
:
_root
.
qgcView
mapControl
:
map
mapPolygon
:
_polygon
borderWidth
:
1
borderColor
:
"
black
"
interiorColor
:
"
blue
"
interiorOpacity
:
0.25
}
/*WimaMapPolylineVisuals {
qgcView: _root.qgcView
mapControl: map
mapPolyline: _polyline
lineWidth: 4
lineColor: interactive ? "white" : "yellow"
enableSplitHandels: false
enableDragHandels: true
edgeHandelsOnly: true
}*/
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment