mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
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:
commit
32ba5bf23f
@ -28,6 +28,8 @@ class QgsGeometrySnapper : QObject
|
||||
{
|
||||
PreferNodes,
|
||||
PreferClosest,
|
||||
PreferNodesNoExtraVertices,
|
||||
PreferClosestNoExtraVertices,
|
||||
EndPointPreferNodes,
|
||||
EndPointPreferClosest,
|
||||
EndPointToEndPoint,
|
||||
|
@ -436,6 +436,29 @@ Returns the centroid of the geometry
|
||||
:rtype: QgsAbstractGeometry
|
||||
%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;
|
||||
%Docstring
|
||||
Returns approximate angle at a vertex. This is usually the average angle between adjacent
|
||||
|
@ -83,6 +83,9 @@ class QgsCircularString: QgsCurve
|
||||
|
||||
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 transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
|
||||
|
@ -79,6 +79,8 @@ class QgsCompoundCurve: QgsCurve
|
||||
|
||||
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;
|
||||
%Docstring
|
||||
|
@ -67,6 +67,8 @@ class QgsCurvePolygon: QgsSurface
|
||||
|
||||
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;
|
||||
%Docstring
|
||||
|
@ -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
|
||||
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
|
||||
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
|
||||
:return: The squared Cartesian distance is also returned in sqrDist, negative number on error
|
||||
:rtype: float
|
||||
@ -688,6 +688,29 @@ Returns true if WKB of the geometry is of WKBMulti* type
|
||||
:rtype: QgsGeometry
|
||||
%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;
|
||||
%Docstring
|
||||
Tests for intersection with a rectangle (uses GEOS)
|
||||
|
@ -52,6 +52,9 @@ class QgsGeometryCollection: QgsAbstractGeometry
|
||||
virtual void clear();
|
||||
|
||||
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 void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex /Out/, QgsVertexId &nextVertex /Out/ ) const;
|
||||
|
@ -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 bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
|
||||
|
||||
|
||||
virtual bool fromWkb( QgsConstWkbPtr &wkb );
|
||||
|
||||
|
@ -339,6 +339,9 @@ class QgsPoint: QgsAbstractGeometry
|
||||
virtual QgsPoint *clone() 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 bool fromWkb( QgsConstWkbPtr &wkb );
|
||||
|
@ -68,6 +68,9 @@ class CheckValidity(QgisAlgorithm):
|
||||
def group(self):
|
||||
return self.tr('Vector geometry')
|
||||
|
||||
def tags(self):
|
||||
return self.tr('valid,invalid,detect').split(',')
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@ -79,7 +82,7 @@ class CheckValidity(QgisAlgorithm):
|
||||
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
|
||||
self.tr('Input layer')))
|
||||
self.addParameter(QgsProcessingParameterEnum(self.METHOD,
|
||||
self.tr('Method'), self.methods))
|
||||
self.tr('Method'), self.methods, defaultValue=2))
|
||||
self.parameterDefinition(self.METHOD).setMetadata({
|
||||
'widget_wrapper': {
|
||||
'class': 'processing.gui.wrappers.EnumWidgetWrapper',
|
||||
|
@ -61,8 +61,10 @@ class SnapGeometriesToLayer(QgisAlgorithm):
|
||||
self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), type=QgsProcessingParameterNumber.Double,
|
||||
minValue=0.00000001, maxValue=9999999999, defaultValue=10.0))
|
||||
|
||||
self.modes = [self.tr('Prefer aligning nodes'),
|
||||
self.tr('Prefer closest point'),
|
||||
self.modes = [self.tr('Prefer aligning nodes, insert extra vertices where required'),
|
||||
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 closest point'),
|
||||
self.tr('Snap end points to end points only')]
|
||||
|
19
python/plugins/processing/tests/testdata/custom/line_duplicate_nodes.gml
vendored
Normal file
19
python/plugins/processing/tests/testdata/custom/line_duplicate_nodes.gml
vendored
Normal 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>
|
23
python/plugins/processing/tests/testdata/custom/line_duplicate_nodes.xsd
vendored
Normal file
23
python/plugins/processing/tests/testdata/custom/line_duplicate_nodes.xsd
vendored
Normal 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>
|
19
python/plugins/processing/tests/testdata/expected/removed_duplicated_nodes_line.gml
vendored
Normal file
19
python/plugins/processing/tests/testdata/expected/removed_duplicated_nodes_line.gml
vendored
Normal 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>
|
23
python/plugins/processing/tests/testdata/expected/removed_duplicated_nodes_line.xsd
vendored
Normal file
23
python/plugins/processing/tests/testdata/expected/removed_duplicated_nodes_line.xsd
vendored
Normal 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>
|
@ -23,7 +23,7 @@
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
<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>
|
||||
</gml:featureMember>
|
||||
<gml:featureMember>
|
||||
|
@ -13,7 +13,7 @@
|
||||
|
||||
<gml:featureMember>
|
||||
<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:intval>33</ogr:intval>
|
||||
<ogr:floatval>44.123456</ogr:floatval>
|
||||
|
@ -4582,3 +4582,15 @@ tests:
|
||||
name: expected/difference.gml
|
||||
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
|
||||
|
@ -51,6 +51,7 @@ SET(QGIS_ANALYSIS_SRCS
|
||||
processing/qgsalgorithmpackage.cpp
|
||||
processing/qgsalgorithmpromotetomultipart.cpp
|
||||
processing/qgsalgorithmrasterlayeruniquevalues.cpp
|
||||
processing/qgsalgorithmremoveduplicatenodes
|
||||
processing/qgsalgorithmremovenullgeometry.cpp
|
||||
processing/qgsalgorithmrenamelayer.cpp
|
||||
processing/qgsalgorithmsaveselectedfeatures.cpp
|
||||
|
96
src/analysis/processing/qgsalgorithmremoveduplicatenodes.cpp
Normal file
96
src/analysis/processing/qgsalgorithmremoveduplicatenodes.cpp
Normal 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 ¶meters, 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
|
||||
|
||||
|
62
src/analysis/processing/qgsalgorithmremoveduplicatenodes.h
Normal file
62
src/analysis/processing/qgsalgorithmremoveduplicatenodes.h
Normal 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 ¶meters, 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
|
||||
|
||||
|
@ -48,6 +48,7 @@
|
||||
#include "qgsalgorithmpackage.h"
|
||||
#include "qgsalgorithmpromotetomultipart.h"
|
||||
#include "qgsalgorithmrasterlayeruniquevalues.h"
|
||||
#include "qgsalgorithmremoveduplicatenodes.h"
|
||||
#include "qgsalgorithmremovenullgeometry.h"
|
||||
#include "qgsalgorithmrenamelayer.h"
|
||||
#include "qgsalgorithmsaveselectedfeatures.h"
|
||||
@ -128,6 +129,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
|
||||
addAlgorithm( new QgsPackageAlgorithm() );
|
||||
addAlgorithm( new QgsPromoteToMultipartAlgorithm() );
|
||||
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
|
||||
addAlgorithm( new QgsAlgorithmRemoveDuplicateNodes() );
|
||||
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
|
||||
addAlgorithm( new QgsRenameLayerAlgorithm() );
|
||||
addAlgorithm( new QgsSaveSelectedFeatures() );
|
||||
|
@ -550,6 +550,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
|
||||
switch ( mode )
|
||||
{
|
||||
case PreferNodes:
|
||||
case PreferNodesNoExtraVertices:
|
||||
case EndPointPreferNodes:
|
||||
case EndPointToEndPoint:
|
||||
{
|
||||
@ -568,6 +569,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
|
||||
}
|
||||
|
||||
case PreferClosest:
|
||||
case PreferClosestNoExtraVertices:
|
||||
case EndPointPreferClosest:
|
||||
{
|
||||
QgsPoint nodeSnap, segmentSnap;
|
||||
@ -605,8 +607,12 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl
|
||||
if ( qgsgeometry_cast< const QgsPoint * >( subjGeom ) )
|
||||
return QgsGeometry( subjGeom );
|
||||
//or for end point snapping
|
||||
if ( mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
|
||||
return QgsGeometry( subjGeom );
|
||||
if ( mode == PreferClosestNoExtraVertices || mode == PreferNodesNoExtraVertices || mode == EndPointPreferClosest || mode == EndPointPreferNodes || mode == EndPointToEndPoint )
|
||||
{
|
||||
QgsGeometry result( subjGeom );
|
||||
result.removeDuplicateNodes();
|
||||
return result;
|
||||
}
|
||||
|
||||
// SnapIndex for subject feature
|
||||
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 )
|
||||
|
@ -45,8 +45,10 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject
|
||||
//! Snapping modes
|
||||
enum SnapMode
|
||||
{
|
||||
PreferNodes = 0, //!< Prefer to snap to nodes, even when a segment may be closer than a node
|
||||
PreferClosest, //!< Snap to closest point, regardless of it is a node or a segment
|
||||
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. 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
|
||||
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
|
||||
@ -157,6 +159,7 @@ class ANALYSIS_EXPORT QgsInternalGeometrySnapper
|
||||
QgsGeometrySnapper::SnapMode mMode = QgsGeometrySnapper::PreferNodes;
|
||||
QgsSpatialIndex mProcessedIndex;
|
||||
QgsGeometryMap mProcessedGeometries;
|
||||
|
||||
};
|
||||
|
||||
#ifndef SIP_RUN
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* segments, and can be pictured as the orientation of a line following the curvature of the
|
||||
|
@ -403,6 +403,57 @@ QgsCircularString *QgsCircularString::snappedToGrid( double hSpacing, double vSp
|
||||
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
|
||||
{
|
||||
return std::min( mX.size(), mY.size() );
|
||||
|
@ -73,6 +73,8 @@ class CORE_EXPORT QgsCircularString: public QgsCurve
|
||||
QgsPoint endPoint() const override;
|
||||
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;
|
||||
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
|
||||
|
||||
void draw( QPainter &p ) const override;
|
||||
void transform( const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
|
||||
bool transformZ = false ) override;
|
||||
|
@ -408,6 +408,35 @@ QgsCompoundCurve *QgsCompoundCurve::snappedToGrid( double hSpacing, double vSpac
|
||||
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
|
||||
{
|
||||
if ( i < 0 || i >= mCurves.size() )
|
||||
|
@ -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;
|
||||
|
||||
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.
|
||||
|
@ -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
|
||||
{
|
||||
std::unique_ptr< QgsPolygon > poly( new QgsPolygon() );
|
||||
|
@ -63,6 +63,7 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
|
||||
QgsPolygon *surfaceToPolygon() 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;
|
||||
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false ) override;
|
||||
|
||||
//curve polygon interface
|
||||
int numInteriorRings() const;
|
||||
|
@ -1076,6 +1076,15 @@ QgsGeometry QgsGeometry::snappedToGrid( double hSpacing, double vSpacing, double
|
||||
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
|
||||
{
|
||||
QgsGeometry g = fromRect( r );
|
||||
|
@ -556,7 +556,7 @@ class CORE_EXPORT QgsGeometry
|
||||
* \param afterVertex Receives index of the vertex after the closest segment. The vertex
|
||||
* 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
|
||||
* 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
|
||||
* \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;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
bool intersects( const QgsRectangle &r ) const;
|
||||
|
||||
|
@ -102,6 +102,16 @@ QgsGeometryCollection *QgsGeometryCollection::snappedToGrid( double hSpacing, do
|
||||
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
|
||||
{
|
||||
return nullptr;
|
||||
|
@ -64,7 +64,8 @@ class CORE_EXPORT QgsGeometryCollection: public QgsAbstractGeometry
|
||||
int dimension() const override;
|
||||
QString geometryType() const 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;
|
||||
void adjacentVertices( QgsVertexId vertex, QgsVertexId &previousVertex SIP_OUT, QgsVertexId &nextVertex SIP_OUT ) const override;
|
||||
int vertexNumberFromVertexId( QgsVertexId id ) const override;
|
||||
|
@ -202,6 +202,46 @@ QgsLineString *QgsLineString::snappedToGrid( double hSpacing, double vSpacing, d
|
||||
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 )
|
||||
{
|
||||
if ( !wkbPtr )
|
||||
|
@ -180,6 +180,7 @@ class CORE_EXPORT QgsLineString: public QgsCurve
|
||||
void clear() override;
|
||||
bool isEmpty() const override;
|
||||
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 fromWkt( const QString &wkt ) override;
|
||||
|
@ -137,6 +137,11 @@ QgsPoint *QgsPoint::snappedToGrid( double hSpacing, double vSpacing, double dSpa
|
||||
return new QgsPoint( mWkbType, x, y, z, m );
|
||||
}
|
||||
|
||||
bool QgsPoint::removeDuplicateNodes( double, bool )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool QgsPoint::fromWkb( QgsConstWkbPtr &wkbPtr )
|
||||
{
|
||||
QgsWkbTypes::Type type = wkbPtr.readHeader();
|
||||
|
@ -391,7 +391,8 @@ class CORE_EXPORT QgsPoint: public QgsAbstractGeometry
|
||||
QString geometryType() const override;
|
||||
int dimension() const override;
|
||||
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;
|
||||
bool fromWkb( QgsConstWkbPtr &wkb ) override;
|
||||
bool fromWkt( const QString &wkt ) override;
|
||||
|
@ -261,8 +261,6 @@ void QgsGeometryValidator::run()
|
||||
|
||||
case QgsGeometry::ValidatorQgisInternal:
|
||||
{
|
||||
QgsDebugMsg( "validation thread started." );
|
||||
|
||||
QgsWkbTypes::Type flatType = QgsWkbTypes::flatType( mGeometry.wkbType() );
|
||||
//if ( flatType == QgsWkbTypes::Point || flatType == QgsWkbTypes::MultiPoint )
|
||||
// break;
|
||||
@ -327,8 +325,6 @@ void QgsGeometryValidator::run()
|
||||
mErrorCount++;
|
||||
}
|
||||
|
||||
QgsDebugMsg( "validation finished." );
|
||||
|
||||
if ( mStop )
|
||||
{
|
||||
emit errorFound( QgsGeometry::Error( QObject::tr( "Geometry validation was aborted." ) ) );
|
||||
|
@ -596,6 +596,7 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
|
||||
if ( !index.isValid() ||
|
||||
( role != Qt::TextAlignmentRole
|
||||
&& role != Qt::DisplayRole
|
||||
&& role != Qt::ToolTipRole
|
||||
&& role != Qt::EditRole
|
||||
&& role != SortRole
|
||||
&& role != FeatureIdRole
|
||||
@ -647,6 +648,7 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
|
||||
switch ( role )
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
case Qt::ToolTipRole:
|
||||
return mFieldFormatters.at( index.column() )->representValue( layer(), fieldId, mWidgetConfigs.at( index.column() ),
|
||||
mAttributeWidgetCaches.at( index.column() ), val );
|
||||
|
||||
|
@ -48,6 +48,8 @@ class TestQgsGeometrySnapper : public QObject
|
||||
void endPointSnap();
|
||||
void endPointToEndPoint();
|
||||
void internalSnapper();
|
||||
void insertExtra();
|
||||
void duplicateNodes();
|
||||
};
|
||||
|
||||
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)" ) );
|
||||
}
|
||||
|
||||
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 )
|
||||
#include "testqgsgeometrysnapper.moc"
|
||||
|
@ -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( 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()
|
||||
@ -2364,6 +2370,48 @@ void TestQgsGeometry::circularString()
|
||||
QCOMPARE( curveLine2.segmentLength( QgsVertexId( 0, 0, 1 ) ), 0.0 );
|
||||
QGSCOMPARENEAR( curveLine2.segmentLength( QgsVertexId( 0, 0, 2 ) ), 31.4159, 0.001 );
|
||||
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, 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()
|
||||
@ -5881,6 +5961,30 @@ void TestQgsGeometry::polygon()
|
||||
QCOMPARE( p30.segmentLength( QgsVertexId( 0, 1, 4 ) ), 0.0 );
|
||||
QCOMPARE( p30.segmentLength( QgsVertexId( 1, 0, 1 ) ), 100.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()
|
||||
@ -10523,6 +10627,52 @@ void TestQgsGeometry::compoundCurve()
|
||||
QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 3 ) ), 0.0 );
|
||||
QCOMPARE( slc1.segmentLength( QgsVertexId( 0, 0, 4 ) ), 9.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()
|
||||
@ -10994,6 +11144,14 @@ void TestQgsGeometry::multiPoint()
|
||||
QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( 1, 0, 0 ) ), 1 );
|
||||
QCOMPARE( c22.vertexNumberFromVertexId( QgsVertexId( 1, 0, 1 ) ), -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()
|
||||
@ -14855,6 +15013,21 @@ void TestQgsGeometry::geometryCollection()
|
||||
QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 1, 3 ) ), 17 );
|
||||
QCOMPARE( c32.vertexNumberFromVertexId( QgsVertexId( 3, 1, 4 ) ), -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()
|
||||
|
4
tests/testdata/auth_system/README.md
vendored
4
tests/testdata/auth_system/README.md
vendored
@ -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
|
||||
**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
|
||||
in a production environment.
|
||||
@ -22,7 +22,7 @@ filter file open dialogs to specific extensions, e.g. pgAdmin3 always filters
|
||||
|
||||
## Certificate Signing Hierarchy
|
||||
|
||||

|
||||

|
||||
|
||||
## Client Certificates/Keys
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Loading…
x
Reference in New Issue
Block a user