Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Vertex tool improvement #5797

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/core/utils/geometryutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,22 @@ QgsGeometry GeometryUtils::createGeometryFromWkt( const QString &wkt )
{
return QgsGeometry::fromWkt( wkt );
}

double GeometryUtils::calculateAngle( const QgsPoint &a, const QgsPoint &b, const QgsPoint &c )
{
double abX = b.x() - a.x();
double abY = b.y() - a.y();
double bcX = c.x() - b.x();
double bcY = c.y() - b.y();

double dotProduct = abX * bcX + abY * bcY;

double magnitudeAB = std::sqrt( abX * abX + abY * abY );
double magnitudeBC = std::sqrt( bcX * bcX + bcY * bcY );

double cosTheta = dotProduct / ( magnitudeAB * magnitudeBC );

double angle = std::acos( cosTheta );

return std::abs( angle * ( 180.0 / M_PI ) - 180 );
}
3 changes: 3 additions & 0 deletions src/core/utils/geometryutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ class QFIELD_CORE_EXPORT GeometryUtils : public QObject

//! Creates a geometry from a WKT string.
static Q_INVOKABLE QgsGeometry createGeometryFromWkt( const QString &wkt );

//! Calculates angle between AB and AC
static double calculateAngle( const QgsPoint &a, const QgsPoint &b, const QgsPoint &c );
};

#endif // GEOMETRYUTILS_H
61 changes: 61 additions & 0 deletions src/core/vertexmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* *
***************************************************************************/

#include "geometryutils.h"
#include "qgsquickmapsettings.h"
#include "vertexmodel.h"

Expand All @@ -32,6 +33,7 @@ VertexModel::VertexModel( QObject *parent )
connect( this, &VertexModel::vertexCountChanged, this, &VertexModel::updateCanPreviousNextVertex );
connect( this, &VertexModel::currentVertexIndexChanged, this, &VertexModel::updateCanPreviousNextVertex );
connect( this, &VertexModel::currentVertexIndexChanged, this, &VertexModel::updateCanRemoveVertex );
connect( this, &VertexModel::currentVertexIndexChanged, this, &VertexModel::updateSnappedAngle );
}

void VertexModel::setMapSettings( QgsQuickMapSettings *mapSettings )
Expand Down Expand Up @@ -562,6 +564,11 @@ void VertexModel::next()
}
}

QgsPoint VertexModel::getPoint( int index )
{
return mVertices[index].point;
}

void VertexModel::addVertexNearestToPosition( const QgsPoint &mapPoint )
{
double closestDistance = std::numeric_limits<double>::max();
Expand Down Expand Up @@ -696,6 +703,25 @@ void VertexModel::setCurrentPoint( const QgsPoint &point )

Vertex &vertex = mVertices[mCurrentIndex];

int startPoint = mCurrentIndex - 2;
if ( startPoint < 0 )
{
startPoint = vertexCount() + startPoint;
}
int endPoint = mCurrentIndex + 2;
if ( endPoint >= vertexCount() )
{
endPoint = endPoint - vertexCount();
}

int angle = ( int ) GeometryUtils::calculateAngle( mVertices[startPoint].point, mVertices[mCurrentIndex].point, mVertices[endPoint].point );

const double remainder = std::fmod( angle, snapToCommonAngleDegrees() );
const double threshold = .1;
const bool angleFound = std::abs( remainder ) <= threshold || std::abs( std::abs( remainder ) - snapToCommonAngleDegrees() ) <= threshold;

setSnappedAngle( angleFound ? angle : 0 );

if ( mMapSettings && vertex.point.distance( point ) / mMapSettings->mapSettings().mapUnitsPerPixel() < 1 )
return;

Expand Down Expand Up @@ -799,6 +825,11 @@ bool VertexModel::dirty() const
return mDirty;
}

int VertexModel::snappedAngle() const
{
return mSnappedAngle;
}

