[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.
This commit is contained in:
Nyall Dawson 2018-11-06 12:35:10 +10:00
parent 1b79b9a140
commit d1e09a2bc7
9 changed files with 400 additions and 0 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,58 @@
/***************************************************************************
qgsalgorithmforcerhr.h
---------------------
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. *
* *
***************************************************************************/
#ifndef QGSALGORITHMFORCERHR_H
#define QGSALGORITHMFORCERHR_H
#define SIP_NO_FILE
#include "qgis.h"
#include "qgsprocessingalgorithm.h"
///@cond PRIVATE
/**
* Native force right-hand-rule algorithm.
*/
class QgsForceRHRAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{
public:
QgsForceRHRAlgorithm() = default;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QString shortDescription() const override;
QList<int> inputLayerTypes() const override;
QgsForceRHRAlgorithm *createInstance() const override SIP_FACTORY;
protected:
QgsProcessingFeatureSource::Flag sourceFlags() const override;
QString outputName() const override;
QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
///@endcond PRIVATE
#endif // QGSALGORITHMFORCERHR_H

View File

@ -45,6 +45,7 @@
#include "qgsalgorithmfilter.h"
#include "qgsalgorithmfiltervertices.h"
#include "qgsalgorithmfixgeometries.h"
#include "qgsalgorithmforcerhr.h"
#include "qgsalgorithmjoinbyattribute.h"
#include "qgsalgorithmjoinwithlines.h"
#include "qgsalgorithmimportphotos.h"
@ -172,6 +173,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsFilterVerticesByM() );
addAlgorithm( new QgsFilterVerticesByZ() );
addAlgorithm( new QgsFixGeometriesAlgorithm() );
addAlgorithm( new QgsForceRHRAlgorithm() );
addAlgorithm( new QgsImportPhotosAlgorithm() );
addAlgorithm( new QgsInterpolatePointAlgorithm() );
addAlgorithm( new QgsIntersectionAlgorithm() );