Merge pull request #5791 from nyalldawson/geom_snapper_vertices

Fix geometry snapper sometimes creates unwanted overlapping segments when snapping line layers
This commit is contained in:
Nyall Dawson 2017-12-05 09:47:27 +11:00 committed by GitHub
commit 32ba5bf23f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 866 additions and 20 deletions

View File

@ -28,6 +28,8 @@ class QgsGeometrySnapper : QObject
{ {
PreferNodes, PreferNodes,
PreferClosest, PreferClosest,
PreferNodesNoExtraVertices,
PreferClosestNoExtraVertices,
EndPointPreferNodes, EndPointPreferNodes,
EndPointPreferClosest, EndPointPreferClosest,
EndPointToEndPoint, EndPointToEndPoint,

View File

@ -436,6 +436,29 @@ Returns the centroid of the geometry
:rtype: QgsAbstractGeometry :rtype: QgsAbstractGeometry
%End %End
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) = 0;
%Docstring
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a
degenerate geometry.
The ``epsilon`` parameter specifies the tolerance for coordinates when determining that
vertices are identical.
By default, z values are not considered when detecting duplicate nodes. E.g. two nodes
with the same x and y coordinate but different z values will still be considered
duplicate and one will be removed. If ``useZValues`` is true, then the z values are
also tested and nodes with the same x and y but different z will be maintained.
Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g.
a multipoint geometry with overlapping points will not be changed by this method.
The function will return true if nodes were removed, or false if no duplicate nodes
were found.
.. versionadded:: 3.0
:rtype: bool
%End
virtual double vertexAngle( QgsVertexId vertex ) const = 0; virtual double vertexAngle( QgsVertexId vertex ) const = 0;
%Docstring %Docstring
Returns approximate angle at a vertex. This is usually the average angle between adjacent Returns approximate angle at a vertex. This is usually the average angle between adjacent

View File

@ -83,6 +83,9 @@ class QgsCircularString: QgsCurve
virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; virtual QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
virtual void draw( QPainter &p ) const; virtual void draw( QPainter &p ) const;
virtual void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, virtual void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,

View File

@ -79,6 +79,8 @@ class QgsCompoundCurve: QgsCurve
virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; virtual QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
int nCurves() const; int nCurves() const;
%Docstring %Docstring

View File

@ -67,6 +67,8 @@ class QgsCurvePolygon: QgsSurface
virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; virtual QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
int numInteriorRings() const; int numInteriorRings() const;
%Docstring %Docstring

View File

@ -501,7 +501,7 @@ Returns true if WKB of the geometry is of WKBMulti* type
\param afterVertex Receives index of the vertex after the closest segment. The vertex \param afterVertex Receives index of the vertex after the closest segment. The vertex
before the closest segment is always afterVertex - 1 before the closest segment is always afterVertex - 1
\param leftOf Out: Returns if the point lies on the left of left side of the geometry ( < 0 means left, > 0 means right, 0 indicates \param leftOf Out: Returns if the point lies on the left of left side of the geometry ( < 0 means left, > 0 means right, 0 indicates
that the test was unsuccesful, e.g. for a point exactly on the line) that the test was unsuccessful, e.g. for a point exactly on the line)
\param epsilon epsilon for segment snapping \param epsilon epsilon for segment snapping
:return: The squared Cartesian distance is also returned in sqrDist, negative number on error :return: The squared Cartesian distance is also returned in sqrDist, negative number on error
:rtype: float :rtype: float
@ -688,6 +688,29 @@ Returns true if WKB of the geometry is of WKBMulti* type
:rtype: QgsGeometry :rtype: QgsGeometry
%End %End
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
%Docstring
Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a
degenerate geometry.
The ``epsilon`` parameter specifies the tolerance for coordinates when determining that
vertices are identical.
By default, z values are not considered when detecting duplicate nodes. E.g. two nodes
with the same x and y coordinate but different z values will still be considered
duplicate and one will be removed. If ``useZValues`` is true, then the z values are
also tested and nodes with the same x and y but different z will be maintained.
Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g.
a multipoint geometry with overlapping points will not be changed by this method.
The function will return true if nodes were removed, or false if no duplicate nodes
were found.
.. versionadded:: 3.0
:rtype: bool
%End
bool intersects( const QgsRectangle &r ) const; bool intersects( const QgsRectangle &r ) const;
%Docstring %Docstring
Tests for intersection with a rectangle (uses GEOS) Tests for intersection with a rectangle (uses GEOS)

View File

@ -52,6 +52,9 @@ class QgsGeometryCollection: QgsAbstractGeometry
virtual void clear(); virtual void clear();
virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
virtual QgsAbstractGeometry *boundary() const /Factory/; virtual QgsAbstractGeometry *boundary() const /Factory/;
virtual void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex /Out/, QgsVertexId &nextVertex /Out/ ) const; virtual void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex /Out/, QgsVertexId &nextVertex /Out/ ) const;

View File

@ -179,6 +179,8 @@ Closes the line string by appending the first point to the end of the line, if i
virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; virtual QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
virtual bool fromWkb( QgsConstWkbPtr &wkb ); virtual bool fromWkb( QgsConstWkbPtr &wkb );

View File

@ -339,6 +339,9 @@ class QgsPoint: QgsAbstractGeometry
virtual QgsPoint *clone() const /Factory/; virtual QgsPoint *clone() const /Factory/;
virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/; virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const /Factory/;
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
virtual void clear(); virtual void clear();
virtual bool fromWkb( QgsConstWkbPtr &wkb ); virtual bool fromWkb( QgsConstWkbPtr &wkb );

View File

@ -68,6 +68,9 @@ class CheckValidity(QgisAlgorithm):
def group(self): def group(self):
return self.tr('Vector geometry') return self.tr('Vector geometry')
def tags(self):
return self.tr('valid,invalid,detect').split(',')
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@ -79,7 +82,7 @@ class CheckValidity(QgisAlgorithm):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
self.tr('Input layer'))) self.tr('Input layer')))
self.addParameter(QgsProcessingParameterEnum(self.METHOD, self.addParameter(QgsProcessingParameterEnum(self.METHOD,
self.tr('Method'), self.methods)) self.tr('Method'), self.methods, defaultValue=2))
self.parameterDefinition(self.METHOD).setMetadata({ self.parameterDefinition(self.METHOD).setMetadata({
'widget_wrapper': { 'widget_wrapper': {
'class': 'processing.gui.wrappers.EnumWidgetWrapper', 'class': 'processing.gui.wrappers.EnumWidgetWrapper',

View File

@ -61,8 +61,10 @@ class SnapGeometriesToLayer(QgisAlgorithm):
self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), type=QgsProcessingParameterNumber.Double, self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), type=QgsProcessingParameterNumber.Double,
minValue=0.00000001, maxValue=9999999999, defaultValue=10.0)) minValue=0.00000001, maxValue=9999999999, defaultValue=10.0))
self.modes = [self.tr('Prefer aligning nodes'), self.modes = [self.tr('Prefer aligning nodes, insert extra vertices where required'),
self.tr('Prefer closest point'), self.tr('Prefer closest point, insert extra vertices where required'),
self.tr('Prefer aligning nodes, don\'t insert new vertices'),
self.tr('Prefer closest point, don\'t insert new vertices'),
self.tr('Move end points only, prefer aligning nodes'), self.tr('Move end points only, prefer aligning nodes'),
self.tr('Move end points only, prefer closest point'), self.tr('Move end points only, prefer closest point'),
self.tr('Snap end points to end points only')] self.tr('Snap end points to end points only')]

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ line_duplicate_nodes.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>2</gml:X><gml:Y>0</gml:Y></gml:coord>
<gml:coord><gml:X>3</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:line_duplicate_nodes fid="lines.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 3,2 3,3 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:line_duplicate_nodes>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="line_duplicate_nodes" type="ogr:line_duplicate_nodes_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="line_duplicate_nodes_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:LineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ removed_duplicated_nodes_line.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>2</gml:X><gml:Y>0</gml:Y></gml:coord>
<gml:coord><gml:X>3</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:removed_duplicated_nodes_line fid="lines.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 3,2 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:removed_duplicated_nodes_line>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="removed_duplicated_nodes_line" type="ogr:removed_duplicated_nodes_line_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="removed_duplicated_nodes_line_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:LineStringPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

