Skip to content

Commit

Permalink
[FEATURE] New class QgsInternalGeometrySnapper
Browse files Browse the repository at this point in the history
Used for snapping geometries within a set of features to other
features from that same set.

Just like QgsGeometrySnapper, except that where QgsGeometrySnapper
requires a separate reference layer to snap to
QgsInternalGeometrySnapper snaps *within* a single layer. E.g.
allows you to close gaps within that layer.
  • Loading branch information
nyalldawson committed Mar 30, 2017
1 parent 6ea616e commit ece3991
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 8 deletions.
13 changes: 13 additions & 0 deletions python/analysis/vector/qgsgeometrysnapper.sip
Expand Up @@ -47,4 +47,17 @@ class QgsGeometrySnapper : QObject

//! Emitted each time a feature has been processed when calling snapFeatures()
void featureSnapped();

};

class QgsInternalGeometrySnapper
{
%TypeHeaderCode
#include <qgsgeometrysnapper.h>
%End

public:
QgsInternalGeometrySnapper( double snapTolerance, QgsGeometrySnapper::SnapMode mode = QgsGeometrySnapper::PreferNodes );
QgsGeometry snapFeature( const QgsFeature &feature );
QgsGeometryMap snappedGeometries() const;
};
66 changes: 59 additions & 7 deletions src/analysis/vector/qgsgeometrysnapper.cpp
Expand Up @@ -455,7 +455,9 @@ QgsSnapIndex::SnapItem *QgsSnapIndex::getSnapItem( const QgsPointV2 &pos, double
/// @endcond



//
// QgsGeometrySnapper
//

QgsGeometrySnapper::QgsGeometrySnapper( QgsVectorLayer *referenceLayer )
: mReferenceLayer( referenceLayer )
Expand All @@ -479,11 +481,10 @@ void QgsGeometrySnapper::processFeature( QgsFeature &feature, double snapToleran
feature.setGeometry( snapGeometry( feature.geometry(), snapTolerance, mode ) );
}



QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, SnapMode mode ) const
{
QgsPointV2 center = dynamic_cast< const QgsPointV2 * >( geometry.geometry() ) ? *static_cast< const QgsPointV2 * >( geometry.geometry() ) :
QgsPointV2( geometry.geometry()->boundingBox().center() );

// Get potential reference features and construct snap index
QList<QgsGeometry> refGeometries;
mIndexMutex.lock();
Expand All @@ -503,9 +504,16 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
}
mReferenceLayerMutex.unlock();

return snapGeometry( geometry, snapTolerance, refGeometries, mode );
}

QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, const QList<QgsGeometry> &referenceGeometries, QgsGeometrySnapper::SnapMode mode )
{
QgsPointV2 center = dynamic_cast< const QgsPointV2 * >( geometry.geometry() ) ? *static_cast< const QgsPointV2 * >( geometry.geometry() ) :
QgsPointV2( geometry.geometry()->boundingBox().center() );

QgsSnapIndex refSnapIndex( center, 10 * snapTolerance );
Q_FOREACH ( const QgsGeometry &geom, refGeometries )
Q_FOREACH ( const QgsGeometry &geom, referenceGeometries )
{
refSnapIndex.addGeometry( geom.geometry() );
}
Expand Down Expand Up @@ -600,7 +608,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
origSubjSnapIndex->addGeometry( origSubjGeom.get() );

// Pass 2: add missing vertices to subject geometry
Q_FOREACH ( const QgsGeometry &refGeom, refGeometries )
Q_FOREACH ( const QgsGeometry &refGeom, referenceGeometries )
{
for ( int iPart = 0, nParts = refGeom.geometry()->partCount(); iPart < nParts; ++iPart )
{
Expand Down Expand Up @@ -689,7 +697,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
return QgsGeometry( subjGeom );
}

int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing ) const
int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing )
{
int nVerts = geom->vertexCount( iPart, iRing );

Expand All @@ -703,3 +711,47 @@ int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart

return nVerts;
}





//
// QgsInternalGeometrySnapper
//

QgsInternalGeometrySnapper::QgsInternalGeometrySnapper( double snapTolerance, QgsGeometrySnapper::SnapMode mode )
: mSnapTolerance( snapTolerance )
, mMode( mode )
{}

QgsGeometry QgsInternalGeometrySnapper::snapFeature( const QgsFeature &feature )
{
if ( !feature.hasGeometry() )
return QgsGeometry();

QgsFeature feat = feature;
QgsGeometry geometry = feat.geometry();
if ( !mFirstFeature )
{
// snap against processed geometries
// Get potential reference features and construct snap index
QgsRectangle searchBounds = geometry.boundingBox();
searchBounds.grow( mSnapTolerance );
QgsFeatureIds refFeatureIds = mProcessedIndex.intersects( searchBounds ).toSet();
if ( !refFeatureIds.isEmpty() )
{
QList< QgsGeometry > refGeometries;
Q_FOREACH ( QgsFeatureId id, refFeatureIds )
{
refGeometries << mProcessedGeometries.value( id );
}

geometry = QgsGeometrySnapper::snapGeometry( geometry, mSnapTolerance, refGeometries, mMode );
}
}
mProcessedGeometries.insert( feat.id(), geometry );
mProcessedIndex.insertFeature( feat );
mFirstFeature = false;
return geometry;
}
56 changes: 55 additions & 1 deletion src/analysis/vector/qgsgeometrysnapper.h
Expand Up @@ -23,6 +23,7 @@
#include "qgsspatialindex.h"
#include "qgsabstractgeometry.h"
#include "qgspointv2.h"
#include "qgsgeometry.h"
#include "qgis_analysis.h"

