QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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.\n\n"
93  "If a maximum distance is specified, then only features which are closer than this distance "
94  "will be matched.\n\n"
95  "The output features will contain the selected attributes from the nearest feature, "
96  "along with new attributes for the distance to the near feature, the index of the feature, "
97  "and the coordinates of the closest point on the input feature (feature_x, feature_y) "
98  "to the matched nearest feature, and the coordinates of the closet point on the matched feature "
99  "(nearest_x, nearest_y).\n\n"
100  "This algorithm uses purely Cartesian calculations for distance, and does not consider "
101  "geodetic or ellipsoid properties when determining feature proximity." );
102 }
103 
104 QString QgsJoinByNearestAlgorithm::shortDescription() const
105 {
106  return QObject::tr( "Joins a layer to another layer, using the closest features (nearest neighbors)." );
107 }
108 
109 QgsJoinByNearestAlgorithm *QgsJoinByNearestAlgorithm::createInstance() const
110 {
111  return new QgsJoinByNearestAlgorithm();
112 }
113 
114 QVariantMap QgsJoinByNearestAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
115 {
116  const int neighbors = parameterAsInt( parameters, QStringLiteral( "NEIGHBORS" ), context );
117  const bool discardNonMatching = parameterAsBoolean( parameters, QStringLiteral( "DISCARD_NONMATCHING" ), context );
118  const double maxDistance = parameters.value( QStringLiteral( "MAX_DISTANCE" ) ).isValid() ? parameterAsDouble( parameters, QStringLiteral( "MAX_DISTANCE" ), context ) : std::numeric_limits< double >::quiet_NaN();
119  std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
120  if ( !input )
121  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
122 
123  std::unique_ptr< QgsProcessingFeatureSource > input2( parameterAsSource( parameters, QStringLiteral( "INPUT_2" ), context ) );
124  if ( !input2 )
125  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT_2" ) ) );
126 
127  const bool sameSourceAndTarget = parameters.value( QStringLiteral( "INPUT" ) ) == parameters.value( QStringLiteral( "INPUT_2" ) );
128 
129  QString prefix = parameterAsString( parameters, QStringLiteral( "PREFIX" ), context );
130  const QStringList fieldsToCopy = parameterAsFields( parameters, QStringLiteral( "FIELDS_TO_COPY" ), context );
131 
132  QgsFields outFields2;
133  QgsAttributeList fields2Indices;
134  if ( fieldsToCopy.empty() )
135  {
136  outFields2 = input2->fields();
137  fields2Indices.reserve( outFields2.count() );
138  for ( int i = 0; i < outFields2.count(); ++i )
139  {
140  fields2Indices << i;
141  }
142  }
143  else
144  {
145  fields2Indices.reserve( fieldsToCopy.count() );
146  for ( const QString &field : fieldsToCopy )
147  {
148  int index = input2->fields().lookupField( field );
149  if ( index >= 0 )
150  {
151  fields2Indices << index;
152  outFields2.append( input2->fields().at( index ) );
153  }
154  }
155  }
156 
157  if ( !prefix.isEmpty() )
158  {
159  for ( int i = 0; i < outFields2.count(); ++i )
160  {
161  outFields2[ i ].setName( prefix + outFields2[ i ].name() );
162  }
163  }
164 
165  QgsAttributeList fields2Fetch = fields2Indices;
166 
167  QgsFields outFields = QgsProcessingUtils::combineFields( input->fields(), outFields2 );
168  outFields.append( QgsField( QStringLiteral( "n" ), QVariant::Int ) );
169  outFields.append( QgsField( QStringLiteral( "distance" ), QVariant::Double ) );
170  outFields.append( QgsField( QStringLiteral( "feature_x" ), QVariant::Double ) );
171  outFields.append( QgsField( QStringLiteral( "feature_y" ), QVariant::Double ) );
172  outFields.append( QgsField( QStringLiteral( "nearest_x" ), QVariant::Double ) );
173  outFields.append( QgsField( QStringLiteral( "nearest_y" ), QVariant::Double ) );
174 
175  QString dest;
176  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, outFields,
177  input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
178  if ( parameters.value( QStringLiteral( "OUTPUT" ) ).isValid() && !sink )
179  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
180 
181  QString destNonMatching1;
182  std::unique_ptr< QgsFeatureSink > sinkNonMatching1( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING" ), context, destNonMatching1, input->fields(),
183  input->wkbType(), input->sourceCrs(), QgsFeatureSink::RegeneratePrimaryKey ) );
184  if ( parameters.value( QStringLiteral( "NON_MATCHING" ) ).isValid() && !sinkNonMatching1 )
185  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING" ) ) );
186 
187  // make spatial index
188  QgsFeatureIterator f2 = input2->getFeatures( QgsFeatureRequest().setDestinationCrs( input->sourceCrs(), context.transformContext() ).setSubsetOfAttributes( fields2Fetch ) );
190  QHash< QgsFeatureId, QgsAttributes > input2AttributeCache;
191  QgsFeature f;
192  double step = input2->featureCount() > 0 ? 50.0 / input2->featureCount() : 1;
193  int i = 0;
194  while ( f2.nextFeature( f ) )
195  {
196  i++;
197  if ( feedback->isCanceled() )
198  break;
199 
200  feedback->setProgress( i * step );
201 
202  if ( !f.hasGeometry() )
203  continue;
204 
205  index.addFeature( f );
206  // only keep selected attributes
207  QgsAttributes attributes;
208  for ( int j = 0; j < f.attributes().count(); ++j )
209  {
210  if ( ! fields2Indices.contains( j ) )
211  continue;
212  attributes << f.attribute( j );
213  }
214  input2AttributeCache.insert( f.id(), attributes );
215  }
216 
217  // create extra null attributes for non-matched records (the +2 is for the "n" and "distance", and start/end x/y fields)
218  QgsAttributes nullMatch;
219  for ( int i = 0; i < fields2Indices.count() + 6; ++i )
220  nullMatch << QVariant();
221 
222  long long joinedCount = 0;
223  long long unjoinedCount = 0;
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  if ( !f.hasGeometry() )
240  {
241  unjoinedCount++;
242  if ( sinkNonMatching1 )
243  {
244  sinkNonMatching1->addFeature( f, QgsFeatureSink::FastInsert );
245  }
246  if ( sink && !discardNonMatching )
247  {
248  QgsAttributes attr = f.attributes();
249  attr.append( nullMatch );
250  f.setAttributes( attr );
251  sink->addFeature( f, QgsFeatureSink::FastInsert );
252  }
253  }
254  else
255  {
256  // note - if using same source as target, we have to get one extra neighbor, since the first match will be the input feature
257  const QList< QgsFeatureId > nearest = index.nearestNeighbor( f.geometry(), neighbors + ( sameSourceAndTarget ? 1 : 0 ), std::isnan( maxDistance ) ? 0 : maxDistance );
258  QgsFeature out;
259  out.setGeometry( f.geometry() );
260  int j = 0;
261  for ( QgsFeatureId id : nearest )
262  {
263  if ( sameSourceAndTarget && id == f.id() )
264  continue; // don't match to same feature if using a single input table
265  j++;
266  if ( sink )
267  {
268  QgsAttributes attr = f.attributes();
269  attr.append( input2AttributeCache.value( id ) );
270  attr.append( j );
271 
272  const QgsGeometry closestLine = f.geometry().shortestLine( index.geometry( id ) );
273  if ( const QgsLineString *line = qgsgeometry_cast< const QgsLineString *>( closestLine.constGet() ) )
274  {
275  attr.append( line->length() );
276  attr.append( line->startPoint().x() );
277  attr.append( line->startPoint().y() );
278  attr.append( line->endPoint().x() );
279  attr.append( line->endPoint().y() );
280  }
281  else
282  {
283  attr.append( QVariant() ); //distance
284  attr.append( QVariant() ); //start x
285  attr.append( QVariant() ); //start y
286  attr.append( QVariant() ); //end x
287  attr.append( QVariant() ); //end y
288  }
289  out.setAttributes( attr );
290  sink->addFeature( out, QgsFeatureSink::FastInsert );
291  }
292  }
293  if ( j > 0 )
294  joinedCount++;
295  else
296  {
297  if ( sinkNonMatching1 )
298  {
299  sinkNonMatching1->addFeature( f, QgsFeatureSink::FastInsert );
300  }
301  if ( !discardNonMatching && sink )
302  {
303  QgsAttributes attr = f.attributes();
304  attr.append( nullMatch );
305  f.setAttributes( attr );
306  sink->addFeature( f, QgsFeatureSink::FastInsert );
307  }
308  unjoinedCount++;
309  }
310  }
311  }
312 
313  QVariantMap outputs;
314  outputs.insert( QStringLiteral( "JOINED_COUNT" ), joinedCount );
315  outputs.insert( QStringLiteral( "UNJOINABLE_COUNT" ), unjoinedCount );
316  if ( sink )
317  outputs.insert( QStringLiteral( "OUTPUT" ), dest );
318  if ( sinkNonMatching1 )
319  outputs.insert( QStringLiteral( "NON_MATCHING" ), destNonMatching1 );
320  return outputs;
321 }
322 
323 
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:63
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:111
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 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...
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:48
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:54
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
QgsGeometry geometry
Definition: qgsfeature.h:67
QList< int > QgsAttributeList
Definition: qgsfield.h:27
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...