diff --git a/python/gui/auto_generated/qgsmapmouseevent.sip.in b/python/gui/auto_generated/qgsmapmouseevent.sip.in index 4fed1e4d71f..a9063372325 100644 --- a/python/gui/auto_generated/qgsmapmouseevent.sip.in +++ b/python/gui/auto_generated/qgsmapmouseevent.sip.in @@ -117,6 +117,15 @@ The unsnapped, real mouse cursor position in pixel coordinates. Alias to pos() :return: Mouse position in pixel coordinates +%End + + void snapToGrid( double precision, const QgsCoordinateReferenceSystem &crs ); +%Docstring +Snaps the mapPoint to a grid with the given ``precision``. +The snapping will be done in the specified ``crs``. If this crs is +different from the mapCanvas crs, it will be reprojected on the fly. + +.. versionadded:: 3.4 %End }; diff --git a/python/gui/auto_generated/qgsmaptooladvanceddigitizing.sip.in b/python/gui/auto_generated/qgsmaptooladvanceddigitizing.sip.in index 3da3915d3ba..e011743e596 100644 --- a/python/gui/auto_generated/qgsmaptooladvanceddigitizing.sip.in +++ b/python/gui/auto_generated/qgsmaptooladvanceddigitizing.sip.in @@ -149,6 +149,22 @@ canvasMoveEvent is triggered and it's not hidden by the cad's construction mode. :param e: Mouse events prepared by the cad system +%End + + bool snapToLayerGridEnabled() const; +%Docstring +Enables or disables snap to grid of mouse events. +The snapping will occur in the layer's CRS. + +.. versionadded:: 3.4 +%End + + void setSnapToLayerGridEnabled( bool snapToLayerGridEnabled ); +%Docstring +Enables or disables snap to grid of mouse events. +The snapping will occur in the layer's CRS. + +.. versionadded:: 3.4 %End }; diff --git a/python/gui/auto_generated/qgssnaptogridcanvasitem.sip.in b/python/gui/auto_generated/qgssnaptogridcanvasitem.sip.in new file mode 100644 index 00000000000..d96a871c242 --- /dev/null +++ b/python/gui/auto_generated/qgssnaptogridcanvasitem.sip.in @@ -0,0 +1,92 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgssnaptogridcanvasitem.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsSnapToGridCanvasItem : QObject, QgsMapCanvasItem +{ +%Docstring + +Shows a grid on the map canvas given a spatial resolution. + +.. versionadded:: 3.4 +%End + +%TypeHeaderCode +#include "qgssnaptogridcanvasitem.h" +%End + public: + + QgsSnapToGridCanvasItem( QgsMapCanvas *mapCanvas /TransferThis/ ); +%Docstring +Will automatically be added to the ``mapCanvas``. +%End + + virtual void paint( QPainter *painter ); + + + QgsPointXY point() const; +%Docstring +A point that will be highlighted on the map canvas. +The point needs to be in map coordinates. The closest point on the +grid will be highlighted. +%End + + void setPoint( const QgsPointXY &point ); +%Docstring +A point that will be highlighted on the map canvas. +The point needs to be in map coordinates. The closest point on the +grid will be highlighted. +%End + + double precision() const; +%Docstring +The resolution of the grid in map units. +If a crs has been specified it will be in CRS units. +%End + + void setPrecision( double precision ); +%Docstring +The resolution of the grid in map units. +If a crs has been specified it will be in CRS units. +%End + + QgsCoordinateReferenceSystem crs() const; +%Docstring +The CRS in which the grid should be calculated. +By default will be an invalid QgsCoordinateReferenceSystem and +as such equal to the CRS of the map canvas. +%End + + void setCrs( const QgsCoordinateReferenceSystem &crs ); +%Docstring +The CRS in which the grid should be calculated. +By default will be an invalid QgsCoordinateReferenceSystem and +as such equal to the CRS of the map canvas. +%End + + bool enabled() const; +%Docstring +Enable this item. It will be hidden if disabled. +%End + + void setEnabled( bool enabled ); +%Docstring +Enable this item. It will be hidden if disabled. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgssnaptogridcanvasitem.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/gui/gui_auto.sip b/python/gui/gui_auto.sip index 518511760db..5bd30d7e244 100644 --- a/python/gui/gui_auto.sip +++ b/python/gui/gui_auto.sip @@ -193,6 +193,7 @@ %Include auto_generated/qgssearchquerybuilder.sip %Include auto_generated/qgsshortcutsmanager.sip %Include auto_generated/qgsslider.sip +%Include auto_generated/qgssnaptogridcanvasitem.sip %Include auto_generated/qgsstatusbar.sip %Include auto_generated/qgssublayersdialog.sip %Include auto_generated/qgssubstitutionlistwidget.sip diff --git a/src/app/qgsmaptoolsplitfeatures.cpp b/src/app/qgsmaptoolsplitfeatures.cpp index 7cbfb953573..7dc2ff0176e 100644 --- a/src/app/qgsmaptoolsplitfeatures.cpp +++ b/src/app/qgsmaptoolsplitfeatures.cpp @@ -27,6 +27,7 @@ QgsMapToolSplitFeatures::QgsMapToolSplitFeatures( QgsMapCanvas *canvas ) : QgsMapToolCapture( canvas, QgisApp::instance()->cadDockWidget(), QgsMapToolCapture::CaptureLine ) { mToolName = tr( "Split features" ); + setSnapToLayerGridEnabled( false ); } void QgsMapToolSplitFeatures::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) diff --git a/src/app/qgsmaptoolsplitparts.cpp b/src/app/qgsmaptoolsplitparts.cpp index 4a66d4a49ca..b2adf1edccb 100644 --- a/src/app/qgsmaptoolsplitparts.cpp +++ b/src/app/qgsmaptoolsplitparts.cpp @@ -27,6 +27,7 @@ QgsMapToolSplitParts::QgsMapToolSplitParts( QgsMapCanvas *canvas ) : QgsMapToolCapture( canvas, QgisApp::instance()->cadDockWidget(), QgsMapToolCapture::CaptureLine ) { mToolName = tr( "Split parts" ); + setSnapToLayerGridEnabled( false ); } void QgsMapToolSplitParts::cadCanvasReleaseEvent( QgsMapMouseEvent *e ) diff --git a/src/core/qgsvectorlayereditutils.cpp b/src/core/qgsvectorlayereditutils.cpp index 0b42c7490c7..67c2d85bd89 100644 --- a/src/core/qgsvectorlayereditutils.cpp +++ b/src/core/qgsvectorlayereditutils.cpp @@ -47,7 +47,7 @@ bool QgsVectorLayerEditUtils::insertVertex( double x, double y, QgsFeatureId atF geometry.insertVertex( x, y, beforeVertex ); - mLayer->editBuffer()->changeGeometry( atFeatureId, geometry ); + mLayer->changeGeometry( atFeatureId, geometry ); return true; } @@ -64,7 +64,7 @@ bool QgsVectorLayerEditUtils::insertVertex( const QgsPoint &point, QgsFeatureId geometry.insertVertex( point, beforeVertex ); - mLayer->editBuffer()->changeGeometry( atFeatureId, geometry ); + mLayer->changeGeometry( atFeatureId, geometry ); return true; } @@ -87,7 +87,7 @@ bool QgsVectorLayerEditUtils::moveVertex( const QgsPoint &p, QgsFeatureId atFeat geometry.moveVertex( p, atVertex ); - mLayer->editBuffer()->changeGeometry( atFeatureId, geometry ); + mLayer->changeGeometry( atFeatureId, geometry ); return true; } @@ -112,7 +112,7 @@ QgsVectorLayer::EditResult QgsVectorLayerEditUtils::deleteVertex( QgsFeatureId f geometry.set( nullptr ); } - mLayer->editBuffer()->changeGeometry( featureId, geometry ); + mLayer->changeGeometry( featureId, geometry ); return !geometry.isNull() ? QgsVectorLayer::Success : QgsVectorLayer::EmptyGeometry; } @@ -159,7 +159,7 @@ QgsGeometry::OperationResult QgsVectorLayerEditUtils::addRing( QgsCurve *ring, c if ( addRingReturnCode == 0 ) if ( addRingReturnCode == QgsGeometry::Success ) { - mLayer->editBuffer()->changeGeometry( f.id(), g ); + mLayer->changeGeometry( f.id(), g ); if ( modifiedFeatureId ) *modifiedFeatureId = f.id(); @@ -212,7 +212,7 @@ QgsGeometry::OperationResult QgsVectorLayerEditUtils::addPart( const QgsPointSeq //convert back to single part if required by layer geometry.convertToSingleType(); } - mLayer->editBuffer()->changeGeometry( featureId, geometry ); + mLayer->changeGeometry( featureId, geometry ); } return errorCode; } @@ -247,7 +247,7 @@ QgsGeometry::OperationResult QgsVectorLayerEditUtils::addPart( QgsCurve *ring, Q //convert back to single part if required by layer geometry.convertToSingleType(); } - mLayer->editBuffer()->changeGeometry( featureId, geometry ); + mLayer->changeGeometry( featureId, geometry ); } return errorCode; } @@ -267,7 +267,7 @@ int QgsVectorLayerEditUtils::translateFeature( QgsFeatureId featureId, double dx int errorCode = geometry.translate( dx, dy ); if ( errorCode == 0 ) { - mLayer->editBuffer()->changeGeometry( featureId, geometry ); + mLayer->changeGeometry( featureId, geometry ); } return errorCode; } @@ -348,13 +348,13 @@ QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitFeatures( const QVect if ( splitFunctionReturn == QgsGeometry::OperationResult::Success ) { //change this geometry - mLayer->editBuffer()->changeGeometry( feat.id(), featureGeom ); + mLayer->changeGeometry( feat.id(), featureGeom ); //insert new features for ( int i = 0; i < newGeometries.size(); ++i ) { QgsFeature f = QgsVectorLayerUtils::createFeature( mLayer, newGeometries.at( i ), feat.attributes().toMap() ); - mLayer->editBuffer()->addFeature( f ); + mLayer->addFeature( f ); } if ( topologicalEditing ) @@ -470,7 +470,7 @@ QgsGeometry::OperationResult QgsVectorLayerEditUtils::splitParts( const QVector< if ( !addPartRet ) { - mLayer->editBuffer()->changeGeometry( feat.id(), featureGeom ); + mLayer->changeGeometry( feat.id(), featureGeom ); } if ( topologicalEditing ) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index ab048a68e39..6fbe34d668d 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -358,6 +358,7 @@ SET(QGIS_GUI_SRCS qgsshortcutsmanager.cpp qgsslider.cpp qgssnapindicator.cpp + qgssnaptogridcanvasitem.cpp qgssublayersdialog.cpp qgssubstitutionlistwidget.cpp qgssqlcomposerdialog.cpp @@ -527,6 +528,7 @@ SET(QGIS_GUI_MOC_HDRS qgssearchquerybuilder.h qgsshortcutsmanager.h qgsslider.h + qgssnaptogridcanvasitem.h qgssqlcomposerdialog.h qgsstatusbar.h qgssublayersdialog.h diff --git a/src/gui/qgsmapmouseevent.cpp b/src/gui/qgsmapmouseevent.cpp index ac60a876629..a995a0da8b1 100644 --- a/src/gui/qgsmapmouseevent.cpp +++ b/src/gui/qgsmapmouseevent.cpp @@ -71,6 +71,30 @@ void QgsMapMouseEvent::setMapPoint( const QgsPointXY &point ) mPixelPoint = mapToPixelCoordinates( point ); } +void QgsMapMouseEvent::snapToGrid( double precision, const QgsCoordinateReferenceSystem &crs ) +{ + if ( precision <= 0 ) + return; + + try + { + QgsCoordinateTransform ct( mMapCanvas->mapSettings().destinationCrs(), crs, mMapCanvas->mapSettings().transformContext() ); + + QgsPointXY pt = ct.transform( mMapPoint ); + + pt.setX( std::round( pt.x() / precision ) * precision ); + pt.setY( std::round( pt.y() / precision ) * precision ); + + pt = ct.transform( pt, QgsCoordinateTransform::ReverseTransform ); + + setMapPoint( pt ); + } + catch ( QgsCsException &e ) + { + Q_UNUSED( e ) + } +} + QPoint QgsMapMouseEvent::mapToPixelCoordinates( const QgsPointXY &point ) { double x = point.x(), y = point.y(); diff --git a/src/gui/qgsmapmouseevent.h b/src/gui/qgsmapmouseevent.h index dafa108ae51..6b722753edf 100644 --- a/src/gui/qgsmapmouseevent.h +++ b/src/gui/qgsmapmouseevent.h @@ -126,6 +126,15 @@ class GUI_EXPORT QgsMapMouseEvent : public QMouseEvent */ QPoint originalPixelPoint() const { return pos(); } + /** + * Snaps the mapPoint to a grid with the given \a precision. + * The snapping will be done in the specified \a crs. If this crs is + * different from the mapCanvas crs, it will be reprojected on the fly. + * + * \since QGIS 3.4 + */ + void snapToGrid( double precision, const QgsCoordinateReferenceSystem &crs ); + private: QPoint mapToPixelCoordinates( const QgsPointXY &point ); diff --git a/src/gui/qgsmaptooladvanceddigitizing.cpp b/src/gui/qgsmaptooladvanceddigitizing.cpp index bbf73e8c02c..e620fe555f2 100644 --- a/src/gui/qgsmaptooladvanceddigitizing.cpp +++ b/src/gui/qgsmaptooladvanceddigitizing.cpp @@ -17,11 +17,15 @@ #include "qgsmaptooladvanceddigitizing.h" #include "qgsmapcanvas.h" #include "qgsadvanceddigitizingdockwidget.h" +#include "qgsvectorlayer.h" +#include "qgsgeometryfixes.h" +#include "qgssnaptogridcanvasitem.h" QgsMapToolAdvancedDigitizing::QgsMapToolAdvancedDigitizing( QgsMapCanvas *canvas, QgsAdvancedDigitizingDockWidget *cadDockWidget ) : QgsMapToolEdit( canvas ) , mCadDockWidget( cadDockWidget ) { + connect( canvas, &QgsMapCanvas::currentLayerChanged, this, &QgsMapToolAdvancedDigitizing::onCurrentLayerChanged ); } void QgsMapToolAdvancedDigitizing::canvasPressEvent( QgsMapMouseEvent *e ) @@ -38,6 +42,12 @@ void QgsMapToolAdvancedDigitizing::canvasPressEvent( QgsMapMouseEvent *e ) e->snapPoint(); } + QgsVectorLayer *layer = currentVectorLayer(); + if ( mSnapToLayerGridEnabled && layer ) + { + e->snapToGrid( layer->geometryFixes()->geometryPrecision(), layer->crs() ); + } + cadCanvasPressEvent( e ); } @@ -72,6 +82,12 @@ void QgsMapToolAdvancedDigitizing::canvasReleaseEvent( QgsMapMouseEvent *e ) e->snapPoint(); } + QgsVectorLayer *layer = currentVectorLayer(); + if ( mSnapToLayerGridEnabled && layer ) + { + e->snapToGrid( layer->geometryFixes()->geometryPrecision(), layer->crs() ); + } + cadCanvasReleaseEvent( e ); } @@ -91,6 +107,13 @@ void QgsMapToolAdvancedDigitizing::canvasMoveEvent( QgsMapMouseEvent *e ) e->snapPoint(); } + QgsVectorLayer *layer = currentVectorLayer(); + if ( mSnapToLayerGridEnabled && layer ) + { + e->snapToGrid( layer->geometryFixes()->geometryPrecision(), layer->crs() ); + mSnapToGridCanvasItem->setPoint( e->mapPoint() ); + } + cadCanvasMoveEvent( e ); } @@ -99,6 +122,14 @@ void QgsMapToolAdvancedDigitizing::activate() QgsMapToolEdit::activate(); connect( mCadDockWidget, &QgsAdvancedDigitizingDockWidget::pointChanged, this, &QgsMapToolAdvancedDigitizing::cadPointChanged ); mCadDockWidget->enable(); + mSnapToGridCanvasItem = new QgsSnapToGridCanvasItem( mCanvas ); + QgsVectorLayer *layer = currentVectorLayer(); + if ( layer ) + { + mSnapToGridCanvasItem->setCrs( currentVectorLayer()->crs() ); + mSnapToGridCanvasItem->setPrecision( currentVectorLayer()->geometryFixes()->geometryPrecision() ); + } + mSnapToGridCanvasItem->setEnabled( mSnapToLayerGridEnabled ); } void QgsMapToolAdvancedDigitizing::deactivate() @@ -106,6 +137,8 @@ void QgsMapToolAdvancedDigitizing::deactivate() QgsMapToolEdit::deactivate(); disconnect( mCadDockWidget, &QgsAdvancedDigitizingDockWidget::pointChanged, this, &QgsMapToolAdvancedDigitizing::cadPointChanged ); mCadDockWidget->disable(); + delete mSnapToGridCanvasItem; + mSnapToGridCanvasItem = nullptr; } void QgsMapToolAdvancedDigitizing::cadPointChanged( const QgsPointXY &point ) @@ -114,3 +147,36 @@ void QgsMapToolAdvancedDigitizing::cadPointChanged( const QgsPointXY &point ) QMouseEvent *ev = new QMouseEvent( QEvent::MouseMove, mCanvas->mouseLastXY(), Qt::NoButton, Qt::NoButton, Qt::NoModifier ); qApp->postEvent( mCanvas->viewport(), ev ); // event queue will delete the event when processed } + +void QgsMapToolAdvancedDigitizing::onCurrentLayerChanged() +{ + if ( mSnapToGridCanvasItem ) + { + QgsVectorLayer *layer = currentVectorLayer(); + if ( layer && mSnapToLayerGridEnabled ) + { + mSnapToGridCanvasItem->setPrecision( layer->geometryFixes()->geometryPrecision() ); + mSnapToGridCanvasItem->setCrs( layer->crs() ); + } + + if ( !layer ) + mSnapToGridCanvasItem->setEnabled( false ); + else + mSnapToGridCanvasItem->setEnabled( mSnapToLayerGridEnabled ); + } +} + +bool QgsMapToolAdvancedDigitizing::snapToLayerGridEnabled() const +{ + return mSnapToLayerGridEnabled; +} + +void QgsMapToolAdvancedDigitizing::setSnapToLayerGridEnabled( bool snapToGridEnabled ) +{ + mSnapToLayerGridEnabled = snapToGridEnabled; + + if ( mSnapToGridCanvasItem ) + { + mSnapToGridCanvasItem->setEnabled( snapToGridEnabled ); + } +} diff --git a/src/gui/qgsmaptooladvanceddigitizing.h b/src/gui/qgsmaptooladvanceddigitizing.h index a3fba96435a..a0a659bf770 100644 --- a/src/gui/qgsmaptooladvanceddigitizing.h +++ b/src/gui/qgsmaptooladvanceddigitizing.h @@ -22,6 +22,7 @@ class QgsMapMouseEvent; class QgsAdvancedDigitizingDockWidget; +class QgsSnapToGridCanvasItem; /** * \ingroup gui @@ -140,6 +141,22 @@ class GUI_EXPORT QgsMapToolAdvancedDigitizing : public QgsMapToolEdit */ virtual void cadCanvasMoveEvent( QgsMapMouseEvent *e ) { Q_UNUSED( e ) } + /** + * Enables or disables snap to grid of mouse events. + * The snapping will occur in the layer's CRS. + * + * \since QGIS 3.4 + */ + bool snapToLayerGridEnabled() const; + + /** + * Enables or disables snap to grid of mouse events. + * The snapping will occur in the layer's CRS. + * + * \since QGIS 3.4 + */ + void setSnapToLayerGridEnabled( bool snapToLayerGridEnabled ); + private slots: /** @@ -152,6 +169,8 @@ class GUI_EXPORT QgsMapToolAdvancedDigitizing : public QgsMapToolEdit */ void cadPointChanged( const QgsPointXY &point ); + void onCurrentLayerChanged(); + private: QgsAdvancedDigitizingDockWidget *mCadDockWidget = nullptr; @@ -159,6 +178,9 @@ class GUI_EXPORT QgsMapToolAdvancedDigitizing : public QgsMapToolEdit bool mAdvancedDigitizingAllowed = true; //! Whether to snap mouse cursor to map before passing coordinates to cadCanvas*Event() bool mAutoSnapEnabled = true; + //! Whether to snap to grid before passing coordinates to cadCanvas*Event() + bool mSnapToLayerGridEnabled = true; + QgsSnapToGridCanvasItem *mSnapToGridCanvasItem = nullptr; }; #endif // QGSMAPTOOLADVANCEDDIGITIZE_H diff --git a/src/gui/qgssnaptogridcanvasitem.cpp b/src/gui/qgssnaptogridcanvasitem.cpp new file mode 100644 index 00000000000..6b677bb83fc --- /dev/null +++ b/src/gui/qgssnaptogridcanvasitem.cpp @@ -0,0 +1,170 @@ +/*************************************************************************** + qgssnaptogridcanvasitem.cpp + ---------------------- + begin : August 2018 + copyright : (C) 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 "qgssnaptogridcanvasitem.h" +#include "qgsmapcanvas.h" + +QgsSnapToGridCanvasItem::QgsSnapToGridCanvasItem( QgsMapCanvas *mapCanvas ) + : QgsMapCanvasItem( mapCanvas ) +{ + updateMapCanvasCrs(); + connect( mMapCanvas, &QgsMapCanvas::extentsChanged, this, &QgsSnapToGridCanvasItem::updateZoomFactor ); + connect( mMapCanvas, &QgsMapCanvas::destinationCrsChanged, this, &QgsSnapToGridCanvasItem::updateMapCanvasCrs ); +} + +void QgsSnapToGridCanvasItem::paint( QPainter *painter ) +{ + if ( !mEnabled || !mAvailableByZoomFactor ) + return; + + painter->save(); + QgsRectangle mapRect = mMapCanvas->extent(); + if ( rect() != mapRect ) + setRect( mapRect ); + + painter->setRenderHints( QPainter::Antialiasing ); + painter->setCompositionMode( QPainter::CompositionMode_Difference ); + + double scaleFactor = painter->fontMetrics().xHeight() * .2; + + mGridPen.setWidth( scaleFactor ); + mCurrentPointPen.setWidth( scaleFactor * 3 ); + const int gridMarkerLength = scaleFactor * 3; + + try + { + const QgsRectangle layerExtent = mTransform.transformBoundingBox( mapRect, QgsCoordinateTransform::ReverseTransform ); + const QgsPointXY layerPt = mTransform.transform( mPoint, QgsCoordinateTransform::ReverseTransform ); + + const double gridXMin = std::ceil( layerExtent.xMinimum() / mPrecision ) * mPrecision; + const double gridXMax = std::ceil( layerExtent.xMaximum() / mPrecision ) * mPrecision; + const double gridYMin = std::ceil( layerExtent.yMinimum() / mPrecision ) * mPrecision; + const double gridYMax = std::ceil( layerExtent.yMaximum() / mPrecision ) * mPrecision; + + for ( int x = gridXMin ; x < gridXMax; x += mPrecision ) + { + for ( int y = gridYMin ; y < gridYMax; y += mPrecision ) + { + const QgsPointXY pt = mTransform.transform( x, y ); + const QPointF canvasPt = toCanvasCoordinates( pt ); + + if ( qgsDoubleNear( layerPt.x(), x, mPrecision / 2 ) && qgsDoubleNear( layerPt.y(), y, mPrecision / 2 ) ) + { + painter->setPen( mCurrentPointPen ); + } + else + { + painter->setPen( mGridPen ); + } + painter->drawLine( canvasPt.x() - gridMarkerLength, canvasPt.y(), canvasPt.x() + gridMarkerLength, canvasPt.y() ); + painter->drawLine( canvasPt.x(), canvasPt.y() - gridMarkerLength, canvasPt.x(), canvasPt.y() + gridMarkerLength ); + + } + } + } + catch ( QgsCsException &e ) + { + Q_UNUSED( e ) + mAvailableByZoomFactor = false; + } + + painter->restore(); +} + +QgsPointXY QgsSnapToGridCanvasItem::point() const +{ + return mPoint; +} + +void QgsSnapToGridCanvasItem::setPoint( const QgsPointXY &point ) +{ + mPoint = point; + update(); +} + +double QgsSnapToGridCanvasItem::precision() const +{ + return mPrecision; +} + +void QgsSnapToGridCanvasItem::setPrecision( double precision ) +{ + mPrecision = precision; + updateZoomFactor(); +} + +QgsCoordinateReferenceSystem QgsSnapToGridCanvasItem::crs() const +{ + return mTransform.sourceCrs(); +} + +void QgsSnapToGridCanvasItem::setCrs( const QgsCoordinateReferenceSystem &crs ) +{ + mTransform.setSourceCrs( crs ); + updateZoomFactor(); +} + +bool QgsSnapToGridCanvasItem::enabled() const +{ + return mEnabled; +} + +void QgsSnapToGridCanvasItem::setEnabled( bool enabled ) +{ + mEnabled = enabled; + update(); +} + +void QgsSnapToGridCanvasItem::updateMapCanvasCrs() +{ + mTransform.setContext( mMapCanvas->mapSettings().transformContext() ); + mTransform.setDestinationCrs( mMapCanvas->mapSettings().destinationCrs() ); + update(); +} + + + +void QgsSnapToGridCanvasItem::updateZoomFactor() +{ + if ( !isVisible() ) + return; + + try + { + const int threshold = 5; + + const QgsPointXY centerPoint = mMapCanvas->extent().center(); + const QPointF canvasCenter = toCanvasCoordinates( centerPoint ); + + const QgsPointXY pt1 = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( canvasCenter.x() - threshold, canvasCenter.y() - threshold ); + const QgsPointXY pt2 = mMapCanvas->mapSettings().mapToPixel().toMapCoordinates( canvasCenter.x() + threshold, canvasCenter.y() + threshold ); + + const QgsPointXY layerPt1 = mTransform.transform( pt1, QgsCoordinateTransform::ReverseTransform ); + const QgsPointXY layerPt2 = mTransform.transform( pt2, QgsCoordinateTransform::ReverseTransform ); + + const double dist = layerPt1.distance( layerPt2 ); + + if ( dist < mPrecision ) + mAvailableByZoomFactor = true; + else + mAvailableByZoomFactor = false; + } + catch ( QgsCsException &e ) + { + // transform errors? + // you've probably got worse problems than the grid with your digitizing operations in the current projection. + mAvailableByZoomFactor = false; + } +} diff --git a/src/gui/qgssnaptogridcanvasitem.h b/src/gui/qgssnaptogridcanvasitem.h new file mode 100644 index 00000000000..31d35da2440 --- /dev/null +++ b/src/gui/qgssnaptogridcanvasitem.h @@ -0,0 +1,113 @@ +/*************************************************************************** + qgssnaptogridcanvasitem.h + ---------------------- + begin : August 2018 + copyright : (C) 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 QGSSNAPTOGRIDCANVASITEM_H +#define QGSSNAPTOGRIDCANVASITEM_H + +#include +#include + +#include "qgscoordinatereferencesystem.h" +#include "qgsmapcanvasitem.h" +#include "qgscoordinatetransform.h" + +/** + * \ingroup gui + * + * Shows a grid on the map canvas given a spatial resolution. + * + * \since QGIS 3.4 + */ +class GUI_EXPORT QgsSnapToGridCanvasItem : public QObject, public QgsMapCanvasItem +{ + Q_OBJECT + + public: + + /** + * Will automatically be added to the \a mapCanvas. + */ + QgsSnapToGridCanvasItem( QgsMapCanvas *mapCanvas SIP_TRANSFERTHIS ); + + void paint( QPainter *painter ) override; + + /** + * A point that will be highlighted on the map canvas. + * The point needs to be in map coordinates. The closest point on the + * grid will be highlighted. + */ + QgsPointXY point() const; + + /** + * A point that will be highlighted on the map canvas. + * The point needs to be in map coordinates. The closest point on the + * grid will be highlighted. + */ + void setPoint( const QgsPointXY &point ); + + /** + * The resolution of the grid in map units. + * If a crs has been specified it will be in CRS units. + */ + double precision() const; + + /** + * The resolution of the grid in map units. + * If a crs has been specified it will be in CRS units. + */ + void setPrecision( double precision ); + + /** + * The CRS in which the grid should be calculated. + * By default will be an invalid QgsCoordinateReferenceSystem and + * as such equal to the CRS of the map canvas. + */ + QgsCoordinateReferenceSystem crs() const; + + /** + * The CRS in which the grid should be calculated. + * By default will be an invalid QgsCoordinateReferenceSystem and + * as such equal to the CRS of the map canvas. + */ + void setCrs( const QgsCoordinateReferenceSystem &crs ); + + /** + * Enable this item. It will be hidden if disabled. + */ + bool enabled() const; + + /** + * Enable this item. It will be hidden if disabled. + */ + void setEnabled( bool enabled ); + + private slots: + void updateMapCanvasCrs(); + + void updateZoomFactor(); + + private: + QPen mGridPen = QPen( QColor( 127, 127, 127, 150 ) ); + QPen mCurrentPointPen = QPen( QColor( 200, 200, 200, 150 ) ); + + bool mEnabled = true; + bool mAvailableByZoomFactor = false; + + double mPrecision = 0.0; + QgsCoordinateTransform mTransform; + QgsPointXY mPoint; +}; + +#endif // QGSSNAPTOGRIDCANVASITEM_H