Branch data Line data Source code
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 : :
18 : : #include "qgsalgorithmangletonearest.h"
19 : : #include "qgsprocessingoutputs.h"
20 : : #include "qgslinestring.h"
21 : : #include "qgsvectorlayer.h"
22 : : #include "qgsrenderer.h"
23 : : #include "qgsstyleentityvisitor.h"
24 : :
25 : : ///@cond PRIVATE
26 : :
27 : 0 : class SetMarkerRotationVisitor : public QgsStyleEntityVisitorInterface
28 : : {
29 : : public:
30 : :
31 : 0 : SetMarkerRotationVisitor( const QString &rotationField )
32 : 0 : : mRotationField( rotationField )
33 : 0 : {}
34 : :
35 : 0 : bool visit( const QgsStyleEntityVisitorInterface::StyleLeaf &entity )
36 : : {
37 : 0 : if ( const QgsStyleSymbolEntity *symbolEntity = dynamic_cast< const QgsStyleSymbolEntity * >( entity.entity ) )
38 : : {
39 : 0 : if ( QgsMarkerSymbol *marker = dynamic_cast< QgsMarkerSymbol * >( symbolEntity->symbol() ) )
40 : : {
41 : 0 : marker->setDataDefinedAngle( QgsProperty::fromField( mRotationField ) );
42 : 0 : }
43 : 0 : }
44 : 0 : return true;
45 : 0 : }
46 : :
47 : : private:
48 : : QString mRotationField;
49 : :
50 : : };
51 : :
52 : 0 : class SetMarkerRotationPostProcessor : public QgsProcessingLayerPostProcessorInterface
53 : : {
54 : : public:
55 : :
56 : 0 : SetMarkerRotationPostProcessor( std::unique_ptr< QgsFeatureRenderer > renderer, const QString &rotationField )
57 : 0 : : mRenderer( std::move( renderer ) )
58 : 0 : , mRotationField( rotationField )
59 : 0 : {}
60 : :
61 : 0 : void postProcessLayer( QgsMapLayer *layer, QgsProcessingContext &, QgsProcessingFeedback * ) override
62 : : {
63 : 0 : if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
64 : : {
65 : 0 : SetMarkerRotationVisitor visitor( mRotationField );
66 : 0 : mRenderer->accept( &visitor );
67 : 0 : vl->setRenderer( mRenderer.release() );
68 : 0 : vl->triggerRepaint();
69 : 0 : }
70 : 0 : }
71 : :
72 : : private:
73 : :
74 : : std::unique_ptr<QgsFeatureRenderer> mRenderer;
75 : : QString mRotationField;
76 : : };
77 : :
78 : 0 : QString QgsAngleToNearestAlgorithm::name() const
79 : : {
80 : 0 : return QStringLiteral( "angletonearest" );
81 : : }
82 : :
83 : 0 : QString QgsAngleToNearestAlgorithm::displayName() const
84 : : {
85 : 0 : return QObject::tr( "Align points to features" );
86 : : }
87 : :
88 : 0 : QStringList QgsAngleToNearestAlgorithm::tags() const
89 : : {
90 : 0 : return QObject::tr( "align,marker,stroke,fill,orient,points,lines,angles,rotation,rotate" ).split( ',' );
91 : 0 : }
92 : :
93 : 0 : QString QgsAngleToNearestAlgorithm::group() const
94 : : {
95 : 0 : return QObject::tr( "Cartography" );
96 : : }
97 : :
98 : 0 : QString QgsAngleToNearestAlgorithm::groupId() const
99 : : {
100 : 0 : return QStringLiteral( "cartography" );
101 : : }
102 : :
103 : 0 : QgsAngleToNearestAlgorithm::~QgsAngleToNearestAlgorithm() = default;
104 : :
105 : 0 : void QgsAngleToNearestAlgorithm::initAlgorithm( const QVariantMap &configuration )
106 : : {
107 : 0 : mIsInPlace = configuration.value( QStringLiteral( "IN_PLACE" ) ).toBool();
108 : :
109 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
110 : 0 : QObject::tr( "Input layer" ), QList< int >() << QgsProcessing::TypeVectorPoint ) );
111 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE_LAYER" ),
112 : 0 : QObject::tr( "Reference layer" ) ) );
113 : :
114 : 0 : addParameter( new QgsProcessingParameterDistance( QStringLiteral( "MAX_DISTANCE" ),
115 : 0 : QObject::tr( "Maximum distance to consider" ), QVariant(), QStringLiteral( "INPUT" ), true, 0 ) );
116 : :
117 : 0 : if ( !mIsInPlace )
118 : 0 : addParameter( new QgsProcessingParameterString( QStringLiteral( "FIELD_NAME" ), QObject::tr( "Angle field name" ), QStringLiteral( "rotation" ) ) );
119 : : else
120 : 0 : addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD_NAME" ), QObject::tr( "Angle field name" ), QStringLiteral( "rotation" ), QStringLiteral( "INPUT" ) ) );
121 : :
122 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "APPLY_SYMBOLOGY" ), QObject::tr( "Automatically apply symbology" ), true ) );
123 : :
124 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Aligned layer" ), QgsProcessing::TypeVectorPoint ) );
125 : 0 : }
126 : :
127 : 0 : QgsProcessingAlgorithm::Flags QgsAngleToNearestAlgorithm::flags() const
128 : : {
129 : 0 : Flags f = QgsProcessingAlgorithm::flags();
130 : 0 : f |= QgsProcessingAlgorithm::FlagSupportsInPlaceEdits;
131 : 0 : return f;
132 : : }
133 : :
134 : 0 : QString QgsAngleToNearestAlgorithm::shortHelpString() const
135 : : {
136 : 0 : return QObject::tr( "This algorithm calculates the rotation required to align point features with their nearest "
137 : : "feature from another reference layer. A new field is added to the output layer which is filled with the angle "
138 : : "(in degrees, clockwise) to the nearest reference feature.\n\n"
139 : : "Optionally, the output layer's symbology can be set to automatically use the calculated rotation "
140 : : "field to rotate marker symbols.\n\n"
141 : : "If desired, a maximum distance to use when aligning points can be set, to avoid aligning isolated points "
142 : : "to distant features." );
143 : : }
144 : :
145 : 0 : QString QgsAngleToNearestAlgorithm::shortDescription() const
146 : : {
147 : 0 : return QObject::tr( "Rotates point features to align them to nearby features." );
148 : : }
149 : :
150 : 0 : QgsAngleToNearestAlgorithm *QgsAngleToNearestAlgorithm::createInstance() const
151 : : {
152 : 0 : return new QgsAngleToNearestAlgorithm();
153 : : }
154 : :
155 : 0 : bool QgsAngleToNearestAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
156 : : {
157 : 0 : if ( const QgsVectorLayer *vl = qobject_cast< const QgsVectorLayer * >( layer ) )
158 : : {
159 : 0 : return vl->geometryType() == QgsWkbTypes::PointGeometry;
160 : : }
161 : 0 : return false;
162 : 0 : }
163 : :
164 : 0 : bool QgsAngleToNearestAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
165 : : {
166 : 0 : if ( !mIsInPlace )
167 : : {
168 : 0 : if ( QgsVectorLayer *sourceLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context ) )
169 : : {
170 : 0 : mSourceRenderer.reset( sourceLayer->renderer()->clone() );
171 : 0 : }
172 : 0 : }
173 : :
174 : 0 : return true;
175 : 0 : }
176 : :
177 : 0 : QVariantMap QgsAngleToNearestAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
178 : : {
179 : 0 : const double maxDistance = parameters.value( QStringLiteral( "MAX_DISTANCE" ) ).isValid() ? parameterAsDouble( parameters, QStringLiteral( "MAX_DISTANCE" ), context ) : std::numeric_limits< double >::quiet_NaN();
180 : 0 : std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
181 : 0 : if ( !input )
182 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
183 : :
184 : 0 : std::unique_ptr< QgsProcessingFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE_LAYER" ), context ) );
185 : 0 : if ( !referenceSource )
186 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE_LAYER" ) ) );
187 : :
188 : 0 : const QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD_NAME" ), context );
189 : :
190 : 0 : QgsFields outFields = input->fields();
191 : 0 : int fieldIndex = -1;
192 : 0 : if ( mIsInPlace )
193 : : {
194 : 0 : fieldIndex = outFields.lookupField( fieldName );
195 : 0 : }
196 : : else
197 : : {
198 : 0 : outFields.append( QgsField( fieldName, QVariant::Double ) );
199 : : }
200 : :
201 : 0 : QString dest;
202 : 0 : std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
203 : 0 : input->wkbType(), input->sourceCrs() ) );
204 : 0 : if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
205 : 0 : throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
206 : :
207 : : // make spatial index
208 : 0 : QgsFeatureIterator f2 = referenceSource->getFeatures( QgsFeatureRequest().setDestinationCrs( input->sourceCrs(), context.transformContext() ).setNoAttributes() );
209 : 0 : double step = referenceSource->featureCount() > 0 ? 50.0 / referenceSource->featureCount() : 1;
210 : 0 : int i = 0;
211 : 0 : QgsSpatialIndex index( f2, [&]( const QgsFeature & )->bool
212 : : {
213 : 0 : i++;
214 : 0 : if ( feedback->isCanceled() )
215 : 0 : return false;
216 : :
217 : 0 : feedback->setProgress( i * step );
218 : :
219 : 0 : return true;
220 : 0 : }, QgsSpatialIndex::FlagStoreFeatureGeometries );
221 : :
222 : 0 : QgsFeature f;
223 : :
224 : : // Create output vector layer with additional attributes
225 : 0 : step = input->featureCount() > 0 ? 50.0 / input->featureCount() : 1;
226 : 0 : QgsFeatureIterator features = input->getFeatures();
227 : 0 : i = 0;
228 : 0 : while ( features.nextFeature( f ) )
229 : : {
230 : 0 : i++;
231 : 0 : if ( feedback->isCanceled() )
232 : : {
233 : 0 : break;
234 : : }
235 : :
236 : 0 : feedback->setProgress( 50 + i * step );
237 : :
238 : 0 : QgsAttributes attributes = f.attributes();
239 : :
240 : 0 : if ( !f.hasGeometry() )
241 : : {
242 : 0 : if ( !mIsInPlace )
243 : 0 : attributes.append( QVariant() );
244 : : else
245 : 0 : attributes[ fieldIndex ] = QVariant();
246 : 0 : f.setAttributes( attributes );
247 : 0 : sink->addFeature( f, QgsFeatureSink::FastInsert );
248 : 0 : }
249 : : else
250 : : {
251 : 0 : const QList< QgsFeatureId > nearest = index.nearestNeighbor( f.geometry(), 1, std::isnan( maxDistance ) ? 0 : maxDistance );
252 : 0 : if ( nearest.empty() )
253 : : {
254 : 0 : feedback->pushInfo( QObject::tr( "No matching features found within search distance" ) );
255 : 0 : if ( !mIsInPlace )
256 : 0 : attributes.append( QVariant() );
257 : : else
258 : 0 : attributes[ fieldIndex ] = QVariant();
259 : 0 : f.setAttributes( attributes );
260 : 0 : sink->addFeature( f, QgsFeatureSink::FastInsert );
261 : 0 : }
262 : : else
263 : : {
264 : 0 : if ( nearest.count() > 1 )
265 : : {
266 : 0 : feedback->pushInfo( QObject::tr( "Multiple matching features found at same distance from search feature, found %1 features" ).arg( nearest.count() ) );
267 : 0 : }
268 : :
269 : 0 : const QgsGeometry joinLine = f.geometry().shortestLine( index.geometry( nearest.at( 0 ) ) );
270 : 0 : if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString * >( joinLine.constGet() ) )
271 : : {
272 : 0 : if ( !mIsInPlace )
273 : 0 : attributes.append( line->startPoint().azimuth( line->endPoint() ) );
274 : : else
275 : 0 : attributes[ fieldIndex ] = line->startPoint().azimuth( line->endPoint() );
276 : 0 : }
277 : : else
278 : : {
279 : 0 : if ( !mIsInPlace )
280 : 0 : attributes.append( QVariant() );
281 : : else
282 : 0 : attributes[ fieldIndex ] = QVariant();
283 : : }
284 : 0 : f.setAttributes( attributes );
285 : 0 : sink->addFeature( f, QgsFeatureSink::FastInsert );
286 : 0 : }
287 : 0 : }
288 : 0 : }
289 : :
290 : 0 : const bool applySymbology = parameterAsBool( parameters, QStringLiteral( "APPLY_SYMBOLOGY" ), context );
291 : 0 : if ( applySymbology )
292 : : {
293 : 0 : if ( mIsInPlace )
294 : : {
295 : : // get in place vector layer
296 : : // (possibly TODO - make this a reusable method!)
297 : 0 : QVariantMap inPlaceParams = parameters;
298 : 0 : inPlaceParams.insert( QStringLiteral( "INPUT" ), parameters.value( QStringLiteral( "INPUT" ) ).value< QgsProcessingFeatureSourceDefinition >().source );
299 : 0 : if ( QgsVectorLayer *sourceLayer = parameterAsVectorLayer( inPlaceParams, QStringLiteral( "INPUT" ), context ) )
300 : : {
301 : 0 : std::unique_ptr< QgsFeatureRenderer > sourceRenderer( sourceLayer->renderer()->clone() );
302 : 0 : SetMarkerRotationPostProcessor processor( std::move( sourceRenderer ), fieldName );
303 : 0 : processor.postProcessLayer( sourceLayer, context, feedback );
304 : 0 : }
305 : 0 : }
306 : 0 : else if ( mSourceRenderer && context.willLoadLayerOnCompletion( dest ) )
307 : : {
308 : 0 : context.layerToLoadOnCompletionDetails( dest ).setPostProcessor( new SetMarkerRotationPostProcessor( std::move( mSourceRenderer ), fieldName ) );
309 : 0 : }
310 : 0 : }
311 : :
312 : 0 : QVariantMap outputs;
313 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), dest );
314 : 0 : return outputs;
315 : 0 : }
316 : :
317 : :
318 : : ///@endcond
|