QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 
19 #include "qgsprocessingoutputs.h"
20 #include "qgslinestring.h"
21 #include "qgsvectorlayer.h"
22 #include "qgsrenderer.h"
23 #include "qgsstyleentityvisitor.h"
24 #include "qgsmarkersymbol.h"
25 
27 
28 class 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 
53 class 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 
79 QString QgsAngleToNearestAlgorithm::name() const
80 {
81  return QStringLiteral( "angletonearest" );
82 }
83 
84 QString QgsAngleToNearestAlgorithm::displayName() const
85 {
86  return QObject::tr( "Align points to features" );
87 }
88 
89 QStringList QgsAngleToNearestAlgorithm::tags() const
90 {
91  return QObject::tr( "align,marker,stroke,fill,orient,points,lines,angles,rotation,rotate" ).split( ',' );
92 }
93 
94 QString QgsAngleToNearestAlgorithm::group() const
95 {
96  return QObject::tr( "Cartography" );
97 }
98 
99 QString QgsAngleToNearestAlgorithm::groupId() const
100 {
101  return QStringLiteral( "cartography" );
102 }
103 
104 QgsAngleToNearestAlgorithm::~QgsAngleToNearestAlgorithm() = default;
105 
106 void 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 
128 QgsProcessingAlgorithm::Flags QgsAngleToNearestAlgorithm::flags() const
129 {
130  Flags f = QgsProcessingAlgorithm::flags();
132  return f;
133 }
134 
135 QString 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 
146 QString QgsAngleToNearestAlgorithm::shortDescription() const
147 {
148  return QObject::tr( "Rotates point features to align them to nearby features." );
149 }
150 
151 QgsAngleToNearestAlgorithm *QgsAngleToNearestAlgorithm::createInstance() const
152 {
153  return new QgsAngleToNearestAlgorithm();
154 }
155 
156 bool 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 
165 bool 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 
178 QVariantMap 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  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  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  sink->addFeature( f, QgsFeatureSink::FastInsert );
249  }
250  else
251  {
252  const QList< QgsFeatureId > nearest = index.nearestNeighbor( f.geometry(), 1, std::isnan( maxDistance ) ? 0 : maxDistance );
253  if ( nearest.empty() )
254  {
255  feedback->pushInfo( QObject::tr( "No matching features found within search distance" ) );
256  if ( !mIsInPlace )
257  attributes.append( QVariant() );
258  else
259  attributes[ fieldIndex ] = QVariant();
260  f.setAttributes( attributes );
261  sink->addFeature( f, QgsFeatureSink::FastInsert );
262  }
263  else
264  {
265  if ( nearest.count() > 1 )
266  {
267  feedback->pushInfo( QObject::tr( "Multiple matching features found at same distance from search feature, found %1 features" ).arg( nearest.count() ) );
268  }
269 
270  const QgsGeometry joinLine = f.geometry().shortestLine( index.geometry( nearest.at( 0 ) ) );
271  if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( joinLine.constGet() ) )
272  {
273  if ( !mIsInPlace )
274  attributes.append( line->startPoint().azimuth( line->endPoint() ) );
275  else
276  attributes[ fieldIndex ] = line->startPoint().azimuth( line->endPoint() );
277  }
278  else
279  {
280  if ( !mIsInPlace )
281  attributes.append( QVariant() );
282  else
283  attributes[ fieldIndex ] = QVariant();
284  }
285  f.setAttributes( attributes );
286  sink->addFeature( f, QgsFeatureSink::FastInsert );
287  }
288  }
289  }
290 
291  const bool applySymbology = parameterAsBool( parameters, QStringLiteral( "APPLY_SYMBOLOGY" ), context );
292  if ( applySymbology )
293  {
294  if ( mIsInPlace )
295  {
296  // get in place vector layer
297  // (possibly TODO - make this a reusable method!)
298  QVariantMap inPlaceParams = parameters;
299  inPlaceParams.insert( QStringLiteral( "INPUT" ), parameters.value( QStringLiteral( "INPUT" ) ).value< QgsProcessingFeatureSourceDefinition >().source );
300  if ( QgsVectorLayer *sourceLayer = parameterAsVectorLayer( inPlaceParams, QStringLiteral( "INPUT" ), context ) )
301  {
302  std::unique_ptr< QgsFeatureRenderer > sourceRenderer( sourceLayer->renderer()->clone() );
303  SetMarkerRotationPostProcessor processor( std::move( sourceRenderer ), fieldName );
304  processor.postProcessLayer( sourceLayer, context, feedback );
305  }
306  }
307  else if ( mSourceRenderer && context.willLoadLayerOnCompletion( dest ) )
308  {
309  context.layerToLoadOnCompletionDetails( dest ).setPostProcessor( new SetMarkerRotationPostProcessor( std::move( mSourceRenderer ), fieldName ) );
310  }
311  }
312 
313  QVariantMap outputs;
314  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
315  return outputs;
316 }
317 
318 
A vector of attributes.
Definition: qgsattributes.h:58
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:344
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
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:44
Base class for all map layer types.
Definition: qgsmaplayer.h:70
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.
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...
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...
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:1219
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.