Skip to content

Commit

Permalink
fix(QgsMapToolCapture): Allow snapping point with different crs
Browse files Browse the repository at this point in the history
  • Loading branch information
lbartoletti authored and github-actions[bot] committed Feb 6, 2025
1 parent 87c8f73 commit a50c6b4
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,13 @@ Converts a point to map coordinates and layer coordinates

int fetchLayerPoint( const QgsPointLocator::Match &match, QgsPoint &layerPoint );
%Docstring
Fetches the original point from the source layer if it has the same
CRS as the current layer.
If topological editing is activated, the points are projected to the
current layer CRS.
Fetches the original point from the source layer.
If topological editing is activated.
The points are projected to the current layer CRS.

:return:
0 in case of success
1 if not applicable (CRS mismatch / invalid layer)
1 if not applicable (invalid layer)
2 in case of failure
%End

Expand Down
9 changes: 4 additions & 5 deletions python/gui/auto_generated/maptools/qgsmaptoolcapture.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,13 @@ Converts a point to map coordinates and layer coordinates

int fetchLayerPoint( const QgsPointLocator::Match &match, QgsPoint &layerPoint );
%Docstring
Fetches the original point from the source layer if it has the same
CRS as the current layer.
If topological editing is activated, the points are projected to the
current layer CRS.
Fetches the original point from the source layer.
If topological editing is activated.
The points are projected to the current layer CRS.

:return:
0 in case of success
1 if not applicable (CRS mismatch / invalid layer)
1 if not applicable (invalid layer)
2 in case of failure
%End