class QgsVectorLayer;
Expand Down Expand Up @@ -69,6 +70,11 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject
*/
QgsFeatureList snapFeatures( const QgsFeatureList &features, double snapTolerance, SnapMode mode = PreferNodes );

/**
* Snaps a single geometry against a list of reference geometries.
*/
static QgsGeometry snapGeometry( const QgsGeometry &geometry, double snapTolerance, const QList<QgsGeometry> &referenceGeometries, SnapMode mode = PreferNodes );

signals:

//! Emitted each time a feature has been processed when calling snapFeatures()
Expand Down Expand Up @@ -99,7 +105,55 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject

void processFeature( QgsFeature &feature, double snapTolerance, SnapMode mode );

int polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing ) const;
static int polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing );

};


/**
* \class QgsInternalGeometrySnapper
* \ingroup analysis
* QgsInternalGeometrySnapper allows a set of geometries to be snapped to each other. It can be used to close gaps in layers.
*
* To use QgsInternalGeometrySnapper, first construct the snapper using the desired snap parameters. Then,
* features are fed to to the snapper one-by-one by calling snapFeature(). Each feature passed by calling
* snapFeature() will be snapped to any features which have already been processed by the snapper.
*
* After processing all desired features, the results can be fetched by calling snappedGeometries().
* The returned QgsGeometryMap can be passed to QgsVectorDataProvider::changeGeometryValues() to save
* the snapped geometries back to the source layer.
*
* \note added in QGIS 3.0
*/
class ANALYSIS_EXPORT QgsInternalGeometrySnapper
{

public:

/**
* Constructor for QgsInternalGeometrySnapper. The \a snapTolerance and \a mode parameters dictate
* how geometries will be snapped by the snapper.
*/
QgsInternalGeometrySnapper( double snapTolerance, QgsGeometrySnapper::SnapMode mode = QgsGeometrySnapper::PreferNodes );

/**
* Snaps a single feature's geometry against all feature geometries already processed by
* calls to snapFeature() in this object, and returns the snapped geometry.
*/
QgsGeometry snapFeature( const QgsFeature &feature );

/**
* Returns a QgsGeometryMap of all feature geometries snapped by this object.
*/
QgsGeometryMap snappedGeometries() const { return mProcessedGeometries; }

private:

bool mFirstFeature = true;
double mSnapTolerance = 0;
QgsGeometrySnapper::SnapMode mMode = QgsGeometrySnapper::PreferNodes;
QgsSpatialIndex mProcessedIndex;
QgsGeometryMap mProcessedGeometries;
};

///@cond PRIVATE
Expand Down
37 changes: 37 additions & 0 deletions tests/src/analysis/testqgsgeometrysnapper.cpp
Expand Up @@ -45,6 +45,7 @@ class TestQgsGeometrySnapper : public QObject
void snapPointToLine();
void snapPointToLinePreferNearest();
void snapPointToPolygon();
void internalSnapper();
};

void TestQgsGeometrySnapper::initTestCase()
Expand Down Expand Up @@ -409,6 +410,42 @@ void TestQgsGeometrySnapper::snapPointToPolygon()
QCOMPARE( result.exportToWkt(), QStringLiteral( "Point (10 0)" ) );
}

void TestQgsGeometrySnapper::internalSnapper()
{
QgsGeometry refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(0 0, 10 0, 10 10)" ) );
QgsFeature f1( 1 );
f1.setGeometry( refGeom );

QgsInternalGeometrySnapper snapper( 2 );
QgsGeometry result = snapper.snapFeature( f1 );
QCOMPARE( result.exportToWkt(), f1.geometry().exportToWkt() );

refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(5 5, 10 11, 15 15)" ) );
QgsFeature f2( 2 );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.exportToWkt(), QStringLiteral( "LineString (5 5, 10 10, 15 15)" ) );

refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(20 20, 30 20)" ) );
QgsFeature f3( 3 );
f3.setGeometry( refGeom );
result = snapper.snapFeature( f3 );
QCOMPARE( result.exportToWkt(), f3.geometry().exportToWkt() );

refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(0 -1, 5.5 5, 9.8 10, 14.5 14.8)" ) );
QgsFeature f4( 4 );
f4.setGeometry( refGeom );
result = snapper.snapFeature( f4 );
QCOMPARE( result.exportToWkt(), QStringLiteral( "LineString (0 0, 5 5, 10 10, 15 15)" ) );

QgsGeometryMap res = snapper.snappedGeometries();
QCOMPARE( res.count(), 4 );
QCOMPARE( res.value( 1 ).exportToWkt(), f1.geometry().exportToWkt() );
QCOMPARE( res.value( 2 ).exportToWkt(), QStringLiteral( "LineString (5 5, 10 10, 15 15)" ) );
QCOMPARE( res.value( 3 ).exportToWkt(), f3.geometry().exportToWkt() );
QCOMPARE( res.value( 4 ).exportToWkt(), QStringLiteral( "LineString (0 0, 5 5, 10 10, 15 15)" ) );
}


QGSTEST_MAIN( TestQgsGeometrySnapper )
#include "testqgsgeometrysnapper.moc"

0 comments on commit ece3991

Please sign in to comment.