@ -23,7 +23,7 @@
</gml:featureMember> </gml:featureMember>
<gml:featureMember> <gml:featureMember>
<ogr:snap_lines_to_lines fid="lines.2"> <ogr:snap_lines_to_lines fid="lines.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 2,2 3,2</gml:coordinates></gml:LineString></ogr:geometryProperty> <ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 3,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:snap_lines_to_lines> </ogr:snap_lines_to_lines>
</gml:featureMember> </gml:featureMember>
<gml:featureMember> <gml:featureMember>

View File

@ -13,7 +13,7 @@
<gml:featureMember> <gml:featureMember>
<ogr:snap_polys_to_polys fid="polys.0"> <ogr:snap_polys_to_polys fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty> <ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>aaaaa</ogr:name> <ogr:name>aaaaa</ogr:name>
<ogr:intval>33</ogr:intval> <ogr:intval>33</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval> <ogr:floatval>44.123456</ogr:floatval>

View File

@ -4582,3 +4582,15 @@ tests:
name: expected/difference.gml name: expected/difference.gml
type: vector type: vector
- algorithm: native:removeduplicatenodes
name: Remove duplicate nodes lines
params:
INPUT:
name: custom/line_duplicate_nodes.gml
type: vector
TOLERANCE: 1.0e-06
USE_Z_VALUE: false
results:
OUTPUT:
name: expected/removed_duplicated_nodes_line.gml
type: vector

View File

@ -51,6 +51,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmpackage.cpp processing/qgsalgorithmpackage.cpp
processing/qgsalgorithmpromotetomultipart.cpp processing/qgsalgorithmpromotetomultipart.cpp
processing/qgsalgorithmrasterlayeruniquevalues.cpp processing/qgsalgorithmrasterlayeruniquevalues.cpp
processing/qgsalgorithmremoveduplicatenodes
processing/qgsalgorithmremovenullgeometry.cpp processing/qgsalgorithmremovenullgeometry.cpp
processing/qgsalgorithmrenamelayer.cpp processing/qgsalgorithmrenamelayer.cpp
processing/qgsalgorithmsaveselectedfeatures.cpp processing/qgsalgorithmsaveselectedfeatures.cpp

View File

@ -0,0 +1,96 @@
/***************************************************************************
qgsalgorithmremoveduplicatenodes.cpp
---------------------
begin : November 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail 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 "qgsalgorithmremoveduplicatenodes.h"
///@cond PRIVATE
QString QgsAlgorithmRemoveDuplicateNodes::name() const
{
return QStringLiteral( "removeduplicatenodes" );
}
QString QgsAlgorithmRemoveDuplicateNodes::displayName() const
{
return QObject::tr( "Remove duplicate nodes" );
}
QStringList QgsAlgorithmRemoveDuplicateNodes::tags() const
{
return QObject::tr( "points,valid,overlapping" ).split( ',' );
}
QString QgsAlgorithmRemoveDuplicateNodes::group() const
{
return QObject::tr( "Vector geometry" );
}
QString QgsAlgorithmRemoveDuplicateNodes::outputName() const
{
return QObject::tr( "Cleaned" );
}
QString QgsAlgorithmRemoveDuplicateNodes::shortHelpString() const
{
return QObject::tr( "This algorithm removes duplicate nodes from features, wherever removing the nodes does "
"not result in a degenerate geometry.\n\n"
"The tolerance parameter specifies the tolerance for coordinates when determining whether "
"vertices are identical.\n\n"
"By default, z values are not considered when detecting duplicate nodes. E.g. two nodes "
"with the same x and y coordinate but different z values will still be considered "
"duplicate and one will be removed. If the Use Z Value parameter is true, then the z values are "
"also tested and nodes with the same x and y but different z will be maintained.\n\n"
"Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g. "
"a multipoint geometry with overlapping points will not be changed by this method." );
}
QgsAlgorithmRemoveDuplicateNodes *QgsAlgorithmRemoveDuplicateNodes::createInstance() const
{
return new QgsAlgorithmRemoveDuplicateNodes();
}
void QgsAlgorithmRemoveDuplicateNodes::initParameters( const QVariantMap & )
{
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "TOLERANCE" ),
QObject::tr( "Tolerance" ), QgsProcessingParameterNumber::Double,
0.000001, false, 0, 10000000.0 ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "USE_Z_VALUE" ),
QObject::tr( "Use Z Value" ), false ) );
}
bool QgsAlgorithmRemoveDuplicateNodes::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mTolerance = parameterAsDouble( parameters, QStringLiteral( "TOLERANCE" ), context );
mUseZValues = parameterAsBool( parameters, QStringLiteral( "USE_Z_VALUE" ), context );
return true;
}
QgsFeature QgsAlgorithmRemoveDuplicateNodes::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback * )
{
QgsFeature f = feature;
if ( f.hasGeometry() )
{
QgsGeometry geometry = f.geometry();
geometry.removeDuplicateNodes( mTolerance, mUseZValues );
f.setGeometry( geometry );
}
return f;
}
///@endcond

View File

@ -0,0 +1,62 @@
/***************************************************************************
qgsalgorithmremoveduplicatenodes.h
---------------------
begin : November 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail 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. *
* *
***************************************************************************/
#ifndef QGSALGORITHMREMOVEDUPLICATENODES_H
#define QGSALGORITHMREMOVEDUPLICATENODES_H
#define SIP_NO_FILE
#include "qgis.h"
#include "qgsprocessingalgorithm.h"
///@cond PRIVATE
/**
* Native remove duplicate nodes algorithm.
*/
class QgsAlgorithmRemoveDuplicateNodes : public QgsProcessingFeatureBasedAlgorithm
{
public:
QgsAlgorithmRemoveDuplicateNodes() = default;
QString name() const override;
QString displayName() const override;
virtual QStringList tags() const override;
QString group() const override;
QString shortHelpString() const override;
QgsAlgorithmRemoveDuplicateNodes *createInstance() const override SIP_FACTORY;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
protected:
QString outputName() const override;
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsFeature processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback *feedback ) override;
private:
double mTolerance = 1.0;
bool mUseZValues = false;
};
///@endcond PRIVATE
#endif // QGSALGORITHMSIMPLIFY_H