Expand Down
9 changes: 5 additions & 4 deletions src/gui/maptools/qgsmaptoolcapture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,6 @@ int QgsMapToolCapture::fetchLayerPoint( const QgsPointLocator::Match &match, Qgs
{
if ( ( match.hasVertex() || match.hasLineEndpoint() ) )
{
if ( sourceLayer->crs() != vlayer->crs() )
{
return 1;
}
QgsFeature f;
QgsFeatureRequest request;
request.setFilterFid( match.featureId() );
Expand All @@ -657,6 +653,7 @@ int QgsMapToolCapture::fetchLayerPoint( const QgsPointLocator::Match &match, Qgs
{
return 2;
}

layerPoint = f.geometry().constGet()->vertexAt( vId );
if ( QgsWkbTypes::hasZ( vlayer->wkbType() ) && !layerPoint.is3D() )
layerPoint.addZValue( defaultZValue() );
Expand All @@ -674,6 +671,10 @@ int QgsMapToolCapture::fetchLayerPoint( const QgsPointLocator::Match &match, Qgs
layerPoint.dropMValue();
}

if ( sourceLayer->crs() != vlayer->crs() )
{
layerPoint = toLayerCoordinates( vlayer, layerPoint );
}
return 0;
}
return 2;
Expand Down
9 changes: 4 additions & 5 deletions src/gui/maptools/qgsmaptoolcapture.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,12 @@ class GUI_EXPORT QgsMapToolCapture : public QgsMapToolAdvancedDigitizing
// TODO QGIS 4.0 returns an enum instead of a magic constant

/**
* Fetches the original point from the source layer if it has the same
* CRS as the current layer.
* If topological editing is activated, the points are projected to the
* current layer CRS.
* Fetches the original point from the source layer.
* If topological editing is activated.
* The points are projected to the current layer CRS.
* \returns
* 0 in case of success
* 1 if not applicable (CRS mismatch / invalid layer)
* 1 if not applicable (invalid layer)
* 2 in case of failure
*/
int fetchLayerPoint( const QgsPointLocator::Match &match, QgsPoint &layerPoint );
Expand Down
1 change: 1 addition & 0 deletions tests/src/app/maptooladdfeatureline/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ set(TESTS
testqgsmaptooladdfeaturelinez.cpp
testqgsmaptooladdfeaturelinem.cpp
testqgsmaptooladdfeaturelinezm.cpp
testqgsmaptooladdfeaturelinezm_crs.cpp
)

foreach(TESTSRC ${TESTS})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/***************************************************************************
testqgsmaptooladdfeaturelinezm.cpp
----------------------
Date : February 2025
Copyright : (C) 2025 by Loïc Bartoletti
Email : loic dot bartoletti at oslandia dot com
***************************************************************************
* *
* 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 "qgstest.h"
#include "qgisapp.h"
#include "qgsadvanceddigitizingdockwidget.h"
#include "qgsgeometry.h"
#include "qgsmapcanvas.h"
#include "qgsmapcanvassnappingutils.h"
#include "qgssnappingconfig.h"
#include "qgsmaptooladdfeature.h"
#include "qgsproject.h"
#include "qgssettings.h"
#include "qgssettingsregistrycore.h"
#include "qgsvectorlayer.h"
#include "qgswkbtypes.h"
#include "qgsmapmouseevent.h"
#include <QTest>
#include <QDebug>

// Comparison operator for QgsGeometry.
bool operator==( const QgsGeometry &g1, const QgsGeometry &g2 )
{
if ( g1.isNull() && g2.isNull() )
return true;
else
return g1.equals( g2 );
}

namespace QTest
{
// Pretty-printing for QgsGeometry.
template<> char *toString( const QgsGeometry &geom )
{
QByteArray ba = geom.asWkt().toLatin1();
return qstrdup( ba.data() );
}
} // namespace QTest

class TestQgsMapToolAddFeatureLineZMCRS : public QObject
{
Q_OBJECT
public:
TestQgsMapToolAddFeatureLineZMCRS() = default;

private slots:
void initTestCase();
void cleanupTestCase();
void testZMCRS();

private:
QgisApp *mQgisApp = nullptr;
QgsMapCanvas *mCanvas = nullptr;
QgsMapToolAddFeature *mCaptureTool = nullptr;
QgsVectorLayer *mCaptureLayer = nullptr;
QgsVectorLayer *mSnappingLayer = nullptr;
};

void TestQgsMapToolAddFeatureLineZMCRS::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();
mQgisApp = new QgisApp();


mCanvas = new QgsMapCanvas();
mCanvas->setDestinationCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) );
mCanvas->setFrameStyle( QFrame::NoFrame );
mCanvas->resize( 512, 512 );
mCanvas->setExtent( QgsRectangle( 0, 0, 10, 10 ) );
mCanvas->show();


mCaptureLayer = new QgsVectorLayer( QStringLiteral( "LineStringZM?crs=EPSG:3857" ), QStringLiteral( "Line Capture Layer" ), QStringLiteral( "memory" ) );
QVERIFY( mCaptureLayer->isValid() );
QgsProject::instance()->addMapLayers( { mCaptureLayer } );


mSnappingLayer = new QgsVectorLayer( QStringLiteral( "PointZM?crs=EPSG:4326" ), QStringLiteral( "Snapping Points" ), QStringLiteral( "memory" ) );
QVERIFY( mSnappingLayer->isValid() );

QgsFeature snapFeature( mSnappingLayer->fields() );
QgsPoint snapPoint( 7, 4, 999, 888 );
snapFeature.setGeometry( QgsGeometry::fromPoint( snapPoint ) );
mSnappingLayer->startEditing();
mSnappingLayer->addFeature( snapFeature );
mSnappingLayer->commitChanges();
QCOMPARE( mSnappingLayer->featureCount(), ( long ) 1 );


QgsProject::instance()->addMapLayers( { mSnappingLayer, mCaptureLayer } );
mCanvas->setLayers( { mCaptureLayer, mSnappingLayer } );
mCanvas->setSnappingUtils( new QgsMapCanvasSnappingUtils( mCanvas, this ) );
}

void TestQgsMapToolAddFeatureLineZMCRS::cleanupTestCase()
{
delete mCaptureTool;
delete mCanvas;
QgsApplication::exitQgis();
}

void TestQgsMapToolAddFeatureLineZMCRS::testZMCRS()
{
QgsSettingsRegistryCore::settingsDigitizingDefaultMValue->setValue( 222 );
QgsSettingsRegistryCore::settingsDigitizingDefaultZValue->setValue( 456 );

mCanvas->setCurrentLayer( mCaptureLayer );
mCaptureLayer->startEditing();

QgsSnappingConfig snapConfig = mCanvas->snappingUtils()->config();
snapConfig.setEnabled( true );
snapConfig.setTolerance( 2 );
snapConfig.setUnits( Qgis::MapToolUnit::Layer );
snapConfig.setMode( Qgis::SnappingMode::AllLayers );
snapConfig.setTypeFlag( Qgis::SnappingType::Vertex );
mCanvas->snappingUtils()->setConfig( snapConfig );
mCanvas->snappingUtils()->locatorForLayer( mSnappingLayer )->init();
mCanvas->snappingUtils()->locatorForLayer( mCaptureLayer )->init();
mCaptureTool = new QgsMapToolAddFeature( mCanvas, QgisApp::instance()->cadDockWidget(), QgsMapToolCapture::CaptureLine );
mCanvas->setMapTool( mCaptureTool );

QSet<QgsFeatureId> oldFids;
QgsFeatureIterator it = mCaptureLayer->getFeatures();
QgsFeature f;
while ( it.nextFeature( f ) )
oldFids.insert( f.id() );

QgsPointXY p2XY = mCanvas->mapSettings().layerToMapCoordinates( mSnappingLayer, QgsPointXY( 7, 4 ) );
QPoint p1( int( ( 0.0 / 10 ) * 512 ), int( ( ( 10 - 1.0 ) / 10 ) * 512 ) ); // (0,1)
QPoint p2( int( ( p2XY.x() / 10 ) * 512 ), int( ( ( 10 - p2XY.y() ) / 10 ) * 512 ) ); // (7,4) – should snap
QPoint p3( int( ( 2.0 / 10 ) * 512 ), int( ( ( 10 - 2.0 ) / 10 ) * 512 ) ); // (2,2) – should remain unsnapped

QTest::mouseClick( mCanvas->viewport(), Qt::LeftButton, Qt::NoModifier, p1 );
QTest::mouseMove( mCanvas->viewport(), p2 );
QTest::mouseClick( mCanvas->viewport(), Qt::LeftButton, Qt::NoModifier, p2 );
QTest::mouseClick( mCanvas->viewport(), Qt::LeftButton, Qt::NoModifier, p3 );
QTest::mouseClick( mCanvas->viewport(), Qt::RightButton, Qt::NoModifier, p3 );

QgsFeature newFeature;
QgsFeatureIterator itNew = mCaptureLayer->getFeatures();
while ( itNew.nextFeature( f ) )
{
if ( !oldFids.contains( f.id() ) )
{
newFeature = f;
break;
}
}
QVERIFY( newFeature.isValid() );

QString expectedWkt = QStringLiteral( "LineString ZM (0 1 456 222, 7 4 999 888, 2 2 456 222)" );
qDebug() << "Captured geometry:" << newFeature.geometry().asWkt();
QCOMPARE( newFeature.geometry().asWkt( 0 ), expectedWkt );
}

QGSTEST_MAIN( TestQgsMapToolAddFeatureLineZMCRS )
#include "testqgsmaptooladdfeaturelinezm_crs.moc"

0 comments on commit a50c6b4

Please sign in to comment.