bool VertexModel::canRemoveVertex()
{
return mCanRemoveVertex;
Expand Down Expand Up @@ -881,6 +912,15 @@ void VertexModel::setDirty( bool dirty )
emit dirtyChanged();
}

void VertexModel::setSnappedAngle( int snappedAngle )
{
if ( mSnappedAngle == snappedAngle )
return;

mSnappedAngle = snappedAngle;
emit snappedAngleChanged();
}

void VertexModel::updateCanRemoveVertex()
{
bool canRemoveVertex = false;
Expand Down Expand Up @@ -1032,3 +1072,24 @@ void VertexModel::setEditingMode( VertexModel::EditingMode mode )

emit editingModeChanged();
}

void VertexModel::updateSnappedAngle()
{
if ( currentVertexIndex() == -1 )
{
setSnappedAngle( 0 );
}
}

int VertexModel::snapToCommonAngleDegrees() const
{
return mSnapToCommonAngleDegrees;
}

void VertexModel::setSnapToCommonAngleDegrees( int snapToCommonAngleDegrees )
{
if ( mSnapToCommonAngleDegrees == snapToCommonAngleDegrees )
return;
mSnapToCommonAngleDegrees = snapToCommonAngleDegrees;
emit snapToCommonAngleDegreesChanged();
}
34 changes: 34 additions & 0 deletions src/core/vertexmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ class QFIELD_CORE_EXPORT VertexModel : public QAbstractListModel
* The index of the currently active vertex. If no vertex is selected, this is -1.
*/
Q_PROPERTY( int currentVertexIndex READ currentVertexIndex WRITE setCurrentVertexIndex NOTIFY currentVertexIndexChanged )
//! The `snappedAngle` property holds the angle that has been adjusted to the nearest multiple of `snapToCommonAngleDegrees`.
//! This value is automatically updated whenever the angle is changed.
Q_PROPERTY( int snappedAngle READ snappedAngle NOTIFY snappedAngleChanged )
//! The `snapToCommonAngleDegrees` property specifies the angle increment to which the original angle will be snapped.
//! This value defines the common angle step for snapping.
Q_PROPERTY( int snapToCommonAngleDegrees READ snapToCommonAngleDegrees WRITE setSnapToCommonAngleDegrees NOTIFY snapToCommonAngleDegreesChanged )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation


/**
* The geometry in layer coordinates
Expand Down Expand Up @@ -187,6 +193,8 @@ class QFIELD_CORE_EXPORT VertexModel : public QAbstractListModel
//! next vertex or segment
Q_INVOKABLE void next();

Q_INVOKABLE QgsPoint getPoint( int index );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to expose this to QML? Should it be a private function?


//! Selects the vertex at the given screen \a point within a given \a threshold
Q_INVOKABLE void selectVertexAtPosition( const QPointF &point, double threshold, bool autoInsert = true );

Expand Down Expand Up @@ -265,6 +273,18 @@ class QFIELD_CORE_EXPORT VertexModel : public QAbstractListModel

bool canUndo();

//! Returns `snappedAngle`
int snappedAngle() const;

//! sets snapped angle
void setSnappedAngle( int snappedAngle );

//! Returns `snapToCommonAngleDegrees`
int snapToCommonAngleDegrees() const;

//! Sets `snapToCommonAngleDegrees`
void setSnapToCommonAngleDegrees( int snapToCommonAngleDegrees );

signals:
//! \copydoc editingMode
void editingModeChanged();
Expand Down Expand Up @@ -301,6 +321,12 @@ class QFIELD_CORE_EXPORT VertexModel : public QAbstractListModel
//! Emitted when the history has been modified
void historyChanged();

//! Emitted when `snappedAngle` modified
void snappedAngleChanged();

//! Emitted when new `snapToCommonAngleDegrees` modified
void snapToCommonAngleDegreesChanged();

private:
void refreshGeometry();
//! Add the candidates of new vertices (extending or segment)
Expand All @@ -312,6 +338,11 @@ class QFIELD_CORE_EXPORT VertexModel : public QAbstractListModel
void updateCanPreviousNextVertex();
void setGeometryType( const Qgis::GeometryType &geometryType );

//! This function is called whenever the `currentVertexIndex` changes. It checks if there is an active vertex in editing mode.
//! If no vertex is active, the `snappedAngle` will be reset to 0. This ensures that the angle snapping logic is only applied
//! when there is an active vertex to work with.
void updateSnappedAngle();

QList<Vertex> mVertices;

//! copy of the initial geometry, in destination (layer) CRS
Expand Down Expand Up @@ -349,6 +380,9 @@ class QFIELD_CORE_EXPORT VertexModel : public QAbstractListModel
int mHistoryIndex = -1;
bool mHistoryTraversing = false;

int mSnappedAngle = 0;
int mSnapToCommonAngleDegrees = 0;

friend class VertexModelTest;
};

Expand Down
118 changes: 117 additions & 1 deletion src/qml/CoordinateLocator.qml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ Item {
readonly property alias snappedPoint: snappingUtils.snappedPoint // In screen coordinates, derived from snappinResult
readonly property alias topSnappingResult: snappingUtils.snappingResult // The snappingResult itself, only one (compared to QGIS where it's usually a list)

property VertexModel editingVertexModel

SnappingUtils {
id: snappingUtils

Expand All @@ -63,16 +65,37 @@ Item {
inputCoordinate: {
// Get the current crosshair location in screen coordinates. If `undefined`, then we use the center of the screen as input point.
const location = sourceLocation === undefined ? Qt.point(locator.width / 2, locator.height / 2) : sourceLocation;
if (snapToCommonAngleButton.isSnapToCommonAngleEnabled) {
if (snapToCommonAngleButton.isSnapToCommonAngleEnabled && editingVertexModel.snappedAngle > 0) {
const vertexCount = editingVertexModel.vertexCount;
const currentIndex = editingVertexModel.currentVertexIndex;
let startPoint = currentIndex - 2;
if (startPoint < 0) {
startPoint = vertexCount + startPoint;
}
let endPoint = currentIndex + 2;
if (endPoint >= vertexCount) {
endPoint = endPoint - vertexCount;
}
const start = mapSettings.coordinateToScreen(editingVertexModel.currentPoint);
const p1 = mapSettings.coordinateToScreen(editingVertexModel.getPoint(startPoint));
const p2 = mapSettings.coordinateToScreen(editingVertexModel.getPoint(endPoint));
const intersections = getIntersectionPoints(start, p1, p2);
vertexSnapToCommonAngleLines.endCoordX1 = intersections.x1 || 0;
vertexSnapToCommonAngleLines.endCoordY1 = intersections.y1 || 0;
vertexSnapToCommonAngleLines.endCoordX2 = intersections.x2 || 0;
vertexSnapToCommonAngleLines.endCoordY2 = intersections.y2 || 0;
} else if (snapToCommonAngleButton.isSnapToCommonAngleEnabled) {
locator.commonAngleInDegrees = getCommonAngleInDegrees(location, locator.rubberbandModel, snapToCommonAngleButton.snapToCommonAngleDegrees, snapToCommonAngleButton.isSnapToCommonAngleRelative);
const coords = calculateSnapToAngleLineEndCoords(snappedPoint, locator.commonAngleInDegrees, snapToCommonAngleButton.isSnapToCommonAngleRelative, 1000);
snapToCommonAngleLines.endCoordX = coords.x || 0;
snapToCommonAngleLines.endCoordY = coords.y || 0;
vertexSnapToCommonAngleLines.reset();
return snapPointToCommonAngle(location, locator.rubberbandModel, locator.commonAngleInDegrees, snapToCommonAngleButton.isSnapToCommonAngleRelative);
} else {
locator.commonAngleInDegrees = null;
snapToCommonAngleLines.endCoordX = 0;
snapToCommonAngleLines.endCoordY = 0;
vertexSnapToCommonAngleLines.reset();
}
return location;
}
Expand Down Expand Up @@ -280,6 +303,86 @@ Item {
}
}

Shape {
id: vertexSnapToCommonAngleLines

property double endCoordX1: 0
property double endCoordY1: 0
property double endCoordX2: 0
property double endCoordY2: 0

visible: (endCoordX1 + endCoordY1 + endCoordX2 + endCoordY2) != 0
width: parent.width
height: parent.height
anchors.centerIn: parent
opacity: 0.5

function reset() {
vertexSnapToCommonAngleLines.endCoordX1 = 0;
vertexSnapToCommonAngleLines.endCoordY1 = 0;
vertexSnapToCommonAngleLines.endCoordX2 = 0;
vertexSnapToCommonAngleLines.endCoordY2 = 0;
}

ShapePath {
id: firstLineOuter
strokeWidth: 4
strokeColor: "#fff"
strokeStyle: ShapePath.DashLine
dashPattern: [5, 3]
startX: snappedPoint.x
startY: snappedPoint.y

PathLine {
x: vertexSnapToCommonAngleLines.endCoordX1
y: vertexSnapToCommonAngleLines.endCoordY1
}
}
// inner line
ShapePath {
strokeWidth: firstLineOuter.strokeWidth / 2
strokeColor: "#000"
strokeStyle: ShapePath.DashLine
dashPattern: firstLineOuter.dashPattern.map(v => v * 2)
startX: snappedPoint.x
startY: snappedPoint.y

PathLine {
x: vertexSnapToCommonAngleLines.endCoordX1
y: vertexSnapToCommonAngleLines.endCoordY1
}
}

ShapePath {
id: secondLineOuter
strokeWidth: 4
strokeColor: "#fff"
strokeStyle: ShapePath.DashLine
dashPattern: [5, 3]
startX: snappedPoint.x
startY: snappedPoint.y

PathLine {
x: vertexSnapToCommonAngleLines.endCoordX2
y: vertexSnapToCommonAngleLines.endCoordY2
}
}
// inner line
ShapePath {
strokeWidth: secondLineOuter.strokeWidth / 2
strokeColor: "#000"
strokeStyle: ShapePath.DashLine
dashPattern: secondLineOuter.dashPattern.map(v => v * 2)
startX: snappedPoint.x
startY: snappedPoint.y

PathLine {
x: vertexSnapToCommonAngleLines.endCoordX2
y: vertexSnapToCommonAngleLines.endCoordY2
}
}
}

ParallelAnimation {
id: flashAnimation

Expand Down Expand Up @@ -439,4 +542,17 @@ Item {
"y": y2
};
}

function getIntersectionPoints(centeralPoint, pointB, pointC) {
const xDiffAB = centeralPoint.x - pointB.x;
const yDiffAB = centeralPoint.y - pointB.y;
const xDiffAC = centeralPoint.x - pointC.x;
const yDiffAC = centeralPoint.y - pointC.y;
return {
"x1": centeralPoint.x - (10 * xDiffAB),
"y1": centeralPoint.y - (10 * yDiffAB),
"x2": centeralPoint.x - (10 * xDiffAC),
"y2": centeralPoint.y - (10 * yDiffAC)
};
}
}
1 change: 1 addition & 0 deletions src/qml/qgismobileapp.qml
Original file line number Diff line number Diff line change
Expand Up @@ -4000,6 +4000,7 @@ ApplicationWindow {
currentPoint: coordinateLocator.currentCoordinate
mapSettings: mapCanvas.mapSettings
isHovering: mapCanvasMap.hovered
snapToCommonAngleDegrees: snapToCommonAngleButton.snapToCommonAngleDegrees
}

ScreenLocker {
Expand Down
Loading