View File

@ -48,6 +48,7 @@
#include "qgsalgorithmpackage.h" #include "qgsalgorithmpackage.h"
#include "qgsalgorithmpromotetomultipart.h" #include "qgsalgorithmpromotetomultipart.h"
#include "qgsalgorithmrasterlayeruniquevalues.h" #include "qgsalgorithmrasterlayeruniquevalues.h"
#include "qgsalgorithmremoveduplicatenodes.h"
#include "qgsalgorithmremovenullgeometry.h" #include "qgsalgorithmremovenullgeometry.h"
#include "qgsalgorithmrenamelayer.h" #include "qgsalgorithmrenamelayer.h"
#include "qgsalgorithmsaveselectedfeatures.h" #include "qgsalgorithmsaveselectedfeatures.h"
@ -128,6 +129,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsPackageAlgorithm() ); addAlgorithm( new QgsPackageAlgorithm() );
addAlgorithm( new QgsPromoteToMultipartAlgorithm() ); addAlgorithm( new QgsPromoteToMultipartAlgorithm() );
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() ); addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
addAlgorithm( new QgsAlgorithmRemoveDuplicateNodes() );
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() ); addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
addAlgorithm( new QgsRenameLayerAlgorithm() ); addAlgorithm( new QgsRenameLayerAlgorithm() );
addAlgorithm( new QgsSaveSelectedFeatures() ); addAlgorithm( new QgsSaveSelectedFeatures() );

View File

@ -550,6 +550,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
switch ( mode ) switch ( mode )
{ {
case PreferNodes: case PreferNodes:
case PreferNodesNoExtraVertices:
case EndPointPreferNodes: case EndPointPreferNodes:
case EndPointToEndPoint: case EndPointToEndPoint:
{ {
@ -568,6 +569,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
} }
case PreferClosest: case PreferClosest:
case PreferClosestNoExtraVertices:
case EndPointPreferClosest: case EndPointPreferClosest:
{ {
QgsPoint nodeSnap, segmentSnap; QgsPoint nodeSnap, segmentSnap;
@ -605,8 +607,12 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) ) if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) )
return QgsGeometry( subjGeom ); return QgsGeometry( subjGeom );
//or for end point snapping //or for end point snapping
if ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint ) if ( mode == PreferClosestNoExtraVertices || mode == PreferNodesNoExtraVertices || mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
return QgsGeometry( subjGeom ); {
QgsGeometry result( subjGeom );
result.removeDuplicateNodes();
return result;
}
// SnapIndex for subject feature // SnapIndex for subject feature
std::unique_ptr< QgsSnapIndex > subjSnapIndex( new QgsSnapIndex( center, 10 * snapTolerance ) ); std::unique_ptr< QgsSnapIndex > subjSnapIndex( new QgsSnapIndex( center, 10 * snapTolerance ) );
@ -703,7 +709,9 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
} }
} }
return QgsGeometry( subjGeom ); QgsGeometry result( subjGeom );
result.removeDuplicateNodes();
return result;
} }
int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing ) int QgsGeometrySnapper::polyLineSize( const QgsAbstractGeometry *geom, int iPart, int iRing )

View File

@ -45,8 +45,10 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject
//! Snapping modes //! Snapping modes
enum SnapMode enum SnapMode
{ {
PreferNodes = 0, //!< Prefer to snap to nodes, even when a segment may be closer than a node PreferNodes = 0, //!< Prefer to snap to nodes, even when a segment may be closer than a node. New nodes will be inserted to make geometries follow each other exactly when inside allowable tolerance.
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment. New nodes will be inserted to make geometries follow each other exactly when inside allowable tolerance.
PreferNodesNoExtraVertices, //!< Prefer to snap to nodes, even when a segment may be closer than a node. No new nodes will be inserted.
PreferClosestNoExtraVertices, //!< Snap to closest point, regardless of it is a node or a segment. No new nodes will be inserted.
EndPointPreferNodes, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), prefer to snap to nodes EndPointPreferNodes, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), prefer to snap to nodes
EndPointPreferClosest, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), snap to closest point EndPointPreferClosest, //!< Only snap start/end points of lines (point features will also be snapped, polygon features will not be modified), snap to closest point
EndPointToEndPoint, //!< Only snap the start/end points of lines to other start/end points of lines EndPointToEndPoint, //!< Only snap the start/end points of lines to other start/end points of lines
@ -157,6 +159,7 @@ class ANALYSIS_EXPORT QgsInternalGeometrySnapper
QgsGeometrySnapper::SnapMode mMode = QgsGeometrySnapper::PreferNodes; QgsGeometrySnapper::SnapMode mMode = QgsGeometrySnapper::PreferNodes;
QgsSpatialIndex mProcessedIndex; QgsSpatialIndex mProcessedIndex;
QgsGeometryMap mProcessedGeometries; QgsGeometryMap mProcessedGeometries;
}; };
#ifndef SIP_RUN #ifndef SIP_RUN

View File

