mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-17 00:04:02 -04:00
[FEATURE] New class QgsInternalGeometrySnapper
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.
This commit is contained in:
parent
6ea616ef4b
commit
ece3991dbe
@ -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;
|
||||
};
|
||||
|
@ -455,7 +455,9 @@ QgsSnapIndex::SnapItem *QgsSnapIndex::getSnapItem( const QgsPointV2 &pos, double
|
||||
/// @endcond
|
||||
|
||||
|
||||
|
||||
//
|
||||
// QgsGeometrySnapper
|
||||
//
|
||||
|
||||
QgsGeometrySnapper::QgsGeometrySnapper( QgsVectorLayer *referenceLayer )
|
||||
: mReferenceLayer( referenceLayer )
|
||||
@ -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();
|
||||
@ -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() );
|
||||
}
|
||||
@ -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 )
|
||||
{
|
||||
@ -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 );
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "qgsspatialindex.h"
|
||||
#include "qgsabstractgeometry.h"
|
||||
#include "qgspointv2.h"
|
||||
#include "qgsgeometry.h"
|
||||
#include "qgis_analysis.h"
|
||||
|
||||
class QgsVectorLayer;
|
||||
@ -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()
|
||||
@ -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
|
||||
|
@ -45,6 +45,7 @@ class TestQgsGeometrySnapper : public QObject
|
||||
void snapPointToLine();
|
||||
void snapPointToLinePreferNearest();
|
||||
void snapPointToPolygon();
|
||||
void internalSnapper();
|
||||
};
|
||||
|
||||
void TestQgsGeometrySnapper::initTestCase()
|
||||
@ -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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user