Skip to content

Commit

Permalink
[FEATURE][processing] Snap geometries algorithm allows snapping
Browse files Browse the repository at this point in the history
to other layer types, supports point/line layers

Fix #14791, #15313
  • Loading branch information
nyalldawson committed Nov 7, 2016
1 parent c3a978b commit dae0a01
Show file tree
Hide file tree
Showing 9 changed files with 589 additions and 15 deletions.
5 changes: 0 additions & 5 deletions python/plugins/processing/algs/qgis/SnapGeometries.py
Expand Up @@ -31,7 +31,6 @@

from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.parameters import ParameterVector, ParameterNumber
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector

Expand All @@ -57,10 +56,6 @@ def processAlgorithm(self, progress):
reference_layer = dataobjects.getObjectFromUri(self.getParameterValue(self.REFERENCE_LAYER))
tolerance = self.getParameterValue(self.TOLERANCE)

if not layer.geometryType() == reference_layer.geometryType():
raise GeoAlgorithmExecutionException(
self.tr('Input layer and reference layer must have the same geometry type (eg both are line layers)'))

writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
layer.fields(), layer.wkbType(), layer.crs())

Expand Down
@@ -0,0 +1,16 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>snap_points_to_points</Name>
<ElementPath>snap_points_to_points</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>9</FeatureCount>
<ExtentXMin>0.20114</ExtentXMin>
<ExtentXMax>7.97127</ExtentXMax>
<ExtentYMin>-4.82759</ExtentYMin>
<ExtentYMax>2.74139</ExtentYMax>
</DatasetSpecificInfo>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>7.971265678449257</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:snap_points_to_points fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.971265678449257,0.609122006841505</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points_to_points fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points_to_points>
</gml:featureMember>
</ogr:FeatureCollection>
15 changes: 15 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -1417,3 +1417,18 @@ tests:
OUTPUT:
name: expected/snap_polys_to_polys.gml
type: vector

- algorithm: qgis:snapgeometriestolayer
name: Snap points to points
params:
INPUT:
name: snap_points.gml
type: vector
REFERENCE_LAYER:
name: points.gml
type: vector
TOLERANCE: 1.0
results:
OUTPUT:
name: expected/snap_points_to_points.gml
type: vector
16 changes: 16 additions & 0 deletions python/plugins/processing/tests/testdata/snap_points.gfs
@@ -0,0 +1,16 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>snap_points</Name>
<ElementPath>snap_points</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>9</FeatureCount>
<ExtentXMin>0.20114</ExtentXMin>
<ExtentXMax>7.97127</ExtentXMax>
<ExtentYMin>-4.82759</ExtentYMin>
<ExtentYMax>2.74139</ExtentYMax>
</DatasetSpecificInfo>
</GMLFeatureClass>
</GMLFeatureClassList>
59 changes: 59 additions & 0 deletions python/plugins/processing/tests/testdata/snap_points.gml
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0.2011402508551881</gml:X><gml:Y>-4.827594070695552</gml:Y></gml:coord>
<gml:coord><gml:X>7.971265678449257</gml:X><gml:Y>2.74139110604333</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:snap_points fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.134093500570125,2.74139110604333</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5.25860889395667,1.578563283922463</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3.683922462941847,0.961687571265678</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.201140250855188,-4.827594070695552</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.971265678449257,0.609122006841505</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7.181984036488029,-1.268187001140251</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
<gml:featureMember>
<ogr:snap_points fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0.220296465222349,-1.210718358038768</gml:coordinates></gml:Point></ogr:geometryProperty>
</ogr:snap_points>
</gml:featureMember>
</ogr:FeatureCollection>
42 changes: 32 additions & 10 deletions src/analysis/vector/qgsgeometrysnapper.cpp
Expand Up @@ -24,6 +24,8 @@
#include "qgsvectordataprovider.h"
#include "qgsgeometryutils.h"
#include "qgsmapsettings.h"
#include "qgssurface.h"
#include "qgscurve.h"

///@cond PRIVATE

