QGIS API Documentation  3.12.1-București (121cc00ff0)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
qgsalgorithmjoinbynearest.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmjoinbynearest.cpp
3  ---------------------
4  begin : April 2017
5  copyright : (C) 2017 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 
23 
24 QString QgsJoinByNearestAlgorithm::name() const
25 {
26  return QStringLiteral( "joinbynearest" );
27 }
28 
29 QString QgsJoinByNearestAlgorithm::displayName() const
30 {
31  return QObject::tr( "Join attributes by nearest" );
32 }
33 
34 QStringList QgsJoinByNearestAlgorithm::tags() const
35 {
36  return QObject::tr( "join,connect,attributes,values,fields,tables,proximity,closest,neighbour,neighbor,n-nearest,distance" ).split( ',' );
37 }
38 
39 QString QgsJoinByNearestAlgorithm::group() const
40 {
41  return QObject::tr( "Vector general" );
42 }
43 
44 QString QgsJoinByNearestAlgorithm::groupId() const
45 {
46  return QStringLiteral( "vectorgeneral" );
47 }
48 
49 void QgsJoinByNearestAlgorithm::initAlgorithm( const QVariantMap & )
50 {
51  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
52  QObject::tr( "Input layer" ) ) );
53  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT_2" ),
54  QObject::tr( "Input layer 2" ) ) );
55 
56  addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELDS_TO_COPY" ),
57  QObject::tr( "Layer 2 fields to copy (leave empty to copy all fields)" ),
58  QVariant(), QStringLiteral( "INPUT_2" ), QgsProcessingParameterField::Any,
59  true, true ) );
60 
61  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISCARD_NONMATCHING" ),
62  QObject::tr( "Discard records which could not be joined" ),
63  false ) );
64 
65  addParameter( new QgsProcessingParameterString( QStringLiteral( "PREFIX" ),
66  QObject::tr( "Joined field prefix" ), QVariant(), false, true ) );
67 
68  addParameter( new QgsProcessingParameterNumber( QStringLiteral( "NEIGHBORS" ),
69  QObject::tr( "Maximum nearest neighbors" ), QgsProcessingParameterNumber::Integer, 1, false, 1 ) );
70 
71  addParameter( new QgsProcessingParameterDistance( QStringLiteral( "MAX_DISTANCE" ),
72  QObject::tr( "Maximum distance" ), QVariant(), QStringLiteral( "INPUT" ), true, 0 ) );
73 
74  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Joined layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, true ) );
75 
76  std::unique_ptr< QgsProcessingParameterFeatureSink > nonMatchingSink = qgis::make_unique< QgsProcessingParameterFeatureSink >(
77  QStringLiteral( "NON_MATCHING" ), QObject::tr( "Unjoinable features from first layer" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, false );
78  // TODO GUI doesn't support advanced outputs yet
79  //nonMatchingSink->setFlags(nonMatchingSink->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
80  addParameter( nonMatchingSink.release() );
81 
82  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "JOINED_COUNT" ), QObject::tr( "Number of joined features from input table" ) ) );
83  addOutput( new QgsProcessingOutputNumber( QStringLiteral( "UNJOINABLE_COUNT" ), QObject::tr( "Number of unjoinable features from input table" ) ) );
84 }
85 
86 QString QgsJoinByNearestAlgorithm::shortHelpString() const
87 {
88  return QObject::tr( "This algorithm takes an input vector layer and creates a new vector layer that is an extended version of the "
89  "input one, with additional attributes in its attribute table.\n\n"
90  "The additional attributes and their values are taken from a second vector layer, where features are joined "
91  "by finding the closest features from each layer. By default only the single nearest feature is joined,"
92  "but optionally the join can use the n-nearest neighboring features instead. If multiple features are found "
93  "with identical distances these will all be returned (even if the total number of features exceeds the specified "
94  "maximum feature count).\n\n"
95  "If a maximum distance is specified, then only features which are closer than this distance "
96  "will be matched.\n\n"
97  "The output features will contain the selected attributes from the nearest feature, "
98  "along with new attributes for the distance to the near feature, the index of the feature, "
99  "and the coordinates of the closest point on the input feature (feature_x, feature_y) "
100  "to the matched nearest feature, and the coordinates of the closet point on the matched feature "
101  "(nearest_x, nearest_y).\n\n"
102  "This algorithm uses purely Cartesian calculations for distance, and does not consider "
103  "geodetic or ellipsoid properties when determining feature proximity." );
104 }
105 
106 QString QgsJoinByNearestAlgorithm::shortDescription() const
107 {
108  return QObject::tr( "Joins a layer to another layer, using the closest features (nearest neighbors)." );
109 }
110 
111 QgsJoinByNearestAlgorithm *QgsJoinByNearestAlgorithm::createInstance() const
112 {
113  return new QgsJoinByNearestAlgorithm();
114 }
115 
116 QVariantMap QgsJoinByNearestAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
117 {
118  const int neighbors = parameterAsInt( parameters, QStringLiteral( "NEIGHBORS" ), context );
119  const bool discardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
120  const double maxDistance = parameters.value( QStringLiteral( "MAX_DISTANCE" ) ).isValid() ? parameterAsDouble( parameters, QStringLiteral( "MAX_DISTANCE" ), context ) : std::numeric_limits< double >::quiet_NaN();
121  std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
122  if ( !input )
123  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
124 
125  std::unique_ptr< QgsProcessingFeatureSource > input2( parameterAsSource( parameters, QStringLiteral( "INPUT_2" ), context ) );
126  if ( !input2 )
127  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_2" ) ) );
128 
129  const bool sameSourceAndTarget = parameters.value( QStringLiteral( "INPUT" ) ) == parameters.value( QStringLiteral( "INPUT_2" ) );
130 
131  QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
132  const QStringList fieldsToCopy = parameterAsFields( parameters, QStringLiteral( "FIELDS_TO_COPY" ), context );
133 
134  QgsFields outFields2;
135  QgsAttributeList fields2Indices;
136  if ( fieldsToCopy.empty() )
137  {
138  outFields2 = input2->fields();
139  fields2Indices.reserve( outFields2.count() );
140  for ( int i = 0; i < outFields2.count(); ++i )
141  {
142  fields2Indices << i;
143  }
144  }
145  else
146  {
147  fields2Indices.reserve( fieldsToCopy.count() );
148  for ( const QString &field : fieldsToCopy )
149  {
150  int index = input2->fields().lookupField( field );
151  if ( index >= 0 )
152  {
153  fields2Indices << index;
154  outFields2.append( input2->fields().at( index ) );
155  }
156  }
157  }
158 
159  if ( !prefix.isEmpty() )
160  {
161  for ( int i = 0; i < outFields2.count(); ++i )
162  {
163  outFields2.rename( i, prefix + outFields2[ i ].name() );
164  }
165  }
166 
167  QgsAttributeList fields2Fetch = fields2Indices;
168 
169  QgsFields outFields = QgsProcessingUtils::combineFields( input->fields(), outFields2 );
170  outFields.append( QgsField( QStringLiteral( "n" ), QVariant::Int ) );
171  outFields.append( QgsField( QStringLiteral( "distance" ), QVariant::Double ) );
172  outFields.append( QgsField( QStringLiteral( "feature_x" ), QVariant::Double ) );
173  outFields.append( QgsField( QStringLiteral( "feature_y" ), QVariant::Double ) );
174  outFields.append( QgsField( QStringLiteral( "nearest_x" ), QVariant::Double ) );
175  outFields.append( QgsField( QStringLiteral( "nearest_y" ), QVariant::Double ) );
176 
177  QString dest;
178  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
179  input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
180  if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
181  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
182 
183  QString destNonMatching1;
184  std::unique_ptr< QgsFeatureSink > sinkNonMatching1( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, destNonMatching1, input->fields(),
185  input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
186  if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !sinkNonMatching1 )
187  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );
188 
189  // make spatial index
190  QgsFeatureIterator f2 = input2->getFeatures( QgsFeatureRequest().setDestinationCrs( input->sourceCrs(), context.transformContext() ).setSubsetOfAttributes( fields2Fetch ) );
191  QHash< QgsFeatureId, QgsAttributes > input2AttributeCache;
192  double step = input2->featureCount() > 0 ? 50.0 / input2->featureCount() : 1;
193  int i = 0;
194  QgsSpatialIndex index( f2, [&]( const QgsFeature & f )->bool
195  {
196  i++;
197  if ( feedback->isCanceled() )
198  return false;
199 
200  feedback->setProgress( i * step );
201 
202  if ( !f.hasGeometry() )
203  return true;
204 
205  // only keep selected attributes
206  QgsAttributes attributes;
207  for ( int j = 0; j < f.attributes().count(); ++j )
208  {
209  if ( ! fields2Indices.contains( j ) )
210  continue;
211  attributes << f.attribute( j );
212  }
213  input2AttributeCache.insert( f.id(), attributes );
214 
215  return true;
217 
218  QgsFeature f;
219 
220  // create extra null attributes for non-matched records (the +2 is for the "n" and "distance", and start/end x/y fields)
221  QgsAttributes nullMatch;
222  nullMatch.reserve( fields2Indices.size() + 6 );
223  for ( int i = 0; i < fields2Indices.count() + 6; ++i )
224  nullMatch << QVariant();
225 
226  long long joinedCount = 0;
227  long long unjoinedCount = 0;
228 
229  // Create output vector layer with additional attributes
230  step = input->featureCount() > 0 ? 50.0 / input->featureCount() : 1;
231  QgsFeatureIterator features = input->getFeatures();
232  i = 0;
233  while ( features.nextFeature( f ) )
234  {
235  i++;
236  if ( feedback->isCanceled() )
237  {
238  break;
239  }
240 
241  feedback->setProgress( 50 + i * step );
242 
243  if ( !f.hasGeometry() )
244  {
245  unjoinedCount++;
246  if ( sinkNonMatching1 )
247  {
248  sinkNonMatching1->addFeature( f, QgsFeatureSink::FastInsert );
249  }
250  if ( sink && !discardNonMatching )
251  {
252  QgsAttributes attr = f.attributes();
253  attr.append( nullMatch );
254  f.setAttributes( attr );
255  sink->addFeature( f, QgsFeatureSink::FastInsert );
256  }
257  }
258  else
259  {
260  // note - if using same source as target, we have to get one extra neighbor, since the first match will be the input feature
261  const QList< QgsFeatureId > nearest = index.nearestNeighbor( f.geometry(), neighbors + ( sameSourceAndTarget ? 1 : 0 ), std::isnan( maxDistance ) ? 0 : maxDistance );
262 
263  if ( nearest.count() > neighbors + ( sameSourceAndTarget ? 1 : 0 ) )
264  {
265  feedback->pushInfo( QObject::tr( "Multiple matching features found at same distance from search feature, found %1 features instead of %2" ).arg( nearest.count() - ( sameSourceAndTarget ? 1 : 0 ) ).arg( neighbors ) );
266  }
267  QgsFeature out;
268  out.setGeometry( f.geometry() );
269  int j = 0;
270  for ( QgsFeatureId id : nearest )
271  {
272  if ( sameSourceAndTarget && id == f.id() )
273  continue; // don't match to same feature if using a single input table
274  j++;
275  if ( sink )
276  {
277  QgsAttributes attr = f.attributes();
278  attr.append( input2AttributeCache.value( id ) );
279  attr.append( j );
280 
281  const QgsGeometry closestLine = f.geometry().shortestLine( index.geometry( id ) );
282  if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString *>( closestLine.constGet() ) )
283  {
284  attr.append( line->length() );
285  attr.append( line->startPoint().x() );
286  attr.append( line->startPoint().y() );
287  attr.append( line->endPoint().x() );
288  attr.append( line->endPoint().y() );
289  }
290  else
291  {
292  attr.append( QVariant() ); //distance
293  attr.append( QVariant() ); //start x
294  attr.append( QVariant() ); //start y
295  attr.append( QVariant() ); //end x
296  attr.append( QVariant() ); //end y
297  }
298  out.setAttributes( attr );
299  sink->addFeature( out, QgsFeatureSink::FastInsert );
300  }
301  }
302  if ( j > 0 )
303  joinedCount++;
304  else
305  {
306  if ( sinkNonMatching1 )
307  {
308  sinkNonMatching1->addFeature( f, QgsFeatureSink::FastInsert );
309  }
310  if ( !discardNonMatching && sink )
311  {
312  QgsAttributes attr = f.attributes();
313  attr.append( nullMatch );
314  f.setAttributes( attr );
315  sink->addFeature( f, QgsFeatureSink::FastInsert );
316  }
317  unjoinedCount++;
318  }
319  }
320  }
321 
322  QVariantMap outputs;
323  outputs.insert( QStringLiteral( "JOINED_COUNT" ), joinedCount );
324  outputs.insert( QStringLiteral( "UNJOINABLE_COUNT" ), unjoinedCount );
325  if ( sink )
326  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
327  if ( sinkNonMatching1 )
328  outputs.insert( QStringLiteral( "NON_MATCHING" ), destNonMatching1 );
329  return outputs;
330 }
331 
332 
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
A boolean parameter for processing algorithms.
QgsFeatureId id
Definition: qgsfeature.h:64
Wrapper for iterator of features from vector data provider or vector layer.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Base class for providing feedback from a processing algorithm.
A vector layer or feature source field parameter for processing algorithms.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:64
qint64 QgsFeatureId
Definition: qgsfeatureid.h:25
Container of fields for a vector layer.
Definition: qgsfields.h:42
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:122
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
A numeric output for processing algorithms.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool rename(int fieldIdx, const QString &name)
Renames a name of field.
Definition: qgsfields.cpp:72
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
int count() const
Returns number of items.
Definition: qgsfields.cpp:133
A feature sink output for processing algorithms.
This flag indicates, that a primary key field cannot be guaranteed to be unique and the sink should i...
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
A double numeric parameter for distance values.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
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
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:49
A numeric parameter for processing algorithms.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
A spatial index for QgsFeature objects.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:55
An input feature source (such as vector layers) parameter for processing algorithms.
Line string geometry type, with support for z-dimension and m-values.
Definition: qgslinestring.h:43
QgsGeometry shortestLine(const QgsGeometry &other) const
Returns the shortest line joining this geometry to another geometry.
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
QList< int > QgsAttributeList
Definition: qgsfield.h:26
bool nextFeature(QgsFeature &f)
static QgsFields combineFields(const QgsFields &fieldsA, const QgsFields &fieldsB, const QString &fieldsBPrefix=QString())
Combines two field lists, avoiding duplicate field names (in a case-insensitive manner).
A vector of attributes.
Definition: qgsattributes.h:57
QVariant attribute(const QString &name) const
Lookup attribute value from attribute name.
Definition: qgsfeature.cpp:262
Contains information about the context in which a processing algorithm is executed.
A string parameter for processing algorithms.
Any vector layer with geometry.
Definition: qgsprocessing.h:47
QgsAttributes attributes
Definition: qgsfeature.h:65
Indicates that the spatial index should also store feature geometries. This requires more memory...