@ -443,6 +443,28 @@ class CORE_EXPORT QgsAbstractGeometry
*/ */
virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const = 0 SIP_FACTORY; virtual QgsAbstractGeometry *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const = 0 SIP_FACTORY;
/**
* Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a
* degenerate geometry.
*
* The \a epsilon parameter specifies the tolerance for coordinates when determining that
* vertices are identical.
*
* By default, z values are not considered when detecting duplicate nodes. E.g. two nodes
* with the same x and y coordinate but different z values will still be considered
* duplicate and one will be removed. If \a useZValues is true, then the z values are
* also tested and nodes with the same x and y but different z will be maintained.
*
* Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g.
* a multipoint geometry with overlapping points will not be changed by this method.
*
* The function will return true if nodes were removed, or false if no duplicate nodes
* were found.
*
* \since QGIS 3.0
*/
virtual bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) = 0;
/** /**
* Returns approximate angle at a vertex. This is usually the average angle between adjacent * Returns approximate angle at a vertex. This is usually the average angle between adjacent
* segments, and can be pictured as the orientation of a line following the curvature of the * segments, and can be pictured as the orientation of a line following the curvature of the

View File

@ -403,6 +403,57 @@ QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSp
return nullptr; return nullptr;
} }
bool QgsCircularString::removeDuplicateNodes( double epsilon, bool useZValues )
{
if ( mX.count() <= 3 )
return false; // don't create degenerate lines
bool result = false;
double prevX = mX.at( 0 );
double prevY = mY.at( 0 );
bool hasZ = is3D();
bool useZ = hasZ && useZValues;
double prevZ = useZ ? mZ.at( 0 ) : 0;
int i = 1;
int remaining = mX.count();
// we have to consider points in pairs, since a segment can validly have the same start and
// end if it has a different curve point
while ( i + 1 < remaining )
{
double currentCurveX = mX.at( i );
double currentCurveY = mY.at( i );
double currentX = mX.at( i + 1 );
double currentY = mY.at( i + 1 );
double currentZ = useZ ? mZ.at( i + 1 ) : 0;
if ( qgsDoubleNear( currentCurveX, prevX, epsilon ) &&
qgsDoubleNear( currentCurveY, prevY, epsilon ) &&
qgsDoubleNear( currentX, prevX, epsilon ) &&
qgsDoubleNear( currentY, prevY, epsilon ) &&
( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
{
result = true;
// remove point
mX.removeAt( i );
mX.removeAt( i );
mY.removeAt( i );
mY.removeAt( i );
if ( hasZ )
{
mZ.removeAt( i );
mZ.removeAt( i );
}
remaining -= 2;
}
else
{
prevX = currentX;
prevY = currentY;
prevZ = currentZ;
i += 2;
}
}
return result;
}
int QgsCircularString::numPoints() const int QgsCircularString::numPoints() const
{ {
return std::min( mX.size(), mY.size() ); return std::min( mX.size(), mY.size() );

View File

@ -73,6 +73,8 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
QgsPoint endPoint() const override; QgsPoint endPoint() const override;
QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY; QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY;
QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; QgsCircularString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
void draw( QPainter &p ) const override; void draw( QPainter &p ) const override;
void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform, void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
bool transformZ = false ) override; bool transformZ = false ) override;

View File

@ -408,6 +408,35 @@ QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpac
return result.release(); return result.release();
} }
bool QgsCompoundCurve::removeDuplicateNodes( double epsilon, bool useZValues )
{
bool result = false;
const QVector< QgsCurve * > curves = mCurves;
int i = 0;
QgsPoint lastEnd;
for ( QgsCurve *curve : curves )
{
result = result || curve->removeDuplicateNodes( epsilon, useZValues );
if ( curve->numPoints() == 0 || qgsDoubleNear( curve->length(), 0.0, epsilon ) )
{
// empty curve, remove it
delete mCurves.takeAt( i );
result = true;
}
else
{
// ensure this line starts exactly where previous line ended
if ( i > 0 )
{
curve->moveVertex( QgsVertexId( -1, -1, 0 ), lastEnd );
}
lastEnd = curve->vertexAt( QgsVertexId( -1, -1, curve->numPoints() - 1 ) );
}
i++;
}
return result;
}
const QgsCurve *QgsCompoundCurve::curveAt( int i ) const const QgsCurve *QgsCompoundCurve::curveAt( int i ) const
{ {
if ( i < 0 || i >= mCurves.size() ) if ( i < 0 || i >= mCurves.size() )

View File

@ -69,6 +69,7 @@ class CORE_EXPORT QgsCompoundCurve: public QgsCurve
QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY; QgsLineString *curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override SIP_FACTORY;
QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; QgsCompoundCurve *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
/** /**
* Returns the number of curves in the geometry. * Returns the number of curves in the geometry.

View File

@ -534,6 +534,37 @@ QgsCurvePolygon *QgsCurvePolygon::snappedToGrid( double hSpacing, double vSpacin
} }
bool QgsCurvePolygon::removeDuplicateNodes( double epsilon, bool useZValues )
{
bool result = false;
auto cleanRing = [this, &result, epsilon, useZValues ]( QgsCurve * ring )->bool
{
if ( ring->numPoints() <= 4 )
return false;
if ( ring->removeDuplicateNodes( epsilon, useZValues ) )
{
QgsPoint startPoint;
QgsVertexId::VertexType type;
ring->pointAt( 0, startPoint, type );
// ensure ring is properly closed - if we removed the final node, it may no longer be properly closed
ring->moveVertex( QgsVertexId( -1, -1, ring->numPoints() - 1 ), startPoint );
return true;
}
return false;
};
if ( mExteriorRing )
{
result = cleanRing( mExteriorRing.get() );
}
for ( QgsCurve *ring : qgis::as_const( mInteriorRings ) )
{
result = result || cleanRing( ring );
}
return result;
}
QgsPolygon *QgsCurvePolygon::toPolygon( double tolerance, SegmentationToleranceType toleranceType ) const QgsPolygon *QgsCurvePolygon::toPolygon( double tolerance, SegmentationToleranceType toleranceType ) const
{ {
std::unique_ptr< QgsPolygon > poly( new QgsPolygon() ); std::unique_ptr< QgsPolygon > poly( new QgsPolygon() );

View File

@ -63,6 +63,7 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
QgsPolygon *surfaceToPolygon() const override SIP_FACTORY; QgsPolygon *surfaceToPolygon() const override SIP_FACTORY;
QgsAbstractGeometry *boundary() const override SIP_FACTORY; QgsAbstractGeometry *boundary() const override SIP_FACTORY;
QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; QgsCurvePolygon *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
//curve polygon interface //curve polygon interface
int numInteriorRings() const; int numInteriorRings() const;

View File

@ -1076,6 +1076,15 @@ QgsGeometry QgsGeometry::snappedToGrid( double hSpacing, double vSpacing, double
return QgsGeometry( d->geometry->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) ); return QgsGeometry( d->geometry->snappedToGrid( hSpacing, vSpacing, dSpacing, mSpacing ) );
} }
bool QgsGeometry::removeDuplicateNodes( double epsilon, bool useZValues )
{
if ( !d->geometry )
return false;
detach();
return d->geometry->removeDuplicateNodes( epsilon, useZValues );
}
bool QgsGeometry::intersects( const QgsRectangle &r ) const bool QgsGeometry::intersects( const QgsRectangle &r ) const
{ {
QgsGeometry g = fromRect( r ); QgsGeometry g = fromRect( r );

View File

@ -556,7 +556,7 @@ class CORE_EXPORT QgsGeometry
* \param afterVertex Receives index of the vertex after the closest segment. The vertex * \param afterVertex Receives index of the vertex after the closest segment. The vertex
* before the closest segment is always afterVertex - 1 * before the closest segment is always afterVertex - 1
* \param leftOf Out: Returns if the point lies on the left of left side of the geometry ( < 0 means left, > 0 means right, 0 indicates * \param leftOf Out: Returns if the point lies on the left of left side of the geometry ( < 0 means left, > 0 means right, 0 indicates
* that the test was unsuccesful, e.g. for a point exactly on the line) * that the test was unsuccessful, e.g. for a point exactly on the line)
* \param epsilon epsilon for segment snapping * \param epsilon epsilon for segment snapping
* \returns The squared Cartesian distance is also returned in sqrDist, negative number on error * \returns The squared Cartesian distance is also returned in sqrDist, negative number on error
*/ */
@ -747,6 +747,28 @@ class CORE_EXPORT QgsGeometry
*/ */
QgsGeometry snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const; QgsGeometry snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const;
/**
* Removes duplicate nodes from the geometry, wherever removing the nodes does not result in a
* degenerate geometry.
*
* The \a epsilon parameter specifies the tolerance for coordinates when determining that
* vertices are identical.
*
* By default, z values are not considered when detecting duplicate nodes. E.g. two nodes
* with the same x and y coordinate but different z values will still be considered
* duplicate and one will be removed. If \a useZValues is true, then the z values are
* also tested and nodes with the same x and y but different z will be maintained.
*
* Note that duplicate nodes are not tested between different parts of a multipart geometry. E.g.
* a multipoint geometry with overlapping points will not be changed by this method.
*
* The function will return true if nodes were removed, or false if no duplicate nodes
* were found.
*
* \since QGIS 3.0
*/
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
//! Tests for intersection with a rectangle (uses GEOS) //! Tests for intersection with a rectangle (uses GEOS)
bool intersects( const QgsRectangle &r ) const; bool intersects( const QgsRectangle &r ) const;

