Skip to content

Commit d1e09a2

Browse files
committedNov 9, 2018
[FEATURE][processing] New algorithm to force right hand rule for polygons
This algorithm forces polygon geometries to respect the Right-Hand-Rule, in which the area that is bounded by a polygon is to the right of the boundary. In particular, the exterior ring is oriented in a clockwise direction and the interior rings in a counter-clockwise direction.
1 parent 1b79b9a commit d1e09a2

File tree

9 files changed

+400
-0
lines changed

9 files changed

+400
-0
lines changed
 
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ force_rhr_multipolys.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>0</gml:X><gml:Y>-1</gml:Y></gml:coord>
10+
<gml:coord><gml:X>9</gml:X><gml:Y>6</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:force_rhr_multipolys fid="multipolys.0">
16+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,1 2,2 3,2 3,3 4,3 4,1 2,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
17+
<ogr:Bname>Test</ogr:Bname>
18+
<ogr:Bintval>1</ogr:Bintval>
19+
<ogr:Bfloatval>0.123</ogr:Bfloatval>
20+
</ogr:force_rhr_multipolys>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:force_rhr_multipolys fid="multipolys.1">
24+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,-1 7,3 8,3 8,-1 7,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7,6 9,6 9,5 8,4 7,4 7,5 7,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
25+
<ogr:Bname xsi:nil="true"/>
26+
<ogr:Bintval xsi:nil="true"/>
27+
<ogr:Bfloatval xsi:nil="true"/>
28+
</ogr:force_rhr_multipolys>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:force_rhr_multipolys fid="multipolys.2">
32+
<ogr:geometryProperty><gml:MultiPolygon srsName="EPSG:4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>0,0 0,1 1,1 1,0 0,0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></ogr:geometryProperty>
33+
<ogr:Bname>Test</ogr:Bname>
34+
<ogr:Bintval>2</ogr:Bintval>
35+
<ogr:Bfloatval>-0.123</ogr:Bfloatval>
36+
</ogr:force_rhr_multipolys>
37+
</gml:featureMember>
38+
<gml:featureMember>
39+
<ogr:force_rhr_multipolys fid="multipolys.3">
40+
<ogr:Bname>Test</ogr:Bname>
41+
<ogr:Bintval>3</ogr:Bintval>
42+
<ogr:Bfloatval>0</ogr:Bfloatval>
43+
</ogr:force_rhr_multipolys>
44+
</gml:featureMember>
45+
</ogr:FeatureCollection>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="force_rhr_multipolys" type="ogr:force_rhr_multipolys_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="force_rhr_multipolys_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:MultiPolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="Bname" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="4"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="Bintval" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:integer">
29+
<xs:totalDigits value="10"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
<xs:element name="Bfloatval" nillable="true" minOccurs="0" maxOccurs="1">
34+
<xs:simpleType>
35+
<xs:restriction base="xs:decimal">
36+
</xs:restriction>
37+
</xs:simpleType>
38+
</xs:element>
39+
</xs:sequence>
40+
</xs:extension>
41+
</xs:complexContent>
42+
</xs:complexType>
43+
</xs:schema>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://ogr.maptools.org/ force_rhr_polys.xsd"
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:force_rhr_polys fid="polys.0">
16+
<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>
17+
<ogr:name>aaaaa</ogr:name>
18+
<ogr:intval>33</ogr:intval>
19+
<ogr:floatval>44.123456</ogr:floatval>
20+
</ogr:force_rhr_polys>
21+
</gml:featureMember>
22+
<gml:featureMember>
23+
<ogr:force_rhr_polys fid="polys.1">
24+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5,5 6,4 4,4 5,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
25+
<ogr:name>Aaaaa</ogr:name>
26+
<ogr:intval>-33</ogr:intval>
27+
<ogr:floatval>0</ogr:floatval>
28+
</ogr:force_rhr_polys>
29+
</gml:featureMember>
30+
<gml:featureMember>
31+
<ogr:force_rhr_polys fid="polys.2">
32+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,5 2,6 3,6 3,5 2,5</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
33+
<ogr:name>bbaaa</ogr:name>
34+
<ogr:intval xsi:nil="true"/>
35+
<ogr:floatval>0.123</ogr:floatval>
36+
</ogr:force_rhr_polys>
37+
</gml:featureMember>
38+
<gml:featureMember>
39+
<ogr:force_rhr_polys fid="polys.3">
40+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7,0 7,-2 9,-2 9,0 7,0</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
41+
<ogr:name>ASDF</ogr:name>
42+
<ogr:intval>0</ogr:intval>
43+
<ogr:floatval xsi:nil="true"/>
44+
</ogr:force_rhr_polys>
45+
</gml:featureMember>
46+
<gml:featureMember>
47+
<ogr:force_rhr_polys fid="polys.4">
48+
<ogr:name xsi:nil="true"/>
49+
<ogr:intval>120</ogr:intval>
50+
<ogr:floatval>-100291.43213</ogr:floatval>
51+
</ogr:force_rhr_polys>
52+
</gml:featureMember>
53+
<gml:featureMember>
54+
<ogr:force_rhr_polys fid="polys.5">
55+
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 6,1 6,-3 2,-1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
56+
<ogr:name>elim</ogr:name>
57+
<ogr:intval>2</ogr:intval>
58+
<ogr:floatval>3.33</ogr:floatval>
59+
</ogr:force_rhr_polys>
60+
</gml:featureMember>
61+
</ogr:FeatureCollection>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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">
3+
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
4+
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
5+
<xs:complexType name="FeatureCollectionType">
6+
<xs:complexContent>
7+
<xs:extension base="gml:AbstractFeatureCollectionType">
8+
<xs:attribute name="lockId" type="xs:string" use="optional"/>
9+
<xs:attribute name="scope" type="xs:string" use="optional"/>
10+
</xs:extension>
11+
</xs:complexContent>
12+
</xs:complexType>
13+
<xs:element name="force_rhr_polys" type="ogr:force_rhr_polys_Type" substitutionGroup="gml:_Feature"/>
14+
<xs:complexType name="force_rhr_polys_Type">
15+
<xs:complexContent>
16+
<xs:extension base="gml:AbstractFeatureType">
17+
<xs:sequence>
18+
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
19+
<xs:element name="name" nillable="true" minOccurs="0" maxOccurs="1">
20+
<xs:simpleType>
21+
<xs:restriction base="xs:string">
22+
<xs:maxLength value="5"/>
23+
</xs:restriction>
24+
</xs:simpleType>
25+
</xs:element>
26+
<xs:element name="intval" nillable="true" minOccurs="0" maxOccurs="1">
27+
<xs:simpleType>
28+
<xs:restriction base="xs:integer">
29+
<xs:totalDigits value="10"/>
30+
</xs:restriction>
31+
</xs:simpleType>
32+
</xs:element>
33+
<xs:element name="floatval" nillable="true" minOccurs="0" maxOccurs="1">
34+
<xs:simpleType>
35+
<xs:restriction base="xs:decimal">
36+
</xs:restriction>
37+
</xs:simpleType>
38+
</xs:element>
39+
</xs:sequence>
40+
</xs:extension>
41+
</xs:complexContent>
42+
</xs:complexType>
43+
</xs:schema>

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6325,4 +6325,26 @@ tests:
63256325
name: expected/remove_duplicates3.gml
63266326
type: vector
63276327

