Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE][processing] New algorithm to force right hand rule for poly…
…gons

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.
  • Loading branch information
nyalldawson committed Nov 9, 2018
1 parent 1b79b9a commit d1e09a2
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 0 deletions.
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ force_rhr_multipolys.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-1</gml:Y></gml:coord>
<gml:coord><gml:X>9</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:force_rhr_multipolys fid="multipolys.0">
<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>
<ogr:Bname>Test</ogr:Bname>
<ogr:Bintval>1</ogr:Bintval>
<ogr:Bfloatval>0.123</ogr:Bfloatval>
</ogr:force_rhr_multipolys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_multipolys fid="multipolys.1">
<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>
<ogr:Bname xsi:nil="true"/>
<ogr:Bintval xsi:nil="true"/>
<ogr:Bfloatval xsi:nil="true"/>
</ogr:force_rhr_multipolys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_multipolys fid="multipolys.2">
<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>
<ogr:Bname>Test</ogr:Bname>
<ogr:Bintval>2</ogr:Bintval>
<ogr:Bfloatval>-0.123</ogr:Bfloatval>
</ogr:force_rhr_multipolys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_multipolys fid="multipolys.3">
<ogr:Bname>Test</ogr:Bname>
<ogr:Bintval>3</ogr:Bintval>
<ogr:Bfloatval>0</ogr:Bfloatval>
</ogr:force_rhr_multipolys>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,43 @@
<?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="force_rhr_multipolys" type="ogr:force_rhr_multipolys_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="force_rhr_multipolys_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:MultiPolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="Bname" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="4"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Bintval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="Bfloatval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ force_rhr_polys.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:force_rhr_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,-1 -1,-1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>aaaaa</ogr:name>
<ogr:intval>33</ogr:intval>
<ogr:floatval>44.123456</ogr:floatval>
</ogr:force_rhr_polys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_polys fid="polys.1">
<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>
<ogr:name>Aaaaa</ogr:name>
<ogr:intval>-33</ogr:intval>
<ogr:floatval>0</ogr:floatval>
</ogr:force_rhr_polys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_polys fid="polys.2">
<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>
<ogr:name>bbaaa</ogr:name>
<ogr:intval xsi:nil="true"/>
<ogr:floatval>0.123</ogr:floatval>
</ogr:force_rhr_polys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_polys fid="polys.3">
<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>
<ogr:name>ASDF</ogr:name>
<ogr:intval>0</ogr:intval>
<ogr:floatval xsi:nil="true"/>
</ogr:force_rhr_polys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_polys fid="polys.4">
<ogr:name xsi:nil="true"/>
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:force_rhr_polys>
</gml:featureMember>
<gml:featureMember>
<ogr:force_rhr_polys fid="polys.5">
<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>
<ogr:name>elim</ogr:name>
<ogr:intval>2</ogr:intval>
<ogr:floatval>3.33</ogr:floatval>
</ogr:force_rhr_polys>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,43 @@
<?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="force_rhr_polys" type="ogr:force_rhr_polys_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="force_rhr_polys_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="geometryProperty" type="gml:PolygonPropertyType" nillable="true" minOccurs="0" maxOccurs="1"/>
<xs:element name="name" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:maxLength value="5"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="intval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="floatval" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:decimal">
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>
22 changes: 22 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -6325,4 +6325,26 @@ tests:
name: expected/remove_duplicates3.gml
type: vector

- algorithm: native:forcerhr
name: Force right-hand-rule polys
params:
INPUT:
name: polys.gml|layername=polys2
type: vector
results:
OUTPUT:
name: expected/force_rhr_polys.gml
type: vector

- algorithm: native:forcerhr
name: Force right-hand-rule multipolys
params:
INPUT:
name: multipolys.gml|layername=multipolys
type: vector
results:
OUTPUT:
name: expected/force_rhr_multipolys.gml
type: vector