View File

@ -102,6 +102,16 @@ QgsGeometryCollection *QgsGeometryCollection::snappedToGrid( double hSpacing, do
return result.release(); return result.release();
} }
bool QgsGeometryCollection::removeDuplicateNodes( double epsilon, bool useZValues )
{
bool result = false;
for ( QgsAbstractGeometry *geom : qgis::as_const( mGeometries ) )
{
result = result || geom->removeDuplicateNodes( epsilon, useZValues );
}
return result;
}
QgsAbstractGeometry *QgsGeometryCollection::boundary() const QgsAbstractGeometry *QgsGeometryCollection::boundary() const
{ {
return nullptr; return nullptr;

View File

@ -64,7 +64,8 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
int dimension() const override; int dimension() const override;
QString geometryType() const override; QString geometryType() const override;
void clear() override; void clear() override;
virtual QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; QgsGeometryCollection *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
QgsAbstractGeometry *boundary() const override SIP_FACTORY; QgsAbstractGeometry *boundary() const override SIP_FACTORY;
void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex SIP_OUT, QgsVertexId &nextVertex SIP_OUT ) const override; void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex SIP_OUT, QgsVertexId &nextVertex SIP_OUT ) const override;
int vertexNumberFromVertexId( QgsVertexId id ) const override; int vertexNumberFromVertexId( QgsVertexId id ) const override;

View File

@ -202,6 +202,46 @@ QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, d
return nullptr; return nullptr;
} }
bool QgsLineString::removeDuplicateNodes( double epsilon, bool useZValues )
{
if ( mX.count() <= 2 )
return false; // don't create degenerate lines
bool result = false;
double prevX = mX.at( 0 );
double prevY = mY.at( 0 );
bool hasZ = is3D();
bool useZ = hasZ && useZValues;
double prevZ = useZ ? mZ.at( 0 ) : 0;
int i = 1;
int remaining = mX.count();
while ( i < remaining )
{
double currentX = mX.at( i );
double currentY = mY.at( i );
double currentZ = useZ ? mZ.at( i ) : 0;
if ( qgsDoubleNear( currentX, prevX, epsilon ) &&
qgsDoubleNear( currentY, prevY, epsilon ) &&
( !useZ || qgsDoubleNear( currentZ, prevZ, epsilon ) ) )
{
result = true;
// remove point
mX.removeAt( i );
mY.removeAt( i );
if ( hasZ )
mZ.removeAt( i );
remaining--;
}
else
{
prevX = currentX;
prevY = currentY;
prevZ = currentZ;
i++;
}
}
return result;
}
bool QgsLineString::fromWkb( QgsConstWkbPtr &wkbPtr ) bool QgsLineString::fromWkb( QgsConstWkbPtr &wkbPtr )
{ {
if ( !wkbPtr ) if ( !wkbPtr )

View File

@ -180,6 +180,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve
void clear() override; void clear() override;
bool isEmpty() const override; bool isEmpty() const override;
QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; QgsLineString *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
bool fromWkb( QgsConstWkbPtr &wkb ) override; bool fromWkb( QgsConstWkbPtr &wkb ) override;
bool fromWkt( const QString &wkt ) override; bool fromWkt( const QString &wkt ) override;

View File

@ -137,6 +137,11 @@ QgsPoint *QgsPoint::snappedToGrid( double hSpacing, double vSpacing, double dSpa
return new QgsPoint( mWkbType, x, y, z, m ); return new QgsPoint( mWkbType, x, y, z, m );
} }
bool QgsPoint::removeDuplicateNodes( double, bool )
{
return false;
}
bool QgsPoint::fromWkb( QgsConstWkbPtr &wkbPtr ) bool QgsPoint::fromWkb( QgsConstWkbPtr &wkbPtr )
{ {
QgsWkbTypes::Type type = wkbPtr.readHeader(); QgsWkbTypes::Type type = wkbPtr.readHeader();

View File

@ -391,7 +391,8 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry
QString geometryType() const override; QString geometryType() const override;
int dimension() const override; int dimension() const override;
QgsPoint *clone() const override SIP_FACTORY; QgsPoint *clone() const override SIP_FACTORY;
virtual QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY; QgsPoint *snappedToGrid( double hSpacing, double vSpacing, double dSpacing = 0, double mSpacing = 0 ) const override SIP_FACTORY;
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
void clear() override; void clear() override;
bool fromWkb( QgsConstWkbPtr &wkb ) override; bool fromWkb( QgsConstWkbPtr &wkb ) override;
bool fromWkt( const QString &wkt ) override; bool fromWkt( const QString &wkt ) override;

View File

@ -261,8 +261,6 @@ void QgsGeometryValidator::run()
case QgsGeometry::ValidatorQgisInternal: case QgsGeometry::ValidatorQgisInternal:
{ {
QgsDebugMsg( "validation thread started." );
QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( mGeometry.wkbType() ); QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( mGeometry.wkbType() );
//if ( flatType == QgsWkbTypes::Point || flatType == QgsWkbTypes::MultiPoint ) //if ( flatType == QgsWkbTypes::Point || flatType == QgsWkbTypes::MultiPoint )
// break; // break;
@ -327,8 +325,6 @@ void QgsGeometryValidator::run()
mErrorCount++; mErrorCount++;
} }
QgsDebugMsg( "validation finished." );
if ( mStop ) if ( mStop )
{ {
emit errorFound( QgsGeometry::Error( QObject::tr( "Geometry validation was aborted." ) ) ); emit errorFound( QgsGeometry::Error( QObject::tr( "Geometry validation was aborted." ) ) );

View File

@ -596,6 +596,7 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
if ( !index.isValid() || if ( !index.isValid() ||
( role != Qt::TextAlignmentRole ( role != Qt::TextAlignmentRole
&& role != Qt::DisplayRole && role != Qt::DisplayRole
&& role != Qt::ToolTipRole
&& role != Qt::EditRole && role != Qt::EditRole
&& role != SortRole && role != SortRole
&& role != FeatureIdRole && role != FeatureIdRole
@ -647,6 +648,7 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
switch ( role ) switch ( role )
{ {
case Qt::DisplayRole: case Qt::DisplayRole:
case Qt::ToolTipRole:
return mFieldFormatters.at( index.column() )->representValue( layer(), fieldId, mWidgetConfigs.at( index.column() ), return mFieldFormatters.at( index.column() )->representValue( layer(), fieldId, mWidgetConfigs.at( index.column() ),
mAttributeWidgetCaches.at( index.column() ), val ); mAttributeWidgetCaches.at( index.column() ), val );

View File

@ -48,6 +48,8 @@ class TestQgsGeometrySnapper : public QObject
void endPointSnap(); void endPointSnap();
void endPointToEndPoint(); void endPointToEndPoint();
void internalSnapper(); void internalSnapper();
void insertExtra();
void duplicateNodes();
}; };
void TestQgsGeometrySnapper::initTestCase() void TestQgsGeometrySnapper::initTestCase()
@ -499,6 +501,117 @@ void TestQgsGeometrySnapper::internalSnapper()
QCOMPARE( res.value( 4 ).asWkt(), QStringLiteral( "LineString (0 0, 5 5, 10 10, 15 15)" ) ); QCOMPARE( res.value( 4 ).asWkt(), QStringLiteral( "LineString (0 0, 5 5, 10 10, 15 15)" ) );
} }
void TestQgsGeometrySnapper::insertExtra()
{
// test extra node insertion behavior
QgsGeometry refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(0 0, 0.1 0, 0.2 0, 9.8 0, 9.9 0, 10 0, 10.1 0, 10.2 0, 20 0)" ) );
QgsFeature f1( 1 );
f1.setGeometry( refGeom );
// inserting extra nodes
QgsInternalGeometrySnapper snapper( 2, QgsGeometrySnapper::PreferNodes );
QgsGeometry result = snapper.snapFeature( f1 );
QCOMPARE( result.asWkt(), f1.geometry().asWkt() );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0, 10 5)" ) );
QgsFeature f2( 2 );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9.8 0, 9.9 0, 10 0, 10.1 0, 10 5)" ) );
// reset snapper
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodes );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
// should 'follow' line for a bit
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 9.8 0, 9.9 0, 10 0)" ) );
// using PreferNodesNoExtraVertices mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0.1, 10 5)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9.8 0, 10 5)" ) );
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.1 0.1)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10.1 0)" ) );
// using PreferClosestNoExtraVertices mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferClosestNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(8 -5, 9 0.1, 10 5)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (8 -5, 9 0, 10 5)" ) );
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferClosestNoExtraVertices );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.1 0.1)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10.1 0)" ) );
// using EndPointPreferNodes mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointPreferNodes );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.02 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10 0)" ) );
// using EndPointPreferClosest mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointPreferClosest );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(7 -2, 10.02 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (7 -2, 10 0)" ) );
// using EndPointToEndPoint mode, no extra vertices should be inserted
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::EndPointToEndPoint );
result = snapper.snapFeature( f1 );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(-7 -2, 0.12 0)" ) );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (-7 -2, 0 0)" ) );
}
void TestQgsGeometrySnapper::duplicateNodes()
{
// test that snapper does not result in duplicate nodes
QgsGeometry refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(0 0, 20 0)" ) );
QgsFeature f1( 1 );
f1.setGeometry( refGeom );
QgsInternalGeometrySnapper snapper( 2, QgsGeometrySnapper::PreferNodes );
QgsGeometry result = snapper.snapFeature( f1 );
QCOMPARE( result.asWkt(), f1.geometry().asWkt() );
refGeom = QgsGeometry::fromWkt( QStringLiteral( "LineString(10 10, 19 0, 19.5 1, 20 0.1)" ) );
QgsFeature f2( 2 );
f2.setGeometry( refGeom );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (10 10, 20 0)" ) );
snapper = QgsInternalGeometrySnapper( 2, QgsGeometrySnapper::PreferNodesNoExtraVertices );
result = snapper.snapFeature( f1 );
QCOMPARE( result.asWkt(), f1.geometry().asWkt() );
result = snapper.snapFeature( f2 );
QCOMPARE( result.asWkt( 1 ), QStringLiteral( "LineString (10 10, 20 0)" ) );
}
QGSTEST_MAIN( TestQgsGeometrySnapper ) QGSTEST_MAIN( TestQgsGeometrySnapper )
#include "testqgsgeometrysnapper.moc" #include "testqgsgeometrysnapper.moc"

