Skip to content

Commit

Permalink
[FEATURE][processing] Rotate features algorithm
Browse files Browse the repository at this point in the history
Allows rotation of features by a set angle.

The rotation can occur around a preset point or the individual
feature's centroids.
  • Loading branch information
nyalldawson committed Mar 6, 2018
1 parent f2e8c37 commit 05484dc
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 0 deletions.
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ rotate_around_centroid.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1.739047663012093</gml:X><gml:Y>-3.657852097554699</gml:Y></gml:coord>
<gml:coord><gml:X>10.6578520975547</gml:X><gml:Y>6.164463024388675</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:rotate_around_centroid fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-1.73904766301209,-0.102859347390987 -0.048574616049295,3.52237180075561 3.5766565320973,1.83189875379281 3.15403827035661,0.925590966756165 2.24773048331995,1.34820922849686 0.979875698097857,-1.37071413261309 -1.73904766301209,-0.102859347390987</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:rotate_around_centroid>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_centroid fid="polys.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.28174550782713,4.9375385246911 5.76543503312308,3.60861247591375 3.95281945904978,4.45384899939515 5.28174550782713,4.9375385246911</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:rotate_around_centroid>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_centroid fid="polys.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1.83553697561133,5.25815523735202 2.25815523735203,6.16446302438867 3.16446302438868,5.74184476264798 2.74184476264798,4.83553697561132 1.83553697561133,5.25815523735202</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>bbaaa</ogr:name>
<ogr:floatval>0.123</ogr:floatval>
</ogr:rotate_around_centroid>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_centroid fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>7.0326209494081,1.6578520975547 10.6578520975547,-0.032620949408099 8.9673790505919,-3.6578520975547 5.3421479024453,-1.9673790505919 7.0326209494081,1.6578520975547</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7.51631047470405,0.328926048777349 6.67107395122265,-1.48368952529595 8.48368952529595,-2.32892604877735 9.32892604877735,-0.51631047470405 7.51631047470405,0.328926048777349</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>ASDF</ogr:name>
<ogr:intval>0</ogr:intval>
</ogr:rotate_around_centroid>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_centroid fid="polys.4">
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:rotate_around_centroid>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_centroid fid="polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4.03876313258057,2.24877608622643 6.33506823194982,0.074613513967679 4.64459518498703,-3.55061763417892 1.86460056032183,-0.047529013142823 3.13245534554392,2.67139434796713 4.03876313258057,2.24877608622643</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:rotate_around_centroid>
</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="rotate_around_centroid" type="ogr:rotate_around_centroid_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="rotate_around_centroid_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>
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ rotate_around_point.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-2.381288744183742</gml:X><gml:Y>-5.692007337623284</gml:Y></gml:coord>
<gml:coord><gml:X>8.433333436700806</gml:X><gml:Y>5.845708839632159</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:rotate_around_point fid="polys.0">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-2.38128874418374,0.769409115597709 -0.690815697220945,4.39464026374431 2.93441545092566,2.70416721678151 2.51179718918496,1.79785942974486 1.60548940214831,2.22047769148556 0.337634616926207,-0.49844566962439 -2.38128874418374,0.769409115597709</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:rotate_around_point>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_point fid="polys.1">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>5.59226754848035,3.67154626737341 6.0759570737763,2.34262021859606 4.263341499703,3.18785674207746 5.59226754848035,3.67154626737341</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:rotate_around_point>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_point fid="polys.2">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2.8733441873704,4.93940105259551 3.2959624491111,5.84570883963216 4.20227023614775,5.42309057789146 3.77965197440705,4.51678279085481 2.8733441873704,4.93940105259551</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>bbaaa</ogr:name>
<ogr:floatval>0.123</ogr:floatval>
</ogr:rotate_around_point>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_point fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>4.80810228855421,-0.376303142513887 8.43333343670081,-2.06677618947669 6.74286038973801,-5.69200733762328 3.11762924159141,-4.00153429066049 4.80810228855421,-0.376303142513887</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>5.29179181385016,-1.70522919129124 4.44655529036876,-3.51784476536454 6.25917086444206,-4.36308128884593 7.10440738792346,-2.55046571477264 5.29179181385016,-1.70522919129124</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>ASDF</ogr:name>
<ogr:intval>0</ogr:intval>
</ogr:rotate_around_point>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_point fid="polys.4">
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:rotate_around_point>
</gml:featureMember>
<gml:featureMember>
<ogr:rotate_around_point fid="polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2.51179718918496,1.79785942974486 4.80810228855421,-0.376303142513887 3.11762924159141,-4.00153429066049 0.337634616926207,-0.49844566962439 1.60548940214831,2.22047769148556 2.51179718918496,1.79785942974486</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:rotate_around_point>
</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="rotate_around_point" type="ogr:rotate_around_point_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="rotate_around_point_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>
25 changes: 25 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -4842,3 +4842,28 @@ tests:
OUTPUT:
name: expected/segmentize_by_distance.gml
type: vector

- algorithm: native:rotatefeatures
name: Rotate around centroid
params:
ANGLE: 25.0
INPUT:
name: polys.gml
type: vector
results:
OUTPUT:
name: expected/rotate_around_centroid.gml
type: vector

- algorithm: native:rotatefeatures
name: Rotate around point
params:
ANCHOR: 2.3,3 [EPSG:4326]
ANGLE: 25.0
INPUT:
name: polys.gml
type: vector
results:
OUTPUT:
name: expected/rotate_around_point.gml
type: vector
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -58,6 +58,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmremoveduplicatevertices.cpp
processing/qgsalgorithmremovenullgeometry.cpp
processing/qgsalgorithmrenamelayer.cpp
processing/qgsalgorithmrotate.cpp
processing/qgsalgorithmsaveselectedfeatures.cpp
processing/qgsalgorithmsegmentize.cpp
processing/qgsalgorithmsimplify.cpp
Expand Down
149 changes: 149 additions & 0 deletions src/analysis/processing/qgsalgorithmrotate.cpp
@@ -0,0 +1,149 @@
/***************************************************************************
qgsalgorithmrotate.cpp
---------------------
begin : March 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 "qgsalgorithmrotate.h"

///@cond PRIVATE

QString QgsRotateFeaturesAlgorithm::name() const
{
return QStringLiteral( "rotatefeatures" );
}

QString QgsRotateFeaturesAlgorithm::displayName() const
{
return QObject::tr( "Rotate" );
}

QStringList QgsRotateFeaturesAlgorithm::tags() const
{
return QObject::tr( "rotate,around,center,point" ).split( ',' );
}

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

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

QString QgsRotateFeaturesAlgorithm::outputName() const
{
return QObject::tr( "Rotated" );
}

QString QgsRotateFeaturesAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm rotates feature geometries, by the specified angle clockwise" )
+ QStringLiteral( "\n\n" )
+ QObject::tr( "Optionally, the rotation can occur around a preset point. If not set the rotation occurs around each feature's centroid." );
}

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

void QgsRotateFeaturesAlgorithm::initParameters( const QVariantMap & )
{
std::unique_ptr< QgsProcessingParameterNumber > rotation = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "ANGLE" ),
QObject::tr( "Rotation (degrees clockwise)" ), QgsProcessingParameterNumber::Double,
0.0 );
rotation->setIsDynamic( true );
rotation->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "ANGLE" ), QObject::tr( "Rotation (degrees clockwise)" ), QgsPropertyDefinition::Rotation ) );
rotation->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
addParameter( rotation.release() );

std::unique_ptr< QgsProcessingParameterPoint > anchor = qgis::make_unique< QgsProcessingParameterPoint >( QStringLiteral( "ANCHOR" ),
QObject::tr( "Rotation anchor point" ), QVariant(), true );
addParameter( anchor.release() );
}

bool QgsRotateFeaturesAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mAngle = parameterAsDouble( parameters, QStringLiteral( "ANGLE" ), context );
mDynamicAngle = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "ANGLE" ) );
if ( mDynamicAngle )
mAngleProperty = parameters.value( QStringLiteral( "ANGLE" ) ).value< QgsProperty >();

mUseAnchor = parameters.value( "ANCHOR" ).isValid();
if ( mUseAnchor )
{
mAnchor = parameterAsPoint( parameters, QStringLiteral( "ANCHOR" ), context );
mAnchorCrs = parameterAsPointCrs( parameters, QStringLiteral( "ANCHOR" ), context );
}

return true;
}

QgsFeatureList QgsRotateFeaturesAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
if ( mUseAnchor && !mTransformedAnchor )
{
mTransformedAnchor = true;
if ( mAnchorCrs != sourceCrs() )
{
QgsCoordinateTransform ct( mAnchorCrs, sourceCrs(), context.transformContext() );
try
{
mAnchor = ct.transform( mAnchor );
}
catch ( QgsCsException & )
{
throw QgsProcessingException( QObject::tr( "Could not transform anchor point to destination CRS" ) );
}
}
}

QgsFeature f = feature;
if ( f.hasGeometry() )
{
QgsGeometry geometry = f.geometry();

double angle = mAngle;
if ( mDynamicAngle )
angle = mAngleProperty.valueAsDouble( context.expressionContext(), angle );

if ( mUseAnchor )
{
geometry.rotate( angle, mAnchor );
f.setGeometry( geometry );
}
else
{
QgsGeometry centroid = geometry.centroid();
if ( centroid )
{
geometry.rotate( angle, centroid.asPoint() );
f.setGeometry( geometry );
}
else
{
feedback->reportError( QObject::tr( "Could not calculate centroid for feature %1: %2" ).arg( feature.id() ).arg( centroid.lastError() ) );
}
}
}
return QgsFeatureList() << f;
}


///@endcond


0 comments on commit 05484dc

Please sign in to comment.