# See ../README.md for a description of the file format
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -50,6 +50,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmfilter.cpp
processing/qgsalgorithmfiltervertices.cpp
processing/qgsalgorithmfixgeometries.cpp
processing/qgsalgorithmforcerhr.cpp
processing/qgsalgorithmimportphotos.cpp
processing/qgsalgorithminterpolatepoint.cpp
processing/qgsalgorithmintersection.cpp
Expand Down
125 changes: 125 additions & 0 deletions src/analysis/processing/qgsalgorithmforcerhr.cpp
@@ -0,0 +1,125 @@
/***************************************************************************
qgsalgorithmforcerhr.cpp
---------------------
begin : November 2018
copyright : (C) 2018 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 "qgsalgorithmforcerhr.h"
#include "qgsvectorlayer.h"
#include "qgsgeometrycollection.h"
#include "qgscurvepolygon.h"

///@cond PRIVATE

QString QgsForceRHRAlgorithm::name() const
{
return QStringLiteral( "forcerhr" );
}

QString QgsForceRHRAlgorithm::displayName() const
{
return QObject::tr( "Force right-hand-rule" );
}

QStringList QgsForceRHRAlgorithm::tags() const
{
return QObject::tr( "clockwise,counter,orientation,ring,repair,invalid,geometry,make,valid" ).split( ',' );
}

QString QgsForceRHRAlgorithm::group() const
{
return QObject::tr( "Vector geometry" );
}

QString QgsForceRHRAlgorithm::groupId() const
{
return QStringLiteral( "vectorgeometry" );
}

QgsProcessingFeatureSource::Flag QgsForceRHRAlgorithm::sourceFlags() const
{
return QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks;
}

QString QgsForceRHRAlgorithm::outputName() const
{
return QObject::tr( "Reoriented" );
}

QString QgsForceRHRAlgorithm::shortHelpString() const
{
return QObject::tr( "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." );
}

QString QgsForceRHRAlgorithm::shortDescription() const
{
return QObject::tr( "Forces polygon geometries to respect the Right-Hand-Rule." );
}

QList<int> QgsForceRHRAlgorithm::inputLayerTypes() const
{
return QList<int>() << QgsProcessing::TypeVectorPolygon;
}

QgsForceRHRAlgorithm *QgsForceRHRAlgorithm::createInstance() const
{
return new QgsForceRHRAlgorithm();
}

QgsFeatureList QgsForceRHRAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback * )
{
if ( !feature.hasGeometry() )
return QgsFeatureList() << feature;

const QgsGeometry inputGeom = feature.geometry();
QgsGeometry outputGeometry = inputGeom;
if ( inputGeom.isMultipart() )
{
const QgsGeometryCollection *collection = qgsgeometry_cast< const QgsGeometryCollection * >( inputGeom.constGet() );
std::unique_ptr< QgsGeometryCollection > newCollection( collection->createEmptyWithSameType() );
for ( int i = 0; i < collection->numGeometries(); ++i )
{
const QgsAbstractGeometry *g = collection->geometryN( i );
if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( g ) )
{
std::unique_ptr< QgsCurvePolygon > corrected( cp->clone() );
corrected->forceRHR();
newCollection->addGeometry( corrected.release() );
}
else
{
newCollection->addGeometry( g->clone() );
}
}
outputGeometry = QgsGeometry( std::move( newCollection ) );
}
else
{
if ( const QgsCurvePolygon *cp = qgsgeometry_cast< const QgsCurvePolygon * >( inputGeom.constGet() ) )
{
std::unique_ptr< QgsCurvePolygon > corrected( cp->clone() );
corrected->forceRHR();
outputGeometry = QgsGeometry( std::move( corrected ) );
}
}

QgsFeature outputFeature = feature;
outputFeature.setGeometry( outputGeometry );

return QgsFeatureList() << outputFeature;
}

///@endcond

0 comments on commit d1e09a2

Please sign in to comment.