6328+
- algorithm: native:forcerhr
6329+
name: Force right-hand-rule polys
6330+
params:
6331+
INPUT:
6332+
name: polys.gml|layername=polys2
6333+
type: vector
6334+
results:
6335+
OUTPUT:
6336+
name: expected/force_rhr_polys.gml
6337+
type: vector
6338+
6339+
- algorithm: native:forcerhr
6340+
name: Force right-hand-rule multipolys
6341+
params:
6342+
INPUT:
6343+
name: multipolys.gml|layername=multipolys
6344+
type: vector
6345+
results:
6346+
OUTPUT:
6347+
name: expected/force_rhr_multipolys.gml
6348+
type: vector
6349+
63286350
# See ../README.md for a description of the file format

‎src/analysis/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ SET(QGIS_ANALYSIS_SRCS
5050
processing/qgsalgorithmfilter.cpp
5151
processing/qgsalgorithmfiltervertices.cpp
5252
processing/qgsalgorithmfixgeometries.cpp
53+
processing/qgsalgorithmforcerhr.cpp
5354
processing/qgsalgorithmimportphotos.cpp
5455
processing/qgsalgorithminterpolatepoint.cpp
5556
processing/qgsalgorithmintersection.cpp
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/***************************************************************************
2+
qgsalgorithmforcerhr.cpp
3+
---------------------
4+
begin : November 2018
5+
copyright : (C) 2018 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#include "qgsalgorithmforcerhr.h"
19+
#include "qgsvectorlayer.h"
20+
#include "qgsgeometrycollection.h"
21+
#include "qgscurvepolygon.h"
22+
23+
///@cond PRIVATE
24+
25+
QString QgsForceRHRAlgorithm::name() const
26+
{
27+
return QStringLiteral( "forcerhr" );
28+
}
29+
30+
QString QgsForceRHRAlgorithm::displayName() const
31+
{
32+
return QObject::tr( "Force right-hand-rule" );
33+
}
34+
35+
QStringList QgsForceRHRAlgorithm::tags() const
36+
{
37+
return QObject::tr( "clockwise,counter,orientation,ring,repair,invalid,geometry,make,valid" ).split( ',' );
38+
}
39+
40+
QString QgsForceRHRAlgorithm::group() const
41+
{
42+
return QObject::tr( "Vector geometry" );
43+
}
44+
45+
QString QgsForceRHRAlgorithm::groupId() const
46+
{
47+
return QStringLiteral( "vectorgeometry" );
48+
}
49+
50+
QgsProcessingFeatureSource::Flag QgsForceRHRAlgorithm::sourceFlags() const
51+
{
52+
return QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks;
53+
}
54+
55+
QString QgsForceRHRAlgorithm::outputName() const
56+
{
57+
return QObject::tr( "Reoriented" );
58+
}
59+
60+
QString QgsForceRHRAlgorithm::shortHelpString() const
61+
{
62+
return QObject::tr( "This algorithm forces polygon geometries to respect the Right-Hand-Rule, in which the area that is bounded by a polygon "
63+
"is to the right of the boundary. In particular, the exterior ring is oriented in a clockwise direction and the interior "
64+
"rings in a counter-clockwise direction." );
65+
}
66+
67+
QString QgsForceRHRAlgorithm::shortDescription() const
68+
{
69+
return QObject::tr( "Forces polygon geometries to respect the Right-Hand-Rule." );
70+
}
71+
72+
QList<int> QgsForceRHRAlgorithm::inputLayerTypes() const
73+
{
74+
return QList<int>() << QgsProcessing::TypeVectorPolygon;
75+
}
76+
77+
QgsForceRHRAlgorithm *QgsForceRHRAlgorithm::createInstance() const
78+
{
79+
return new QgsForceRHRAlgorithm();
80+
}
81+
82+
QgsFeatureList QgsForceRHRAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback * )
83+
{
84+
if ( !feature.hasGeometry() )
85+
return QgsFeatureList() << feature;
86+
87+
const QgsGeometry inputGeom = feature.geometry();
88+
QgsGeometry outputGeometry = inputGeom;
89+
if ( inputGeom.isMultipart() )
90+
{
91+
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( inputGeom.constGet() );
92+
std::unique_ptr< QgsGeometryCollection > newCollection( collection->createEmptyWithSameType() );
93+
for ( int i = 0; i < collection->numGeometries(); ++i )
94+
{
95+
const QgsAbstractGeometry *g = collection->geometryN( i );
96+
if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( g ) )
97+
{
98+
std::unique_ptr< QgsCurvePolygon > corrected( cp->clone() );
99+
corrected->forceRHR();
100+
newCollection->addGeometry( corrected.release() );
101+
}
102+
else
103+
{
104+
newCollection->addGeometry( g->clone() );
105+
}
106+
}
107+
outputGeometry = QgsGeometry( std::move( newCollection ) );
108+
}
109+
else
110+
{
111+
if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( inputGeom.constGet() ) )
112+
{
113+
std::unique_ptr< QgsCurvePolygon > corrected( cp->clone() );
114+
corrected->forceRHR();
115+
outputGeometry = QgsGeometry( std::move( corrected ) );
116+
}
117+
}
118+
119+
QgsFeature outputFeature = feature;
120+
outputFeature.setGeometry( outputGeometry );
121+
122+
return QgsFeatureList() << outputFeature;
123+
}
124+
125+
///@endcond
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/***************************************************************************
2+
qgsalgorithmforcerhr.h
3+
---------------------
4+
begin : November 2018
5+
copyright : (C) 2018 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************/
8+
9+
/***************************************************************************
10+
* *
11+
* This program is free software; you can redistribute it and/or modify *
12+
* it under the terms of the GNU General Public License as published by *
13+
* the Free Software Foundation; either version 2 of the License, or *
14+
* (at your option) any later version. *
15+
* *
16+
***************************************************************************/
17+
18+
#ifndef QGSALGORITHMFORCERHR_H
19+
#define QGSALGORITHMFORCERHR_H
20+
21+
#define SIP_NO_FILE
22+
23+
#include "qgis.h"
24+
#include "qgsprocessingalgorithm.h"
25+
26+
///@cond PRIVATE
27+
28+
/**
29+
* Native force right-hand-rule algorithm.
30+
*/
31+
class QgsForceRHRAlgorithm : public QgsProcessingFeatureBasedAlgorithm
32+
{
33+
34+
public:
35+
36+
QgsForceRHRAlgorithm() = default;
37+
QString name() const override;
38+
QString displayName() const override;
39+
QStringList tags() const override;
40+
QString group() const override;
41+
QString groupId() const override;
42+
QString shortHelpString() const override;
43+
QString shortDescription() const override;
44+
QList<int> inputLayerTypes() const override;
45+
QgsForceRHRAlgorithm *createInstance() const override SIP_FACTORY;
46+
47+
protected:
48+
QgsProcessingFeatureSource::Flag sourceFlags() const override;
49+
QString outputName() const override;
50+
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
51+
52+
};
53+
54+
///@endcond PRIVATE
55+
56+
#endif // QGSALGORITHMFORCERHR_H
57+
58+

‎src/analysis/processing/qgsnativealgorithms.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "qgsalgorithmfilter.h"
4646
#include "qgsalgorithmfiltervertices.h"
4747
#include "qgsalgorithmfixgeometries.h"
48+
#include "qgsalgorithmforcerhr.h"
4849
#include "qgsalgorithmjoinbyattribute.h"
4950
#include "qgsalgorithmjoinwithlines.h"
5051
#include "qgsalgorithmimportphotos.h"
@@ -172,6 +173,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
172173
addAlgorithm( new QgsFilterVerticesByM() );
173174
addAlgorithm( new QgsFilterVerticesByZ() );
174175
addAlgorithm( new QgsFixGeometriesAlgorithm() );
176+
addAlgorithm( new QgsForceRHRAlgorithm() );
175177
addAlgorithm( new QgsImportPhotosAlgorithm() );
176178
addAlgorithm( new QgsInterpolatePointAlgorithm() );
177179
addAlgorithm( new QgsIntersectionAlgorithm() );

0 commit comments

Comments
 (0)
Please sign in to comment.