Expand Down Expand Up @@ -328,7 +330,17 @@ void QgsSnapIndex::addGeometry( const QgsAbstractGeometry* geom )
{
for ( int iRing = 0, nRings = geom->ringCount( iPart ); iRing < nRings; ++iRing )
{
for ( int iVert = 0, nVerts = geom->vertexCount( iPart, iRing ) - 1; iVert < nVerts; ++iVert )
int nVerts = geom->vertexCount( iPart, iRing );

if ( dynamic_cast< const QgsSurface* >( geom ) )
nVerts--;
else if ( const QgsCurve* curve = dynamic_cast< const QgsCurve* >( geom ) )
{
if ( curve->isClosed() )
nVerts--;
}

for ( int iVert = 0; iVert < nVerts; ++iVert )
{
CoordIdx* idx = new CoordIdx( geom, QgsVertexId( iPart, iRing, iVert ) );
CoordIdx* idx1 = new CoordIdx( geom, QgsVertexId( iPart, iRing, iVert + 1 ) );
Expand Down Expand Up @@ -469,16 +481,15 @@ void QgsGeometrySnapper::processFeature( QgsFeature& feature )

QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry& geometry ) const
{
// can't snap to different geometry types
if ( geometry.type() != mReferenceLayer->geometryType() )
return geometry;

QgsPointV2 center = QgsPointV2( geometry.geometry()->boundingBox().center() );
QgsPointV2 center = dynamic_cast< const QgsPointV2* >( geometry.geometry() ) ? *dynamic_cast< const QgsPointV2* >( geometry.geometry() ) :
QgsPointV2( geometry.geometry()->boundingBox().center() );

// Get potential reference features and construct snap index
QList<QgsGeometry> refGeometries;
mIndexMutex.lock();
QgsFeatureIds refFeatureIds = mIndex.intersects( geometry.boundingBox() ).toSet();
QgsRectangle searchBounds = geometry.boundingBox();
searchBounds.grow( mSnapTolerance );
QgsFeatureIds refFeatureIds = mIndex.intersects( searchBounds ).toSet();
mIndexMutex.unlock();

QgsFeatureRequest refFeatureRequest = QgsFeatureRequest().setFilterFids( refFeatureIds ).setSubsetOfAttributes( QgsAttributeList() );
Expand Down Expand Up @@ -541,6 +552,10 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry& geometry ) cons
}
}

//nothing more to do for points
if ( dynamic_cast< const QgsPointV2* >( subjGeom ) )
return QgsGeometry( subjGeom );

// SnapIndex for subject feature
QgsSnapIndex* subjSnapIndex = new QgsSnapIndex( center, 10 * mSnapTolerance );
subjSnapIndex->addGeometry( subjGeom );
Expand Down Expand Up @@ -642,7 +657,14 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry& geometry ) cons
int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry* geom, int iPart, int iRing ) const
{
int nVerts = geom->vertexCount( iPart, iRing );
QgsPointV2 front = geom->vertexAt( QgsVertexId( iPart, iRing, 0 ) );
QgsPointV2 back = geom->vertexAt( QgsVertexId( iPart, iRing, nVerts - 1 ) );
return back == front ? nVerts - 1 : nVerts;

if ( dynamic_cast< const QgsSurface* >( geom ) )
{
QgsPointV2 front = geom->vertexAt( QgsVertexId( iPart, iRing, 0 ) );
QgsPointV2 back = geom->vertexAt( QgsVertexId( iPart, iRing, nVerts - 1 ) );
if ( front == back )
return nVerts - 1;
}

return nVerts;
}
1 change: 1 addition & 0 deletions tests/src/analysis/CMakeLists.txt
Expand Up @@ -77,6 +77,7 @@ ENDMACRO (ADD_QGIS_TEST)
# Tests:

ADD_QGIS_TEST(analyzertest testqgsvectoranalyzer.cpp)
ADD_QGIS_TEST(geometrysnappertest testqgsgeometrysnapper.cpp)
ADD_QGIS_TEST(openstreetmaptest testopenstreetmap.cpp)
ADD_QGIS_TEST(zonalstatisticstest testqgszonalstatistics.cpp)
ADD_QGIS_TEST(rastercalculatortest testqgsrastercalculator.cpp)
Expand Down

0 comments on commit dae0a01

Please sign in to comment.