From 7808ca53e41216ac960cfc77735c9b013d6c608f Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 8 Oct 2016 11:51:40 +0200 Subject: [PATCH] Add snapping utilities --- src/qgismobileapp.cpp | 6 ++ src/qml/CoordinateLocator.qml | 83 ++++++++++++++----------- src/qml/qgismobileapp.qml | 7 +-- src/snappingresult.cpp | 111 ++++++++++++++++++++++++++++++++++ src/snappingresult.h | 97 +++++++++++++++++++++++++++++ src/snappingutils.cpp | 111 ++++++++++++++++++++++++++++++++++ src/snappingutils.h | 78 ++++++++++++++++++++++++ src/src.pro | 8 ++- 8 files changed, 460 insertions(+), 41 deletions(-) create mode 100644 src/snappingresult.cpp create mode 100644 src/snappingresult.h create mode 100644 src/snappingutils.cpp create mode 100644 src/snappingutils.h diff --git a/src/qgismobileapp.cpp b/src/qgismobileapp.cpp index 50e6c468f5..64eaf0965d 100644 --- a/src/qgismobileapp.cpp +++ b/src/qgismobileapp.cpp @@ -60,6 +60,8 @@ #include "submodel.h" #include "expressionvariablemodel.h" #include "badlayerhandler.h" +#include "snappingutils.h" +#include "snappingresult.h" QgisMobileapp::QgisMobileapp( QgsApplication* app, QWindow* parent ) : QQuickView( parent ) @@ -117,6 +119,9 @@ void QgisMobileapp::initDeclarative() qRegisterMetaType( "QgsWkbTypes::GeometryType" ); qRegisterMetaType( "QgsFeatureId" ); qRegisterMetaType( "QgsAttributes" ); + qRegisterMetaType( "SnappingResult" ); + qRegisterMetaType( "QgsPoint" ); + qRegisterMetaType( "QgsSnappingConfig" ); // Register QField QML types qmlRegisterUncreatableType( "org.qgis", 1, 0, "QgisInterface", "QgisInterface is only provided by the environment and cannot be created ad-hoc" ); @@ -142,6 +147,7 @@ void QgisMobileapp::initDeclarative() qmlRegisterType( "org.qfield", 1, 0, "SubModel" ); qmlRegisterType( "org.qfield", 1, 0, "ExpressionVariableModel" ); qmlRegisterType( "org.qfield", 1, 0, "BadLayerHandler" ); + qmlRegisterType( "org.qfield", 1, 0, "SnappingUtils" ); // Calculate device pixels int dpiX = QApplication::desktop()->physicalDpiX(); diff --git a/src/qml/CoordinateLocator.qml b/src/qml/CoordinateLocator.qml index 21c0933490..b723ebb7cf 100644 --- a/src/qml/CoordinateLocator.qml +++ b/src/qml/CoordinateLocator.qml @@ -1,5 +1,6 @@ import QtQuick 2.5 import org.qgis 1.0 +import org.qfield 1.0 import QtPositioning 5.3 @@ -9,10 +10,36 @@ Item { property color color: "#263238" property color highlightColor: "#CFD8DC" - property point coordinate + property point sourcePoint: Qt.point( width / 2, height / 2 ) // In screen coordinates + property alias currentLayer: snappingUtils.currentLayer - property bool __coordinateChangedByMapSettings: false + readonly property alias snappingResult: snappingUtils.snappingResult + readonly property alias snappedCoordinate: snappingUtils.snappedCoordinate // In map coordinates, derived from snappinResult + readonly property alias snappedPoint: snappingUtils.snappedPoint // In screen coordinates, derived from snappinResult + SnappingUtils { + id: snappingUtils + + mapSettings: locator.mapSettings + inputCoordinate: sourcePoint + config: qgisProject.snappingConfig + + property point snappedCoordinate + property point snappedPoint + + onSnappingResultChanged: { + if ( snappingResult.isValid ) + { + snappedCoordinate = Qt.point( snappingResult.point.x, snappingResult.point.y ) + snappedPoint = mapSettings.coordinateToScreen( snappedCoordinate ) + } + else + { + snappedPoint = sourcePoint + snappedCoordinate = mapSettings.screenToCoordinate( snappedPoint ) + } + } + } Rectangle { id: crosshairCircleInnerBuffer @@ -30,10 +57,16 @@ Item { Rectangle { id: crosshairCircle - x: parent.width / 2 - radius - y: parent.height / 2 - radius + x: snappedPoint.x - radius + y: snappedPoint.y - radius border.color: parent.color + + Connections { + target: snappingUtils + onSnappingResultChanged: crosshairCircle.border.color = snappingResult.isValid ? "#9b59b6" : locator.color + } + border.width: 1.2 * dp color: "transparent" antialiasing: true @@ -41,30 +74,24 @@ Item { width: 48 * dp height: width radius: width / 2 - } - - Rectangle { - anchors.centerIn: crosshairCircle - color: parent.color + Rectangle { + anchors.centerIn: parent - width: 1.2 * dp - height: crosshairCircle.height * 4 / 6 - } + color: parent.border.color - Rectangle { - anchors.centerIn: crosshairCircle - - color: parent.color + width: 1.2 * dp + height: parent.height * 4 / 6 + } - width: crosshairCircle.width * 4 / 6 - height: 1.2 * dp - } + Rectangle { + anchors.centerIn: parent - Connections { - target: mapSettings + color: parent.border.color - onExtentChanged: __updateCoordinate() + width: parent.width * 4 / 6 + height: 1.2 * dp + } } ParallelAnimation { @@ -113,16 +140,4 @@ Item { { flashAnimation.start() } - - function __updateCoordinate() - { - __coordinateChangedByMapSettings = true - coordinate = mapSettings.screenToCoordinate( Qt.point( crosshairCircle.x + crosshairCircle.radius, crosshairCircle.y + crosshairCircle.radius ) ) - __coordinateChangedByMapSettings = false - } - - onCoordinateChanged: { - if ( !__coordinateChangedByMapSettings ) - mapSettings.setCenter( coordinate ) - } } diff --git a/src/qml/qgismobileapp.qml b/src/qml/qgismobileapp.qml index 898ae16d38..22fa9f0485 100644 --- a/src/qml/qgismobileapp.qml +++ b/src/qml/qgismobileapp.qml @@ -111,13 +111,9 @@ Rectangle { mapSettings: mapCanvas.mapSettings model: RubberbandModel { - currentCoordinate: coordinateLocator.coordinate + currentCoordinate: coordinateLocator.snappedCoordinate vectorLayer: layerSelector.currentLayer crs: mapCanvas.mapSettings.destinationCrs - - onCurrentCoordinateChanged: { - coordinateLocator.coordinate = currentCoordinate - } } anchors.fill: parent @@ -142,6 +138,7 @@ Rectangle { visible: mainWindow.state === "digitize" highlightColor: digitizingToolbar.isDigitizing ? digitizingRubberband.color : "#CFD8DC" mapSettings: mapCanvas.mapSettings + currentLayer: layerSelector.currentLayer } /* GPS marker */ diff --git a/src/snappingresult.cpp b/src/snappingresult.cpp new file mode 100644 index 0000000000..76d633901a --- /dev/null +++ b/src/snappingresult.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + snappingresult.cpp - SnappingResult + + --------------------- + begin : 8.10.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias@opengis.ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "snappingresult.h" + +SnappingResult::SnappingResult() + : mType( Invalid ) + , mDist( 0 ) + , mPoint() + , mLayer( nullptr ) + , mFid( 0 ) + , mVertexIndex( 0 ) +{} + +SnappingResult::SnappingResult( SnappingResult::Type t, QgsVectorLayer* vl, QgsFeatureId fid, double dist, const QgsPoint& pt, int vertexIndex, QgsPoint* edgePoints ) + : mType( t ) + , mDist( dist ) + , mPoint( pt ) + , mLayer( vl ) + , mFid( fid ) + , mVertexIndex( vertexIndex ) +{ + if ( edgePoints ) + { + mEdgePoints[0] = edgePoints[0]; + mEdgePoints[1] = edgePoints[1]; + } +} + +SnappingResult::SnappingResult( const QgsPointLocator::Match& match ) + : mType( matchTypeToSnappingResultType( match.type() ) ) + , mDist( match.distance() ) + , mPoint( match.point() ) + , mLayer( match.layer() ) + , mFid( match.featureId() ) + , mVertexIndex( match.vertexIndex() ) +{ +} + +SnappingResult::Type SnappingResult::type() const +{ + return mType; +} + +bool SnappingResult::isValid() const +{ + return mType != Invalid; +} + +bool SnappingResult::hasVertex() const +{ + return mType == Vertex; +} + +bool SnappingResult::hasEdge() const +{ + return mType == Edge; +} + +bool SnappingResult::hasArea() const +{ + return mType == Area; +} + +double SnappingResult::distance() const +{ + return mDist; +} + +QgsPoint SnappingResult::point() const +{ + return mPoint; +} + +int SnappingResult::vertexIndex() const +{ + return mVertexIndex; +} + +QgsVectorLayer*SnappingResult::layer() const +{ + return mLayer; +} + +QgsFeatureId SnappingResult::featureId() const +{ + return mFid; +} + +void SnappingResult::edgePoints( QgsPoint& pt1, QgsPoint& pt2 ) const +{ + pt1 = mEdgePoints[0]; + pt2 = mEdgePoints[1]; +} + +SnappingResult::Type SnappingResult::matchTypeToSnappingResultType( QgsPointLocator::Type type ) +{ + return static_cast( type ); +} diff --git a/src/snappingresult.h b/src/snappingresult.h new file mode 100644 index 0000000000..feb4bf1774 --- /dev/null +++ b/src/snappingresult.h @@ -0,0 +1,97 @@ +/*************************************************************************** + snappingresult.h - SnappingResult + + --------------------- + begin : 8.10.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias@opengis.ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#ifndef SNAPPINGRESULT_H +#define SNAPPINGRESULT_H + +#include +#include +#include + +class SnappingResult +{ + Q_GADGET + Q_PROPERTY( QgsPoint point READ point ) + Q_PROPERTY( bool isValid READ isValid ) + + public: + /** + * The type of a snap result or the filter type for a snap request. + */ + enum Type + { + Invalid = 0, //!< Invalid + Vertex = 1, //!< Snapped to a vertex. Can be a vertex of the geometry or an intersection. + Edge = 2, //!< Snapped to an edge + Area = 4, //!< Snapped to an area + All = Vertex | Edge | Area //!< Combination of vertex, edge and area + }; + + Q_DECLARE_FLAGS( Types, Type ) + + //! construct invalid match + SnappingResult(); + + SnappingResult( Type t, QgsVectorLayer* vl, QgsFeatureId fid, double dist, const QgsPoint& pt, int vertexIndex = 0, QgsPoint* edgePoints = nullptr ); + + SnappingResult( const QgsPointLocator::Match& match ); + + Type type() const; + + bool isValid() const; + bool hasVertex() const; + bool hasEdge() const; + bool hasArea() const; + + //! for vertex / edge match + //! units depending on what class returns it (geom.cache: layer units, map canvas snapper: dest crs units) + double distance() const; + + //! for vertex / edge match + //! coords depending on what class returns it (geom.cache: layer coords, map canvas snapper: dest coords) + QgsPoint point() const; + + void setPoint( const QgsPoint& pt ); + + //! for vertex / edge match (first vertex of the edge) + int vertexIndex() const; + + /** + * The vector layer where the snap occurred. + * Will be null if the snap happened on an intersection. + */ + QgsVectorLayer* layer() const; + + /** + * The id of the feature to which the snapped geometry belongs. + */ + QgsFeatureId featureId() const; + + //! Only for a valid edge match - obtain endpoints of the edge + void edgePoints( QgsPoint& pt1, QgsPoint& pt2 ) const; + + private: + Type matchTypeToSnappingResultType( QgsPointLocator::Type type ); + + Type mType; + double mDist; + QgsPoint mPoint; + QgsVectorLayer* mLayer; + QgsFeatureId mFid; + int mVertexIndex; // e.g. vertex index + QgsPoint mEdgePoints[2]; +}; + +#endif // SNAPPINGRESULT_H diff --git a/src/snappingutils.cpp b/src/snappingutils.cpp new file mode 100644 index 0000000000..0ecbc9bbf5 --- /dev/null +++ b/src/snappingutils.cpp @@ -0,0 +1,111 @@ +/*************************************************************************** + snappingutils.cpp + + --------------------- + begin : 8.10.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias@opengis.ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "snappingutils.h" +#include "mapsettings.h" + +#include "qgsvectorlayer.h" + +SnappingUtils::SnappingUtils( QObject* parent ) + : QgsSnappingUtils( parent ) + , mSettings( nullptr ) +{ +} + +void SnappingUtils::onMapSettingsUpdated() +{ + QgsSnappingUtils::setMapSettings( mSettings->mapSettings() ); + + snap(); +} + +void SnappingUtils::snap() +{ + QgsPointLocator::Match match = snapToMap( QPoint( mInputCoordinate .x(), mInputCoordinate .y() ) ); + mSnappingResult = SnappingResult( match ); + + emit snappingResultChanged(); +} + +QPointF SnappingUtils::inputCoordinate() const +{ + return mInputCoordinate; +} + +void SnappingUtils::setInputCoordinate( const QPointF& inputCoordinate ) +{ + if ( inputCoordinate == mInputCoordinate ) + return; + + mInputCoordinate = inputCoordinate; + + snap(); + + emit inputCoordinateChanged(); +} + +SnappingResult SnappingUtils::snappingResult() const +{ + return mSnappingResult; +} + +void SnappingUtils::prepareIndexStarting( int count ) +{ + mIndexLayerCount = count; + emit indexingStarted( count ); +} + +void SnappingUtils::prepareIndexProgress( int index ) +{ + if ( index == mIndexLayerCount ) + emit indexingFinished(); + else + emit indexingProgress( index ); +} + +QgsVectorLayer* SnappingUtils::currentLayer() const +{ + return mCurrentLayer; +} + +void SnappingUtils::setCurrentLayer( QgsVectorLayer* currentLayer ) +{ + if ( currentLayer == mCurrentLayer ) + return; + + mCurrentLayer = currentLayer; + QgsSnappingUtils::setCurrentLayer( currentLayer ); + + emit currentLayerChanged(); +} + +MapSettings* SnappingUtils::mapSettings() const +{ + return mSettings; +} + +void SnappingUtils::setMapSettings( MapSettings* settings ) +{ + if ( mSettings == settings ) + return; + + connect( settings, &MapSettings::extentChanged, this, &SnappingUtils::onMapSettingsUpdated ); + connect( settings, &MapSettings::destinationCrsChanged, this, &SnappingUtils::onMapSettingsUpdated ); + connect( settings, &MapSettings::layersChanged, this, &SnappingUtils::onMapSettingsUpdated ); + + mSettings = settings; + emit mapSettingsChanged(); +} diff --git a/src/snappingutils.h b/src/snappingutils.h new file mode 100644 index 0000000000..53de9c8be8 --- /dev/null +++ b/src/snappingutils.h @@ -0,0 +1,78 @@ +/*************************************************************************** + snappingutils.h + + --------------------- + begin : 8.10.2016 + copyright : (C) 2016 by Matthias Kuhn + email : matthias@opengis.ch + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef SNAPPINGUTILS_H +#define SNAPPINGUTILS_H + +class MapSettings; + +#include + +#include "snappingresult.h" + +class SnappingUtils : public QgsSnappingUtils +{ + Q_OBJECT + + Q_PROPERTY( MapSettings* mapSettings READ mapSettings WRITE setMapSettings NOTIFY mapSettingsChanged ) + Q_PROPERTY( QgsVectorLayer* currentLayer READ currentLayer WRITE setCurrentLayer NOTIFY currentLayerChanged ) + Q_PROPERTY( SnappingResult snappingResult READ snappingResult NOTIFY snappingResultChanged ) + Q_PROPERTY( QPointF inputCoordinate READ inputCoordinate WRITE setInputCoordinate NOTIFY inputCoordinateChanged ) + + public: + SnappingUtils( QObject* parent = nullptr ); + + MapSettings* mapSettings() const; + void setMapSettings( MapSettings* settings ); + + QgsVectorLayer* currentLayer() const; + void setCurrentLayer( QgsVectorLayer* currentLayer ); + + QPointF inputCoordinate() const; + void setInputCoordinate( const QPointF& inputCoordinate ); + + SnappingResult snappingResult() const; + + signals: + void mapSettingsChanged(); + void currentLayerChanged(); + void snappingResultChanged(); + void inputCoordinateChanged(); + + void indexingStarted( int count ); + void indexingProgress( int index ); + void indexingFinished(); + + protected: + virtual void prepareIndexStarting( int count ) override; + virtual void prepareIndexProgress( int index ) override; + + private slots: + void onMapSettingsUpdated(); + + private: + void snap(); + + MapSettings* mSettings; + QgsVectorLayer* mCurrentLayer; + + int mIndexLayerCount; + SnappingResult mSnappingResult; + QPointF mInputCoordinate; +}; + + +#endif // SNAPPINGUTILS_H diff --git a/src/src.pro b/src/src.pro index b1b55e1545..eaed375e76 100644 --- a/src/src.pro +++ b/src/src.pro @@ -46,7 +46,9 @@ HEADERS += \ submodel.h \ attributeformmodel.h \ expressionvariablemodel.h \ - badlayerhandler.h + badlayerhandler.h \ + snappingutils.h \ + snappingresult.h SOURCES += \ appinterface.cpp \ @@ -77,7 +79,9 @@ SOURCES += \ submodel.cpp \ attributeformmodel.cpp \ expressionvariablemodel.cpp \ - badlayerhandler.cpp + badlayerhandler.cpp \ + snappingutils.cpp \ + snappingresult.cpp INCLUDEPATH += ../3rdparty/tessellate LIBS += ../3rdparty/tessellate/libtessellate.a