View File

@ -1053,6 +1053,12 @@ void TestQgsGeometry::point()
QCOMPARE( QgsPoint( 1, 2 ).segmentLength( QgsVertexId( -1, 0, 1 ) ), 0.0 ); QCOMPARE( QgsPoint( 1, 2 ).segmentLength( QgsVertexId( -1, 0, 1 ) ), 0.0 );
QCOMPARE( QgsPoint( 1, 2 ).segmentLength( QgsVertexId( -1, 0, -1 ) ), 0.0 ); QCOMPARE( QgsPoint( 1, 2 ).segmentLength( QgsVertexId( -1, 0, -1 ) ), 0.0 );
QCOMPARE( QgsPoint( 1, 2 ).segmentLength( QgsVertexId( 0, 0, 0 ) ), 0.0 ); QCOMPARE( QgsPoint( 1, 2 ).segmentLength( QgsVertexId( 0, 0, 0 ) ), 0.0 );
// remove duplicate points
QgsPoint p = QgsPoint( 1, 2 );
QVERIFY( !p.removeDuplicateNodes() );
QCOMPARE( p.x(), 1.0 );
QCOMPARE( p.y(), 2.0 );
} }
void TestQgsGeometry::circularString() void TestQgsGeometry::circularString()
@ -2364,6 +2370,48 @@ void TestQgsGeometry::circularString()
QCOMPARE( curveLine2.segmentLength( QgsVertexId( 0, 0, 1 ) ), 0.0 ); QCOMPARE( curveLine2.segmentLength( QgsVertexId( 0, 0, 1 ) ), 0.0 );
QGSCOMPARENEAR( curveLine2.segmentLength( QgsVertexId( 0, 0, 2 ) ), 31.4159, 0.001 ); QGSCOMPARENEAR( curveLine2.segmentLength( QgsVertexId( 0, 0, 2 ) ), 31.4159, 0.001 );
QCOMPARE( curveLine2.segmentLength( QgsVertexId( 0, 0, 3 ) ), 0.0 ); QCOMPARE( curveLine2.segmentLength( QgsVertexId( 0, 0, 3 ) ), 0.0 );
//removeDuplicateNodes
QgsCircularString nodeLine;
QVERIFY( !nodeLine.removeDuplicateNodes() );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) );
QVERIFY( !nodeLine.removeDuplicateNodes() );
QCOMPARE( nodeLine.asWkt(), QStringLiteral( "CircularString (11 2, 11 12, 111 12)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 11, 2 ) );
QVERIFY( !nodeLine.removeDuplicateNodes() );
QCOMPARE( nodeLine.asWkt(), QStringLiteral( "CircularString (11 2, 11 12, 11 2)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 10, 3 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 9, 3 )
<< QgsPoint( 11, 2 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularString (11 2, 10 3, 11.01 1.99, 9 3, 11 2)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) << QgsPoint( 111.01, 11.99 ) );
QVERIFY( !nodeLine.removeDuplicateNodes() );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularString (11 2, 11.01 1.99, 11.02 2.01, 11 12, 111 12, 111.01 11.99)" ) );
QVERIFY( nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularString (11 2, 11 12, 111 12, 111.01 11.99)" ) );
// don't create degenerate lines
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularString (11 2)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularString (11 2, 11.01 1.99)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11, 2 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularString (11 2, 11.01 1.99, 11 2)" ) );
// with z
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2, 1 ) << QgsPoint( 11.01, 1.99, 2 ) << QgsPoint( 11.02, 2.01, 3 )
<< QgsPoint( 11, 12, 4 ) << QgsPoint( 111, 12, 5 ) );
QVERIFY( nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularStringZ (11 2 1, 11 12 4, 111 12 5)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2, 1 ) << QgsPoint( 11.01, 1.99, 2 ) << QgsPoint( 11.02, 2.01, 3 )
<< QgsPoint( 11, 12, 4 ) << QgsPoint( 111, 12, 5 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02, true ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "CircularStringZ (11 2 1, 11.01 1.99 2, 11.02 2.01 3, 11 12 4, 111 12 5)" ) );
} }
@ -4162,6 +4210,38 @@ void TestQgsGeometry::lineString()
QCOMPARE( vertexLine3.segmentLength( QgsVertexId( -1, 0, 0 ) ), 10.0 ); QCOMPARE( vertexLine3.segmentLength( QgsVertexId( -1, 0, 0 ) ), 10.0 );
QCOMPARE( vertexLine3.segmentLength( QgsVertexId( 1, 0, 1 ) ), 100.0 ); QCOMPARE( vertexLine3.segmentLength( QgsVertexId( 1, 0, 1 ) ), 100.0 );
QCOMPARE( vertexLine3.segmentLength( QgsVertexId( 1, 1, 1 ) ), 100.0 ); QCOMPARE( vertexLine3.segmentLength( QgsVertexId( 1, 1, 1 ) ), 100.0 );
//removeDuplicateNodes
QgsLineString nodeLine;
QVERIFY( !nodeLine.removeDuplicateNodes() );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) );
QVERIFY( !nodeLine.removeDuplicateNodes() );
QCOMPARE( nodeLine.asWkt(), QStringLiteral( "LineString (11 2, 11 12, 111 12)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) << QgsPoint( 111.01, 11.99 ) );
QVERIFY( nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt(), QStringLiteral( "LineString (11 2, 11 12, 111 12)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) << QgsPoint( 111.01, 11.99 ) );
QVERIFY( !nodeLine.removeDuplicateNodes() );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "LineString (11 2, 11.01 1.99, 11.02 2.01, 11 12, 111 12, 111.01 11.99)" ) );
// don't create degenerate lines
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "LineString (11 2)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "LineString (11 2, 11.01 1.99)" ) );
// with z
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2, 1 ) << QgsPoint( 11.01, 1.99, 2 ) << QgsPoint( 11.02, 2.01, 3 )
<< QgsPoint( 11, 12, 4 ) << QgsPoint( 111, 12, 5 ) << QgsPoint( 111.01, 11.99, 6 ) );
QVERIFY( nodeLine.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeLine.asWkt(), QStringLiteral( "LineStringZ (11 2 1, 11 12 4, 111 12 5)" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2, 1 ) << QgsPoint( 11.01, 1.99, 2 ) << QgsPoint( 11.02, 2.01, 3 )
<< QgsPoint( 11, 12, 4 ) << QgsPoint( 111, 12, 5 ) << QgsPoint( 111.01, 11.99, 6 ) );
QVERIFY( !nodeLine.removeDuplicateNodes( 0.02, true ) );
QCOMPARE( nodeLine.asWkt( 2 ), QStringLiteral( "LineStringZ (11 2 1, 11.01 1.99 2, 11.02 2.01 3, 11 12 4, 111 12 5, 111.01 11.99 6)" ) );
} }
void TestQgsGeometry::polygon() void TestQgsGeometry::polygon()
@ -5881,6 +5961,30 @@ void TestQgsGeometry::polygon()
QCOMPARE( p30.segmentLength( QgsVertexId( 0, 1, 4 ) ), 0.0 ); QCOMPARE( p30.segmentLength( QgsVertexId( 0, 1, 4 ) ), 0.0 );
QCOMPARE( p30.segmentLength( QgsVertexId( 1, 0, 1 ) ), 100.0 ); QCOMPARE( p30.segmentLength( QgsVertexId( 1, 0, 1 ) ), 100.0 );
QCOMPARE( p30.segmentLength( QgsVertexId( 1, 1, 1 ) ), 2.0 ); QCOMPARE( p30.segmentLength( QgsVertexId( 1, 1, 1 ) ), 2.0 );
//removeDuplicateNodes
QgsPolygon nodePolygon;
QgsLineString nodeLine;
QVERIFY( !nodePolygon.removeDuplicateNodes() );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 11, 22 ) << QgsPoint( 11, 2 ) );
nodePolygon.setExteriorRing( nodeLine.clone() );
QVERIFY( !nodePolygon.removeDuplicateNodes() );
QCOMPARE( nodePolygon.asWkt(), QStringLiteral( "Polygon ((11 2, 11 12, 11 22, 11 2))" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 11, 22 ) << QgsPoint( 11.01, 21.99 ) << QgsPoint( 10.99, 1.99 ) << QgsPoint( 11, 2 ) );
nodePolygon.setExteriorRing( nodeLine.clone() );
QVERIFY( nodePolygon.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodePolygon.asWkt( 2 ), QStringLiteral( "Polygon ((11 2, 11 12, 11 22, 11 2))" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 11, 22 ) << QgsPoint( 11.01, 21.99 ) << QgsPoint( 10.99, 1.99 ) << QgsPoint( 11, 2 ) );
nodePolygon.setExteriorRing( nodeLine.clone() );
QVERIFY( !nodePolygon.removeDuplicateNodes() );
QCOMPARE( nodePolygon.asWkt( 2 ), QStringLiteral( "Polygon ((11 2, 11.01 1.99, 11.02 2.01, 11 12, 11 22, 11.01 21.99, 10.99 1.99, 11 2))" ) );
// don't create degenerate rings
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 2.01 ) << QgsPoint( 11, 2.01 ) << QgsPoint( 11, 2 ) );
nodePolygon.addInteriorRing( nodeLine.clone() );
QVERIFY( nodePolygon.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodePolygon.asWkt( 2 ), QStringLiteral( "Polygon ((11 2, 11 12, 11 22, 11 2),(11 2, 11.01 2.01, 11 2.01, 11 2))" ) );
} }
void TestQgsGeometry::triangle() void TestQgsGeometry::triangle()
@ -10523,6 +10627,52 @@ void TestQgsGeometry::compoundCurve()
QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 3 ) ), 0.0 ); QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 3 ) ), 0.0 );
QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 4 ) ), 9.0 ); QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 4 ) ), 9.0 );
QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 5 ) ), 0.0 ); QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 5 ) ), 0.0 );
//removeDuplicateNodes
QgsCompoundCurve nodeCurve;
QgsCircularString nodeLine;
QVERIFY( !nodeCurve.removeDuplicateNodes() );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) );
nodeCurve.addCurve( nodeLine.clone() );
QVERIFY( !nodeCurve.removeDuplicateNodes() );
QCOMPARE( nodeCurve.asWkt(), QStringLiteral( "CompoundCurve (CircularString (11 2, 11 12, 111 12))" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 11, 2 ) );
nodeCurve.clear();
nodeCurve.addCurve( nodeLine.clone() );
QVERIFY( !nodeCurve.removeDuplicateNodes() );
QCOMPARE( nodeCurve.asWkt(), QStringLiteral( "CompoundCurve (CircularString (11 2, 11 12, 11 2))" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 10, 3 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 9, 3 )
<< QgsPoint( 11, 2 ) );
nodeCurve.clear();
nodeCurve.addCurve( nodeLine.clone() );
QVERIFY( !nodeCurve.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeCurve.asWkt( 2 ), QStringLiteral( "CompoundCurve (CircularString (11 2, 10 3, 11.01 1.99, 9 3, 11 2))" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) << QgsPoint( 111.01, 11.99 ) );
nodeCurve.clear();
nodeCurve.addCurve( nodeLine.clone() );
QVERIFY( !nodeCurve.removeDuplicateNodes() );
QCOMPARE( nodeCurve.asWkt( 2 ), QStringLiteral( "CompoundCurve (CircularString (11 2, 11.01 1.99, 11.02 2.01, 11 12, 111 12, 111.01 11.99))" ) );
QVERIFY( nodeCurve.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeCurve.asWkt( 2 ), QStringLiteral( "CompoundCurve (CircularString (11 2, 11 12, 111 12, 111.01 11.99))" ) );
// with tiny segment
QgsLineString linePart;
linePart.setPoints( QgsPointSequence() << QgsPoint( 111.01, 11.99 ) << QgsPoint( 111, 12 ) );
nodeCurve.addCurve( linePart.clone() );
QVERIFY( !nodeCurve.removeDuplicateNodes() );
QCOMPARE( nodeCurve.asWkt( 2 ), QStringLiteral( "CompoundCurve (CircularString (11 2, 11 12, 111 12, 111.01 11.99),(111.01 11.99, 111 12))" ) );
QVERIFY( nodeCurve.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeCurve.asWkt( 2 ), QStringLiteral( "CompoundCurve (CircularString (11 2, 11 12, 111 12, 111.01 11.99))" ) );
// ensure continuity
nodeCurve.clear();
linePart.setPoints( QgsPointSequence() << QgsPoint( 1, 1 ) << QgsPoint( 111.01, 11.99 ) << QgsPoint( 111, 12 ) );
nodeCurve.addCurve( linePart.clone() );
linePart.setPoints( QgsPointSequence() << QgsPoint( 111, 12 ) << QgsPoint( 31, 33 ) );
nodeCurve.addCurve( linePart.clone() );
QVERIFY( nodeCurve.removeDuplicateNodes( 0.02 ) );
QCOMPARE( nodeCurve.asWkt( 2 ), QStringLiteral( "CompoundCurve ((1 1, 111.01 11.99),(111.01 11.99, 31 33))" ) );
} }
void TestQgsGeometry::multiPoint() void TestQgsGeometry::multiPoint()
@ -10994,6 +11144,14 @@ void TestQgsGeometry::multiPoint()
QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( 1, 0, 0 ) ), 1 ); QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( 1, 0, 0 ) ), 1 );
QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( 1, 0, 1 ) ), -1 ); QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( 1, 0, 1 ) ), -1 );
QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( -1, 0, 0 ) ), -1 ); QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( -1, 0, 0 ) ), -1 );
QgsMultiPoint mp;
// multipoints should not be affected by removeDuplicatePoints
QVERIFY( !mp.removeDuplicateNodes() );
mp.addGeometry( new QgsPoint( QgsWkbTypes::PointZM, 10, 1, 4, 8 ) );
mp.addGeometry( new QgsPoint( QgsWkbTypes::PointZM, 10, 1, 4, 8 ) );
QVERIFY( !mp.removeDuplicateNodes() );
QCOMPARE( mp.numGeometries(), 2 );
} }
void TestQgsGeometry::multiLineString() void TestQgsGeometry::multiLineString()
@ -14855,6 +15013,21 @@ void TestQgsGeometry::geometryCollection()
QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 1, 3 ) ), 17 ); QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 1, 3 ) ), 17 );
QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 1, 4 ) ), -1 ); QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 1, 4 ) ), -1 );
QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 2, 0 ) ), -1 ); QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 2, 0 ) ), -1 );
//removeDuplicateNodes
QgsGeometryCollection gcNodes;
QgsLineString nodeLine;
QVERIFY( !gcNodes.removeDuplicateNodes() );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) );
gcNodes.addGeometry( nodeLine.clone() );
QVERIFY( !gcNodes.removeDuplicateNodes( 0.02 ) );
QCOMPARE( gcNodes.asWkt(), QStringLiteral( "GeometryCollection (LineString (11 2, 11 12, 111 12))" ) );
nodeLine.setPoints( QgsPointSequence() << QgsPoint( 11, 2 ) << QgsPoint( 11.01, 1.99 ) << QgsPoint( 11.02, 2.01 )
<< QgsPoint( 11, 12 ) << QgsPoint( 111, 12 ) << QgsPoint( 111.01, 11.99 ) );
gcNodes.addGeometry( nodeLine.clone() );
QVERIFY( gcNodes.removeDuplicateNodes( 0.02 ) );
QCOMPARE( gcNodes.asWkt( 2 ), QStringLiteral( "GeometryCollection (LineString (11 2, 11 12, 111 12),LineString (11 2, 11 12, 111 12))" ) );
} }
void TestQgsGeometry::fromQgsPointXY() void TestQgsGeometry::fromQgsPointXY()

View File

@ -11,7 +11,7 @@ The Java keystore files are generated/edited using **KeyStore Explorer**:
The default password for the encrypted XCA project and Java keystore files is The default password for the encrypted XCA project and Java keystore files is
**password**. The certificate signing structure can be reviewed in **password**. The certificate signing structure can be reviewed in
`cert_heirarchy_8bit.png`. `cert_hierarchy_8bit.png`.
**WARNING**: These components are just for testing and should _NOT_ be used **WARNING**: These components are just for testing and should _NOT_ be used
in a production environment. in a production environment.
@ -22,7 +22,7 @@ filter file open dialogs to specific extensions, e.g. pgAdmin3 always filters
## Certificate Signing Hierarchy ## Certificate Signing Hierarchy
![Certs tree](cert_heirarchy_8bit.png) ![Certs tree](cert_hierarchy_8bit.png)
## Client Certificates/Keys ## Client Certificates/Keys

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB