mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-17 00:09:36 -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,
|
PreferNodes,
|
||||||
PreferClosest,
|
PreferClosest,
|
||||||
|
PreferNodesNoExtraVertices,
|
||||||
|
PreferClosestNoExtraVertices,
|
||||||
EndPointPreferNodes,
|
EndPointPreferNodes,
|
||||||
EndPointPreferClosest,
|
EndPointPreferClosest,
|
||||||
EndPointToEndPoint,
|
EndPointToEndPoint,
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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;
|
||||||
|
@ -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 );
|
||||||
|
|
||||||
|
@ -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 );
|
||||||
|
@ -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',
|
||||||
|
@ -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')]
|
||||||
|
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>
|
||||||
<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>
|
||||||
|
@ -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>
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
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 "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() );
|
||||||
|
@ -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 )
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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() );
|
||||||
|
@ -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;
|
||||||
|
@ -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() )
|
||||||
|
@ -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.
|
||||||
|
@ -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() );
|
||||||
|
@ -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;
|
||||||
|
@ -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 );
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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 )
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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;
|
||||||
|
@ -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." ) ) );
|
||||||
|
@ -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 );
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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()
|
||||||
|
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
|
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
|
||||||
|
|
||||||

|

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