diff --git a/python/plugins/processing/tests/testdata/expected/force_rhr_multipolys.gml b/python/plugins/processing/tests/testdata/expected/force_rhr_multipolys.gml
new file mode 100644
index 00000000000..b642dd7e631
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/force_rhr_multipolys.gml
@@ -0,0 +1,45 @@
+
+
+
+
+ 0-1
+ 96
+
+
+
+
+
+ 2,1 2,2 3,2 3,3 4,3 4,1 2,1
+ Test
+ 1
+ 0.123
+
+
+
+
+ 7,-1 7,3 8,3 8,-1 7,-17,6 9,6 9,5 8,4 7,4 7,5 7,6
+
+
+
+
+
+
+
+ 0,0 0,1 1,1 1,0 0,0
+ Test
+ 2
+ -0.123
+
+
+
+
+ Test
+ 3
+ 0
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/force_rhr_multipolys.xsd b/python/plugins/processing/tests/testdata/expected/force_rhr_multipolys.xsd
new file mode 100644
index 00000000000..f34f32ca633
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/force_rhr_multipolys.xsd
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/force_rhr_polys.gml b/python/plugins/processing/tests/testdata/expected/force_rhr_polys.gml
new file mode 100644
index 00000000000..00f771189ec
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/force_rhr_polys.gml
@@ -0,0 +1,61 @@
+
+
+
+
+ -1-3
+ 106
+
+
+
+
+
+ -1,-1 -1,3 3,3 3,2 2,2 2,-1 -1,-1
+ aaaaa
+ 33
+ 44.123456
+
+
+
+
+ 5,5 6,4 4,4 5,5
+ Aaaaa
+ -33
+ 0
+
+
+
+
+ 2,5 2,6 3,6 3,5 2,5
+ bbaaa
+
+ 0.123
+
+
+
+
+ 6,1 10,1 10,-3 6,-3 6,17,0 7,-2 9,-2 9,0 7,0
+ ASDF
+ 0
+
+
+
+
+
+
+ 120
+ -100291.43213
+
+
+
+
+ 3,2 6,1 6,-3 2,-1 2,2 3,2
+ elim
+ 2
+ 3.33
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/force_rhr_polys.xsd b/python/plugins/processing/tests/testdata/expected/force_rhr_polys.xsd
new file mode 100644
index 00000000000..489096a0444
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/force_rhr_polys.xsd
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
index 9bae1383da6..e0b51c3dd83 100755
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
@@ -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
diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt
index 8d2a9342030..138656c9847 100644
--- a/src/analysis/CMakeLists.txt
+++ b/src/analysis/CMakeLists.txt
@@ -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
diff --git a/src/analysis/processing/qgsalgorithmforcerhr.cpp b/src/analysis/processing/qgsalgorithmforcerhr.cpp
new file mode 100644
index 00000000000..ca86d29a3f1
--- /dev/null
+++ b/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 QgsForceRHRAlgorithm::inputLayerTypes() const
+{
+ return QList() << 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
diff --git a/src/analysis/processing/qgsalgorithmforcerhr.h b/src/analysis/processing/qgsalgorithmforcerhr.h
new file mode 100644
index 00000000000..8f58bdd9ddd
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmforcerhr.h
@@ -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 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
+
+
diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp
index db86d96ff41..82b36142095 100644
--- a/src/analysis/processing/qgsnativealgorithms.cpp
+++ b/src/analysis/processing/qgsnativealgorithms.cpp
@@ -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() );