Rewrite addTopologicalPoints() and remove legacy snapping code + unit test

This commit is contained in:
Martin Dobias 2017-04-19 11:22:32 +08:00
parent cbf5e9c9c9
commit 24c5b0326f
12 changed files with 88 additions and 351 deletions

View File

@ -286,6 +286,7 @@ should now call QgsCoordinateReferenceSystem::invalidateCache() and QgsCoordinat
- QgsSymbologyV2Conversion was removed. Reading of renderers from pre-1.0 versions is not supported anymore.
- QgsTextAnnotationItem. Use QgsTextAnnotation instead.
- QgsSnapper. Use QgsSnappingUtils instead.
- QgsSnappingResult. Use QgsSnappingUtils instead.
- QgsMapCanvasSnapper. Use QgsMapCanvas::snappingUtils() instead.
@ -2095,6 +2096,9 @@ displayExpression instead. For the map tip use mapTipTemplate() instead.
- readXml() does not resolve references to joined layers. Call resolveReferences() when joined layers are available.
- snapWithContext(), snapToGeometry() - last argument has changed from QgsSnapper::SnappingType to QgsSnappingResult::SnappingType (no change in functionality).
- snapPoint() has been removed - use QgsPointLocator class instead.
- snapWithContext() has been removed - use QgsPointLocator class instead.
- insertSegmentVerticesForSnap() has been removed - use addTopologicalPoints() directly.
QgsVectorLayerEditBuffer {#qgis_api_break_3_0_QgsVectorLayerEditBuffer}
------------------------
@ -2110,6 +2114,7 @@ QgsVectorLayerEditUtils {#qgis_api_break_3_0_QgsVectorLayerEditUtils}
-----------------------
- addTopologicalPoints() now accepts a geometry reference, not a pointer.
- insertSegmentVerticesForSnap() has been removed.
QgsVectorLayerImport {#qgis_api_break_3_0_QgsVectorLayerImport}

View File

@ -137,7 +137,6 @@
%Include qgsscalecalculator.sip
%Include qgsscaleutils.sip
%Include qgssimplifymethod.sip
%Include qgssnapper.sip
%Include qgssnappingutils.sip
%Include qgsspatialindex.sip
%Include qgssqlstatement.sip

View File

@ -1,38 +0,0 @@
/** \ingroup core
* Represents the result of a snapping operation.
* */
struct QgsSnappingResult
{
%TypeHeaderCode
#include <qgssnapper.h>
%End
/** Snap to vertex, to segment or both*/
enum SnappingType
{
SnapToVertex,
SnapToSegment,
//snap to vertex and also to segment if no vertex is within the search tolerance
SnapToVertexAndSegment
};
/** The coordinates of the snapping result*/
QgsPoint snappedVertex;
/** The vertex index of snappedVertex
or -1 if no such vertex number (e.g. snap to segment)*/
int snappedVertexNr;
/** The layer coordinates of the vertex before snappedVertex*/
QgsPoint beforeVertex;
/** The index of the vertex before snappedVertex
or -1 if no such vertex*/
int beforeVertexNr;
/** The layer coordinates of the vertex after snappedVertex*/
QgsPoint afterVertex;
/** The index of the vertex after snappedVertex
or -1 if no such vertex*/
int afterVertexNr;
/** Index of the snapped geometry*/
qint64 snappedAtGeometry;
/** Layer where the snap occurred*/
const QgsVectorLayer* layer;
};

View File

@ -1043,15 +1043,6 @@ TODO QGIS 3.0 returns an enum instead of a magic constant
:rtype: int
%End
int insertSegmentVerticesForSnap( const QList<QgsSnappingResult> &snapResults );
%Docstring
Inserts vertices to the snapped segments.
This is useful for topological editing if snap to segment is enabled.
\param snapResults results collected from the snapping operation
:return: 0 in case of success
:rtype: int
%End
virtual bool isEditable() const;
@ -1068,20 +1059,6 @@ Returns true if the provider has been modified since the last commit
:rtype: bool
%End
int snapWithContext( const QgsPoint &startPoint,
double snappingTolerance,
QMultiMap < double, QgsSnappingResult > &snappingResults /Out/,
QgsSnappingResult::SnappingType snap_to );
%Docstring
Snaps to segment or vertex within given tolerance
\param startPoint point to snap (in layer coordinates)
\param snappingTolerance distance tolerance for snapping
\param snappingResults snapping results. Key is the distance between startPoint and snapping target
\param snap_to to segment / to vertex
:return: 0 in case of success
:rtype: int
%End
virtual void reload();
%Docstring
Synchronises with changes in the datasource

View File

@ -149,16 +149,10 @@ class QgsVectorLayerEditUtils
*/
int addTopologicalPoints( const QgsPoint& p );
/** Inserts vertices to the snapped segments.
* This is useful for topological editing if snap to segment is enabled.
* @param snapResults results collected from the snapping operation
* @return 0 in case of success
*/
int insertSegmentVerticesForSnap( const QList<QgsSnappingResult>& snapResults );
protected:
/** Little helper function that gives bounding box from a list of points.
@return 0 in case of success */
int boundingBoxFromPointList( const QList<QgsPoint>& list, double& xmin, double& ymin, double& xmax, double& ymax ) const;
};

View File

@ -769,7 +769,6 @@ SET(QGIS_CORE_HDRS
qgsscalecalculator.h
qgsscaleutils.h
qgssimplifymethod.h
qgssnapper.h
qgssnappingutils.h
qgsspatialindex.h
qgssqlexpressioncompiler.h

View File

@ -1,65 +0,0 @@
/***************************************************************************
qgssnapper.h
------------
begin : June 7, 2007
copyright : (C) 2007 by Marco Hugentobler
email : marco dot hugentobler at karto dot baug dot ethz dot 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 QGSSNAPPER_H
#define QGSSNAPPER_H
#include "qgis_core.h"
#include "qgspoint.h"
class QgsVectorLayer;
/** \ingroup core
* Represents the result of a snapping operation.
* */
// ### QGIS 3: remove from API
struct CORE_EXPORT QgsSnappingResult
{
//! Snap to vertex, to segment or both
enum SnappingType
{
SnapToVertex,
SnapToSegment,
//snap to vertex and also to segment if no vertex is within the search tolerance
SnapToVertexAndSegment
};
//! The coordinates of the snapping result
QgsPoint snappedVertex;
/** The vertex index of snappedVertex
or -1 if no such vertex number (e.g. snap to segment)*/
int snappedVertexNr;
//! The layer coordinates of the vertex before snappedVertex
QgsPoint beforeVertex;
/** The index of the vertex before snappedVertex
or -1 if no such vertex*/
int beforeVertexNr;
//! The layer coordinates of the vertex after snappedVertex
QgsPoint afterVertex;
/** The index of the vertex after snappedVertex
or -1 if no such vertex*/
int afterVertexNr;
//! Index of the snapped geometry
QgsFeatureId snappedAtGeometry;
//! Layer where the snap occurred
const QgsVectorLayer *layer = nullptr;
};
#endif

View File

@ -2582,127 +2582,6 @@ bool QgsVectorLayer::addFeatures( QgsFeatureList features, bool makeSelected )
}
int QgsVectorLayer::snapWithContext( const QgsPoint &startPoint, double snappingTolerance,
QMultiMap<double, QgsSnappingResult> &snappingResults,
QgsSnappingResult::SnappingType snap_to )
{
if ( !hasGeometryType() )
return 1;
if ( snappingTolerance <= 0 || !mDataProvider )
{
return 1;
}
QgsRectangle searchRect( startPoint.x() - snappingTolerance, startPoint.y() - snappingTolerance,
startPoint.x() + snappingTolerance, startPoint.y() + snappingTolerance );
double sqrSnappingTolerance = snappingTolerance * snappingTolerance;
int n = 0;
QgsFeature f;
if ( mCache->cachedGeometriesRect().contains( searchRect ) )
{
QgsGeometryMap &cachedGeometries = mCache->cachedGeometries();
for ( QgsGeometryMap::iterator it = cachedGeometries.begin(); it != cachedGeometries.end() ; ++it )
{
QgsGeometry g = it.value();
if ( g.boundingBox().intersects( searchRect ) )
{
snapToGeometry( startPoint, it.key(), g, sqrSnappingTolerance, snappingResults, snap_to );
++n;
}
}
}
else
{
// snapping outside cached area
QgsFeatureIterator fit = getFeatures( QgsFeatureRequest()
.setFilterRect( searchRect )
.setFlags( QgsFeatureRequest::ExactIntersect )
.setSubsetOfAttributes( QgsAttributeList() ) );
while ( fit.nextFeature( f ) )
{
snapToGeometry( startPoint, f.id(), f.geometry(), sqrSnappingTolerance, snappingResults, snap_to );
++n;
}
}
return n == 0 ? 2 : 0;
}
void QgsVectorLayer::snapToGeometry( const QgsPoint &startPoint,
QgsFeatureId featureId,
const QgsGeometry &geom,
double sqrSnappingTolerance,
QMultiMap<double, QgsSnappingResult> &snappingResults,
QgsSnappingResult::SnappingType snap_to ) const
{
if ( geom.isNull() )
{
return;
}
int atVertex, beforeVertex, afterVertex;
double sqrDistVertexSnap, sqrDistSegmentSnap;
QgsPoint snappedPoint;
QgsSnappingResult snappingResultVertex;
QgsSnappingResult snappingResultSegment;
if ( snap_to == QgsSnappingResult::SnapToVertex || snap_to == QgsSnappingResult::SnapToVertexAndSegment )
{
snappedPoint = geom.closestVertex( startPoint, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap );
if ( sqrDistVertexSnap < sqrSnappingTolerance )
{
snappingResultVertex.snappedVertex = snappedPoint;
snappingResultVertex.snappedVertexNr = atVertex;
snappingResultVertex.beforeVertexNr = beforeVertex;
if ( beforeVertex != -1 ) // make sure the vertex is valid
{
snappingResultVertex.beforeVertex = geom.vertexAt( beforeVertex );
}
snappingResultVertex.afterVertexNr = afterVertex;
if ( afterVertex != -1 ) // make sure the vertex is valid
{
snappingResultVertex.afterVertex = geom.vertexAt( afterVertex );
}
snappingResultVertex.snappedAtGeometry = featureId;
snappingResultVertex.layer = this;
snappingResults.insert( sqrt( sqrDistVertexSnap ), snappingResultVertex );
return;
}
}
if ( snap_to == QgsSnappingResult::SnapToSegment || snap_to == QgsSnappingResult::SnapToVertexAndSegment ) // snap to segment
{
if ( geometryType() != QgsWkbTypes::PointGeometry ) // cannot snap to segment for points/multipoints
{
sqrDistSegmentSnap = geom.closestSegmentWithContext( startPoint, snappedPoint, afterVertex, nullptr, crs().isGeographic() ? 1e-12 : 1e-8 );
if ( sqrDistSegmentSnap < sqrSnappingTolerance )
{
snappingResultSegment.snappedVertex = snappedPoint;
snappingResultSegment.snappedVertexNr = -1;
snappingResultSegment.beforeVertexNr = afterVertex - 1;
snappingResultSegment.afterVertexNr = afterVertex;
snappingResultSegment.snappedAtGeometry = featureId;
snappingResultSegment.beforeVertex = geom.vertexAt( afterVertex - 1 );
snappingResultSegment.afterVertex = geom.vertexAt( afterVertex );
snappingResultSegment.layer = this;
snappingResults.insert( sqrt( sqrDistSegmentSnap ), snappingResultSegment );
}
}
}
}
int QgsVectorLayer::insertSegmentVerticesForSnap( const QList<QgsSnappingResult> &snapResults )
{
QgsVectorLayerEditUtils utils( this );
return utils.insertSegmentVerticesForSnap( snapResults );
}
void QgsVectorLayer::setCoordinateSystem()
{
QgsDebugMsg( "----- Computing Coordinate System" );

View File

@ -32,7 +32,6 @@
#include "qgsfeature.h"
#include "qgsfeaturerequest.h"
#include "qgsfields.h"
#include "qgssnapper.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorsimplifymethod.h"
#include "qgseditformconfig.h"
@ -1029,13 +1028,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
*/
int addTopologicalPoints( const QgsPoint &p );
/** Inserts vertices to the snapped segments.
* This is useful for topological editing if snap to segment is enabled.
* \param snapResults results collected from the snapping operation
* \returns 0 in case of success
*/
int insertSegmentVerticesForSnap( const QList<QgsSnappingResult> &snapResults );
/** Access to labeling configuration.
* \since QGIS 2.12
* \note not available in Python bindings
@ -1056,18 +1048,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! Returns true if the provider has been modified since the last commit
virtual bool isModified() const;
/** Snaps to segment or vertex within given tolerance
* \param startPoint point to snap (in layer coordinates)
* \param snappingTolerance distance tolerance for snapping
* \param snappingResults snapping results. Key is the distance between startPoint and snapping target
* \param snap_to to segment / to vertex
* \returns 0 in case of success
*/
int snapWithContext( const QgsPoint &startPoint,
double snappingTolerance,
QMultiMap < double, QgsSnappingResult > &snappingResults SIP_OUT,
QgsSnappingResult::SnappingType snap_to );
//! Synchronises with changes in the datasource
virtual void reload() override;
@ -1904,21 +1884,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! Goes through all features and finds a free id (e.g. to give it temporarily to a not-committed feature)
QgsFeatureId findFreeId();
/** Snaps to a geometry and adds the result to the multimap if it is within the snapping result
* \param startPoint start point of the snap
* \param featureId id of feature
* \param geom geometry to snap
* \param sqrSnappingTolerance squared search tolerance of the snap
* \param snappingResults list to which the result is appended
* \param snap_to snap to vertex or to segment
*/
void snapToGeometry( const QgsPoint &startPoint,
QgsFeatureId featureId,
const QgsGeometry &geom,
double sqrSnappingTolerance,
QMultiMap<double, QgsSnappingResult> &snappingResults,
QgsSnappingResult::SnappingType snap_to ) const;
//! Add joined attributes to a feature
//void addJoinedAttributes( QgsFeature& f, bool all = false );

View File

@ -665,11 +665,7 @@ int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPoint &p )
if ( !L->hasGeometryType() )
return 1;
QMultiMap<double, QgsSnappingResult> snapResults; //results from the snapper object
//we also need to snap to vertex to make sure the vertex does not already exist in this geometry
QMultiMap<double, QgsSnappingResult> vertexSnapResults;
QList<QgsSnappingResult> filteredSnapResults; //we filter out the results that are on existing vertices
double segmentSearchEpsilon = L->crs().isGeographic() ? 1e-12 : 1e-8;
//work with a tolerance because coordinate projection may introduce some rounding
double threshold = 0.0000001;
@ -682,68 +678,57 @@ int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPoint &p )
threshold = 0.0001;
}
QgsRectangle searchRect( p.x() - threshold, p.y() - threshold,
p.x() + threshold, p.y() + threshold );
double sqrSnappingTolerance = threshold * threshold;
if ( L->snapWithContext( p, threshold, snapResults, QgsSnappingResult::SnapToSegment ) != 0 )
QgsFeature f;
QgsFeatureIterator fit = L->getFeatures( QgsFeatureRequest()
.setFilterRect( searchRect )
.setFlags( QgsFeatureRequest::ExactIntersect )
.setSubsetOfAttributes( QgsAttributeList() ) );
QMap<QgsFeatureId, QgsGeometry> features;
QMap<QgsFeatureId, int> segments;
while ( fit.nextFeature( f ) )
{
int afterVertex;
QgsPoint snappedPoint;
double sqrDistSegmentSnap = f.geometry().closestSegmentWithContext( p, snappedPoint, afterVertex, nullptr, segmentSearchEpsilon );
if ( sqrDistSegmentSnap < sqrSnappingTolerance )
{
segments[f.id()] = afterVertex;
features[f.id()] = f.geometry();
}
}
if ( segments.isEmpty() )
return 2;
}
QMultiMap<double, QgsSnappingResult>::const_iterator snap_it = snapResults.constBegin();
QMultiMap<double, QgsSnappingResult>::const_iterator vertex_snap_it;
for ( ; snap_it != snapResults.constEnd(); ++snap_it )
for ( QMap<QgsFeatureId, int>::const_iterator it = segments.constBegin(); it != segments.constEnd(); ++it )
{
//test if p is already a vertex of this geometry. If yes, don't insert it
bool vertexAlreadyExists = false;
if ( L->snapWithContext( p, threshold, vertexSnapResults, QgsSnappingResult::SnapToVertex ) != 0 )
{
continue;
}
QgsFeatureId fid = it.key();
int segmentAfterVertex = it.value();
QgsGeometry geom = features[fid];
vertex_snap_it = vertexSnapResults.constBegin();
for ( ; vertex_snap_it != vertexSnapResults.constEnd(); ++vertex_snap_it )
{
if ( snap_it.value().snappedAtGeometry == vertex_snap_it.value().snappedAtGeometry )
{
vertexAlreadyExists = true;
}
}
int atVertex, beforeVertex, afterVertex;
double sqrDistVertexSnap;
geom.closestVertex( p, atVertex, beforeVertex, afterVertex, sqrDistVertexSnap );
if ( !vertexAlreadyExists )
if ( sqrDistVertexSnap < sqrSnappingTolerance )
continue; // the vertex already exists - do not insert it
if ( !L->insertVertex( p.x(), p.y(), fid, segmentAfterVertex ) )
{
filteredSnapResults.push_back( *snap_it );
QgsDebugMsg( "failed to insert topo point" );
}
}
insertSegmentVerticesForSnap( filteredSnapResults );
return 0;
}
int QgsVectorLayerEditUtils::insertSegmentVerticesForSnap( const QList<QgsSnappingResult> &snapResults )
{
if ( !L->hasGeometryType() )
return 1;
int returnval = 0;
QgsPoint layerPoint;
QList<QgsSnappingResult>::const_iterator it = snapResults.constBegin();
for ( ; it != snapResults.constEnd(); ++it )
{
if ( it->snappedVertexNr == -1 ) // segment snap
{
layerPoint = it->snappedVertex;
if ( !insertVertex( layerPoint.x(), layerPoint.y(), it->snappedAtGeometry, it->afterVertexNr ) )
{
returnval = 3;
}
}
}
return returnval;
}
int QgsVectorLayerEditUtils::boundingBoxFromPointList( const QList<QgsPoint> &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
{
if ( list.size() < 1 )

View File

@ -176,19 +176,14 @@ class CORE_EXPORT QgsVectorLayerEditUtils
*/
int addTopologicalPoints( const QgsPoint &p );
/** Inserts vertices to the snapped segments.
* This is useful for topological editing if snap to segment is enabled.
* \param snapResults results collected from the snapping operation
* \returns 0 in case of success
*/
int insertSegmentVerticesForSnap( const QList<QgsSnappingResult> &snapResults );
protected:
/** Little helper function that gives bounding box from a list of points.
\returns 0 in case of success */
int boundingBoxFromPointList( const QList<QgsPoint> &list, double &xmin, double &ymin, double &xmax, double &ymax ) const;
private:
QgsVectorLayer *L = nullptr;
};

View File

@ -22,6 +22,7 @@
#include <QDesktopServices>
//qgis includes...
#include <qgsgeometry.h>
#include <qgsmaplayer.h>
#include <qgsvectordataprovider.h>
#include <qgsvectorlayer.h>
@ -103,6 +104,7 @@ class TestQgsVectorLayer : public QObject
void minimumValue();
void maximumValue();
void isSpatial();
void testAddTopologicalPoints();
};
void TestQgsVectorLayer::initTestCase()
@ -337,5 +339,45 @@ void TestQgsVectorLayer::isSpatial()
QVERIFY( !mpNonSpatialLayer->isSpatial() );
}
void TestQgsVectorLayer::testAddTopologicalPoints()
{
// create a simple linestring layer
QgsVectorLayer *layerLine = new QgsVectorLayer( "LineString?crs=EPSG:27700", "layer line", "memory" );
QVERIFY( layerLine->isValid() );
QgsPolyline line1;
line1 << QgsPoint( 2, 1 ) << QgsPoint( 1, 1 ) << QgsPoint( 1, 3 );
QgsFeature lineF1;
lineF1.setGeometry( QgsGeometry::fromPolyline( line1 ) );
layerLine->startEditing();
layerLine->addFeature( lineF1 );
QgsFeatureId fidLineF1 = lineF1.id();
QCOMPARE( layerLine->featureCount(), ( long )1 );
QCOMPARE( layerLine->undoStack()->index(), 1 );
// outside of the linestring - nothing should happen
layerLine->addTopologicalPoints( QgsPoint( 2, 2 ) );
QCOMPARE( layerLine->undoStack()->index(), 1 );
QCOMPARE( layerLine->getFeature( fidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
// add point at an existing vertex
layerLine->addTopologicalPoints( QgsPoint( 1, 1 ) );
QCOMPARE( layerLine->undoStack()->index(), 1 );
QCOMPARE( layerLine->getFeature( fidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 3)" ) );
// add point on segment of linestring
layerLine->addTopologicalPoints( QgsPoint( 1, 2 ) );
QCOMPARE( layerLine->undoStack()->index(), 2 );
QCOMPARE( layerLine->getFeature( fidLineF1 ).geometry(), QgsGeometry::fromWkt( "LINESTRING(2 1, 1 1, 1 2, 1 3)" ) );
delete layerLine;
}
QGSTEST_MAIN( TestQgsVectorLayer )
#include "testqgsvectorlayer.moc"