QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsalgorithmangletonearest.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmangletonearest.cpp
3 ---------------------
4 begin : July 2020
5 copyright : (C) 2020 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
20#include "qgslinestring.h"
21#include "qgsvectorlayer.h"
22#include "qgsrenderer.h"
24#include "qgsmarkersymbol.h"
25
27
28class SetMarkerRotationVisitor : public QgsStyleEntityVisitorInterface
29{
30 public:
31
32 SetMarkerRotationVisitor( const QString &rotationField )
33 : mRotationField( rotationField )
34 {}
35
36 bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &entity ) override
37 {
38 if ( const QgsStyleSymbolEntity *symbolEntity = dynamic_cast< const QgsStyleSymbolEntity * >( entity.entity ) )
39 {
40 if ( QgsMarkerSymbol *marker = dynamic_cast< QgsMarkerSymbol * >( symbolEntity->symbol() ) )
41 {
42 marker->setDataDefinedAngle( QgsProperty::fromField( mRotationField ) );
43 }
44 }
45 return true;
46 }
47
48 private:
49 QString mRotationField;
50
51};
52
53class SetMarkerRotationPostProcessor : public QgsProcessingLayerPostProcessorInterface
54{
55 public:
56
57 SetMarkerRotationPostProcessor( std::unique_ptr< QgsFeatureRenderer > renderer, const QString &rotationField )
58 : mRenderer( std::move( renderer ) )
59 , mRotationField( rotationField )
60 {}
61
63 {
64 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
65 {
66 SetMarkerRotationVisitor visitor( mRotationField );
67 mRenderer->accept( &visitor );
68 vl->setRenderer( mRenderer.release() );
69 vl->triggerRepaint();
70 }
71 }
72
73 private:
74
75 std::unique_ptr<QgsFeatureRenderer> mRenderer;
76 QString mRotationField;
77};
78
79QString QgsAngleToNearestAlgorithm::name() const
80{
81 return QStringLiteral( "angletonearest" );
82}
83
84QString QgsAngleToNearestAlgorithm::displayName() const
85{
86 return QObject::tr( "Align points to features" );
87}
88
89QStringList QgsAngleToNearestAlgorithm::tags() const
90{
91 return QObject::tr( "align,marker,stroke,fill,orient,points,lines,angles,rotation,rotate" ).split( ',' );
92}
93
94QString QgsAngleToNearestAlgorithm::group() const
95{
96 return QObject::tr( "Cartography" );
97}
98
99QString QgsAngleToNearestAlgorithm::groupId() const
100{
101 return QStringLiteral( "cartography" );
102}
103
104QgsAngleToNearestAlgorithm::~QgsAngleToNearestAlgorithm() = default;
105
106void QgsAngleToNearestAlgorithm::initAlgorithm( const QVariantMap &configuration )
107{
108 mIsInPlace = configuration.value( QStringLiteral( "IN_PLACE" ) ).toBool();
109
110 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
111 QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorPoint ) );
112 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE_LAYER" ),
113 QObject::tr( "Reference layer" ) ) );
114
115 addParameter( new QgsProcessingParameterDistance( QStringLiteral( "MAX_DISTANCE" ),
116 QObject::tr( "Maximum distance to consider" ), QVariant(), QStringLiteral( "INPUT" ), true, 0 ) );
117
118 if ( !mIsInPlace )
119 addParameter( new QgsProcessingParameterString( QStringLiteral( "FIELD_NAME" ), QObject::tr( "Angle field name" ), QStringLiteral( "rotation" ) ) );
120 else
121 addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD_NAME" ), QObject::tr( "Angle field name" ), QStringLiteral( "rotation" ), QStringLiteral( "INPUT" ) ) );
122
123 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "APPLY_SYMBOLOGY" ), QObject::tr( "Automatically apply symbology" ), true ) );
124
125 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Aligned layer" ), QgsProcessing::TypeVectorPoint ) );
126}
127
128QgsProcessingAlgorithm::Flags QgsAngleToNearestAlgorithm::flags() const
129{
132 return f;
133}
134
135QString QgsAngleToNearestAlgorithm::shortHelpString() const
136{
137 return QObject::tr( "This algorithm calculates the rotation required to align point features with their nearest "
138 "feature from another reference layer. A new field is added to the output layer which is filled with the angle "
139 "(in degrees, clockwise) to the nearest reference feature.\n\n"
140 "Optionally, the output layer's symbology can be set to automatically use the calculated rotation "
141 "field to rotate marker symbols.\n\n"
142 "If desired, a maximum distance to use when aligning points can be set, to avoid aligning isolated points "
143 "to distant features." );
144}
145
146QString QgsAngleToNearestAlgorithm::shortDescription() const
147{
148 return QObject::tr( "Rotates point features to align them to nearby features." );
149}
150
151QgsAngleToNearestAlgorithm *QgsAngleToNearestAlgorithm::createInstance() const
152{
153 return new QgsAngleToNearestAlgorithm();
154}
155
156bool QgsAngleToNearestAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
157{
158 if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) )
159 {
160 return vl->geometryType() == QgsWkbTypes::PointGeometry;
161 }
162 return false;
163}
164
165bool QgsAngleToNearestAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
166{
167 if ( !mIsInPlace )
168 {
169 if ( QgsVectorLayer *sourceLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context ) )
170 {
171 mSourceRenderer.reset( sourceLayer->renderer()->clone() );
172 }
173 }
174
175 return true;
176}
177
178QVariantMap QgsAngleToNearestAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
179{
180 const double maxDistance = parameters.value( QStringLiteral( "MAX_DISTANCE" ) ).isValid() ? parameterAsDouble( parameters, QStringLiteral( "MAX_DISTANCE" ), context ) : std::numeric_limits< double >::quiet_NaN();
181 std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
182 if ( !input )
183 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
184
185 std::unique_ptr< QgsProcessingFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE_LAYER" ), context ) );
186 if ( !referenceSource )
187 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE_LAYER" ) ) );
188
189 const QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD_NAME" ), context );
190
191 QgsFields outFields = input->fields();
192 int fieldIndex = -1;
193 if ( mIsInPlace )
194 {
195 fieldIndex = outFields.lookupField( fieldName );
196 }
197 else
198 {
199 outFields.append( QgsField( fieldName, QVariant::Double ) );
200 }
201
202 QString dest;
203 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
204 input->wkbType(), input->sourceCrs() ) );
205 if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
206 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
207
208 // make spatial index
209 const QgsFeatureIterator f2 = referenceSource->getFeatures( QgsFeatureRequest().setDestinationCrs( input->sourceCrs(), context.transformContext() ).setNoAttributes() );
210 double step = referenceSource->featureCount() > 0 ? 50.0 / referenceSource->featureCount() : 1;
211 int i = 0;
212 const QgsSpatialIndex index( f2, [&]( const QgsFeature & )->bool
213 {
214 i++;
215 if ( feedback->isCanceled() )
216 return false;
217
218 feedback->setProgress( i * step );
219
220 return true;
222
223 QgsFeature f;
224
225 // Create output vector layer with additional attributes
226 step = input->featureCount() > 0 ? 50.0 / input->featureCount() : 1;
227 QgsFeatureIterator features = input->getFeatures();
228 i = 0;
229 while ( features.nextFeature( f ) )
230 {
231 i++;
232 if ( feedback->isCanceled() )
233 {
234 break;
235 }
236
237 feedback->setProgress( 50 + i * step );
238
239 QgsAttributes attributes = f.attributes();
240
241 if ( !f.hasGeometry() )
242 {
243 if ( !mIsInPlace )
244 attributes.append( QVariant() );
245 else
246 attributes[ fieldIndex ] = QVariant();
247 f.setAttributes( attributes );
248 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
249 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
250 }
251 else
252 {
253 const QList< QgsFeatureId > nearest = index.nearestNeighbor( f.geometry(), 1, std::isnan( maxDistance ) ? 0 : maxDistance );
254 if ( nearest.empty() )
255 {
256 feedback->pushInfo( QObject::tr( "No matching features found within search distance" ) );
257 if ( !mIsInPlace )
258 attributes.append( QVariant() );
259 else
260 attributes[ fieldIndex ] = QVariant();
261 f.setAttributes( attributes );
262 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
263 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
264 }
265 else
266 {
267 if ( nearest.count() > 1 )
268 {
269 feedback->pushInfo( QObject::tr( "Multiple matching features found at same distance from search feature, found %n feature(s)", nullptr, nearest.count() ) );
270 }
271
272 const QgsGeometry joinLine = f.geometry().shortestLine( index.geometry( nearest.at( 0 ) ) );
273 if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( joinLine.constGet() ) )
274 {
275 if ( !mIsInPlace )
276 attributes.append( line->startPoint().azimuth( line->endPoint() ) );
277 else
278 attributes[ fieldIndex ] = line->startPoint().azimuth( line->endPoint() );
279 }
280 else
281 {
282 if ( !mIsInPlace )
283 attributes.append( QVariant() );
284 else
285 attributes[ fieldIndex ] = QVariant();
286 }
287 f.setAttributes( attributes );
288 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
289 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
290 }
291 }
292 }
293
294 const bool applySymbology = parameterAsBool( parameters, QStringLiteral( "APPLY_SYMBOLOGY" ), context );
295 if ( applySymbology )
296 {
297 if ( mIsInPlace )
298 {
299 // get in place vector layer
300 // (possibly TODO - make this a reusable method!)
301 QVariantMap inPlaceParams = parameters;
302 inPlaceParams.insert( QStringLiteral( "INPUT" ), parameters.value( QStringLiteral( "INPUT" ) ).value< QgsProcessingFeatureSourceDefinition >().source );
303 if ( QgsVectorLayer *sourceLayer = parameterAsVectorLayer( inPlaceParams, QStringLiteral( "INPUT" ), context ) )
304 {
305 std::unique_ptr< QgsFeatureRenderer > sourceRenderer( sourceLayer->renderer()->clone() );
306 SetMarkerRotationPostProcessor processor( std::move( sourceRenderer ), fieldName );
307 processor.postProcessLayer( sourceLayer, context, feedback );
308 }
309 }
310 else if ( mSourceRenderer && context.willLoadLayerOnCompletion( dest ) )
311 {
312 context.layerToLoadOnCompletionDetails( dest ).setPostProcessor( new SetMarkerRotationPostProcessor( std::move( mSourceRenderer ), fieldName ) );
313 }
314 }
315
316 QVariantMap outputs;
317 outputs.insert( QStringLiteral( "OUTPUT" ), dest );
318 return outputs;
319}
320
321
A vector of attributes.
Definition: qgsattributes.h:59
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:51
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:164
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsGeometry shortestLine(const QgsGeometry &other) const
Returns the shortest line joining this geometry to another geometry.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:45
Base class for all map layer types.
Definition: qgsmaplayer.h:73
A marker symbol type, for rendering Point and MultiPoint geometries.
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
@ FlagSupportsInPlaceEdits
Algorithm supports in-place editing.
void setPostProcessor(QgsProcessingLayerPostProcessorInterface *processor)
Sets the layer post-processor.
Contains information about the context in which a processing algorithm is executed.
QgsProcessingContext::LayerDetails & layerToLoadOnCompletionDetails(const QString &layer)
Returns a reference to the details for a given layer which is loaded on completion of the algorithm o...
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
bool willLoadLayerOnCompletion(const QString &layer) const
Returns true if the given layer (by ID or datasource) will be loaded into the current project upon co...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
An interface for layer post-processing handlers for execution following a processing algorithm operat...
virtual void postProcessLayer(QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback)=0
Post-processes the specified layer, following successful execution of a processing algorithm.
A boolean parameter for processing algorithms.
A double numeric parameter for distance values.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer or feature source field parameter for processing algorithms.
A string parameter for processing algorithms.
@ TypeVectorPoint
Vector point layers.
Definition: qgsprocessing.h:49
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
A spatial index for QgsFeature objects.
@ FlagStoreFeatureGeometries
Indicates that the spatial index should also store feature geometries. This requires more memory,...
An interface for classes which can visit style entity (e.g.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Definition: qgsstyle.h:1342
Represents a vector layer which manages a vector based data sets.
Contains information relating to the style entity currently being visited.
const QgsStyleEntityInterface * entity
Reference to style entity being visited.