QGIS API Documentation  2.99.0-Master (7fe5405)
qgsvectorlayerjoinbuffer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvectorlayerjoinbuffer.cpp
3  ----------------------------
4  begin : Feb 09, 2011
5  copyright : (C) 2011 by Marco Hugentobler
6  email : marco dot hugentobler at sourcepole dot ch
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 
20 #include "qgsfeatureiterator.h"
21 #include "qgslogger.h"
22 #include "qgsproject.h"
23 #include "qgsvectordataprovider.h"
24 
25 #include <QDomElement>
26 
28  : mLayer( layer )
29 {
30 }
31 
32 static QList<QgsVectorLayer*> _outEdges( QgsVectorLayer* vl )
33 {
34  QList<QgsVectorLayer*> lst;
35  Q_FOREACH ( const QgsVectorJoinInfo& info, vl->vectorJoins() )
36  {
37  if ( QgsVectorLayer* joinVl = qobject_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( info.joinLayerId ) ) )
38  lst << joinVl;
39  }
40  return lst;
41 }
42 
43 static bool _hasCycleDFS( QgsVectorLayer* n, QHash<QgsVectorLayer*, int>& mark )
44 {
45  if ( mark.value( n ) == 1 ) // temporary
46  return true;
47  if ( mark.value( n ) == 0 ) // not visited
48  {
49  mark[n] = 1; // temporary
50  Q_FOREACH ( QgsVectorLayer* m, _outEdges( n ) )
51  {
52  if ( _hasCycleDFS( m, mark ) )
53  return true;
54  }
55  mark[n] = 2; // permanent
56  }
57  return false;
58 }
59 
60 
62 {
63  QMutexLocker locker( &mMutex );
64  mVectorJoins.push_back( joinInfo );
65 
66  // run depth-first search to detect cycles in the graph of joins between layers.
67  // any cycle would cause infinite recursion when updating fields
68  QHash<QgsVectorLayer*, int> markDFS;
69  if ( mLayer && _hasCycleDFS( mLayer, markDFS ) )
70  {
71  // we have to reject this one
72  mVectorJoins.pop_back();
73  return false;
74  }
75 
76  //cache joined layer to virtual memory if specified by user
77  if ( joinInfo.memoryCache )
78  {
79  cacheJoinLayer( mVectorJoins.last() );
80  }
81 
82  // Wait for notifications about changed fields in joined layer to propagate them.
83  // During project load the joined layers possibly do not exist yet so the connection will not be created,
84  // but then QgsProject makes sure to call createJoinCaches() which will do the connection.
85  // Unique connection makes sure we do not respond to one layer's update more times (in case of multiple join)
86  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( joinInfo.joinLayerId ) ) )
87  {
88  connect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields, Qt::UniqueConnection );
89  connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection );
90  }
91 
92  emit joinedFieldsChanged();
93  return true;
94 }
95 
96 
97 bool QgsVectorLayerJoinBuffer::removeJoin( const QString& joinLayerId )
98 {
99  QMutexLocker locker( &mMutex );
100  bool res = false;
101  for ( int i = 0; i < mVectorJoins.size(); ++i )
102  {
103  if ( mVectorJoins.at( i ).joinLayerId == joinLayerId )
104  {
105  mVectorJoins.removeAt( i );
106  res = true;
107  }
108  }
109 
110  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( joinLayerId ) ) )
111  {
112  disconnect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields );
113  }
114 
115  emit joinedFieldsChanged();
116  return res;
117 }
118 
119 void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorJoinInfo& joinInfo )
120 {
121  //memory cache not required or already done
122  if ( !joinInfo.memoryCache || !joinInfo.cacheDirty )
123  {
124  return;
125  }
126 
127  QgsVectorLayer* cacheLayer = dynamic_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( joinInfo.joinLayerId ) );
128  if ( cacheLayer )
129  {
130  int joinFieldIndex;
131  if ( joinInfo.joinFieldName.isEmpty() )
132  joinFieldIndex = joinInfo.joinFieldIndex; //for compatibility with 1.x
133  else
134  joinFieldIndex = cacheLayer->fields().indexFromName( joinInfo.joinFieldName );
135 
136  if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().count() )
137  return;
138 
139  joinInfo.cachedAttributes.clear();
140 
141  QgsFeatureRequest request;
143 
144  // maybe user requested just a subset of layer's attributes
145  // so we do not have to cache everything
146  bool hasSubset = joinInfo.joinFieldNamesSubset();
147  QVector<int> subsetIndices;
148  if ( hasSubset )
149  {
150  subsetIndices = joinSubsetIndices( cacheLayer, *joinInfo.joinFieldNamesSubset() );
151 
152  // we need just subset of attributes - but make sure to include join field name
153  QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
154  if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
155  cacheLayerAttrs.append( joinFieldIndex );
156  request.setSubsetOfAttributes( cacheLayerAttrs );
157  }
158 
159  QgsFeatureIterator fit = cacheLayer->getFeatures( request );
160  QgsFeature f;
161  while ( fit.nextFeature( f ) )
162  {
163  QgsAttributes attrs = f.attributes();
164  QString key = attrs.at( joinFieldIndex ).toString();
165  if ( hasSubset )
166  {
167  QgsAttributes subsetAttrs( subsetIndices.count() );
168  for ( int i = 0; i < subsetIndices.count(); ++i )
169  subsetAttrs[i] = attrs.at( subsetIndices.at( i ) );
170  joinInfo.cachedAttributes.insert( key, subsetAttrs );
171  }
172  else
173  {
174  QgsAttributes attrs2 = attrs;
175  attrs2.remove( joinFieldIndex ); // skip the join field to avoid double field names (fields often have the same name)
176  joinInfo.cachedAttributes.insert( key, attrs2 );
177  }
178  }
179  joinInfo.cacheDirty = false;
180  }
181 }
182 
183 
184 QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( QgsVectorLayer* joinLayer, const QStringList& joinFieldsSubset )
185 {
186  QVector<int> subsetIndices;
187  const QgsFields& fields = joinLayer->fields();
188  for ( int i = 0; i < joinFieldsSubset.count(); ++i )
189  {
190  QString joinedFieldName = joinFieldsSubset.at( i );
191  int index = fields.lookupField( joinedFieldName );
192  if ( index != -1 )
193  {
194  subsetIndices.append( index );
195  }
196  else
197  {
198  QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
199  }
200  }
201 
202  return subsetIndices;
203 }
204 
206 {
207  QString prefix;
208 
209  QList< QgsVectorJoinInfo>::const_iterator joinIt = mVectorJoins.constBegin();
210  for ( int joinIdx = 0 ; joinIt != mVectorJoins.constEnd(); ++joinIt, ++joinIdx )
211  {
212  QgsVectorLayer* joinLayer = qobject_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( joinIt->joinLayerId ) );
213  if ( !joinLayer )
214  {
215  continue;
216  }
217 
218  const QgsFields& joinFields = joinLayer->fields();
219  QString joinFieldName;
220  if ( joinIt->joinFieldName.isEmpty() && joinIt->joinFieldIndex >= 0 && joinIt->joinFieldIndex < joinFields.count() )
221  joinFieldName = joinFields.field( joinIt->joinFieldIndex ).name(); //for compatibility with 1.x
222  else
223  joinFieldName = joinIt->joinFieldName;
224 
225  QSet<QString> subset;
226  bool hasSubset = false;
227  if ( joinIt->joinFieldNamesSubset() )
228  {
229  hasSubset = true;
230  subset = QSet<QString>::fromList( *joinIt->joinFieldNamesSubset() );
231  }
232 
233  if ( joinIt->prefix.isNull() )
234  {
235  prefix = joinLayer->name() + '_';
236  }
237  else
238  {
239  prefix = joinIt->prefix;
240  }
241 
242  for ( int idx = 0; idx < joinFields.count(); ++idx )
243  {
244  // if using just a subset of fields, filter some of them out
245  if ( hasSubset && !subset.contains( joinFields.at( idx ).name() ) )
246  continue;
247 
248  //skip the join field to avoid double field names (fields often have the same name)
249  // when using subset of field, use all the selected fields
250  if ( hasSubset || joinFields.at( idx ).name() != joinFieldName )
251  {
252  QgsField f = joinFields.at( idx );
253  f.setName( prefix + f.name() );
254  fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx*1000 ) );
255  }
256  }
257  }
258 }
259 
261 {
262  QMutexLocker locker( &mMutex );
263  QList< QgsVectorJoinInfo >::iterator joinIt = mVectorJoins.begin();
264  for ( ; joinIt != mVectorJoins.end(); ++joinIt )
265  {
266  if ( joinIt->memoryCache && joinIt->cacheDirty )
267  cacheJoinLayer( *joinIt );
268 
269  // make sure we are connected to the joined layer
270  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsProject::instance()->mapLayer( joinIt->joinLayerId ) ) )
271  {
272  connect( vl, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields, Qt::UniqueConnection );
273  connect( vl, &QgsVectorLayer::layerModified, this, &QgsVectorLayerJoinBuffer::joinedLayerModified, Qt::UniqueConnection );
274  }
275  }
276 }
277 
278 
279 void QgsVectorLayerJoinBuffer::writeXml( QDomNode& layer_node, QDomDocument& document ) const
280 {
281  QDomElement vectorJoinsElem = document.createElement( QStringLiteral( "vectorjoins" ) );
282  layer_node.appendChild( vectorJoinsElem );
283  QList< QgsVectorJoinInfo >::const_iterator joinIt = mVectorJoins.constBegin();
284  for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
285  {
286  QDomElement joinElem = document.createElement( QStringLiteral( "join" ) );
287 
288  if ( joinIt->targetFieldName.isEmpty() )
289  joinElem.setAttribute( QStringLiteral( "targetField" ), joinIt->targetFieldIndex ); //for compatibility with 1.x
290  else
291  joinElem.setAttribute( QStringLiteral( "targetFieldName" ), joinIt->targetFieldName );
292 
293  joinElem.setAttribute( QStringLiteral( "joinLayerId" ), joinIt->joinLayerId );
294  if ( joinIt->joinFieldName.isEmpty() )
295  joinElem.setAttribute( QStringLiteral( "joinField" ), joinIt->joinFieldIndex ); //for compatibility with 1.x
296  else
297  joinElem.setAttribute( QStringLiteral( "joinFieldName" ), joinIt->joinFieldName );
298 
299  joinElem.setAttribute( QStringLiteral( "memoryCache" ), joinIt->memoryCache );
300 
301  if ( joinIt->joinFieldNamesSubset() )
302  {
303  QDomElement subsetElem = document.createElement( QStringLiteral( "joinFieldsSubset" ) );
304  Q_FOREACH ( const QString& fieldName, *joinIt->joinFieldNamesSubset() )
305  {
306  QDomElement fieldElem = document.createElement( QStringLiteral( "field" ) );
307  fieldElem.setAttribute( QStringLiteral( "name" ), fieldName );
308  subsetElem.appendChild( fieldElem );
309  }
310 
311  joinElem.appendChild( subsetElem );
312  }
313 
314  if ( !joinIt->prefix.isNull() )
315  {
316  joinElem.setAttribute( QStringLiteral( "customPrefix" ), joinIt->prefix );
317  joinElem.setAttribute( QStringLiteral( "hasCustomPrefix" ), 1 );
318  }
319 
320  vectorJoinsElem.appendChild( joinElem );
321  }
322 }
323 
324 void QgsVectorLayerJoinBuffer::readXml( const QDomNode& layer_node )
325 {
326  mVectorJoins.clear();
327  QDomElement vectorJoinsElem = layer_node.firstChildElement( QStringLiteral( "vectorjoins" ) );
328  if ( !vectorJoinsElem.isNull() )
329  {
330  QDomNodeList joinList = vectorJoinsElem.elementsByTagName( QStringLiteral( "join" ) );
331  for ( int i = 0; i < joinList.size(); ++i )
332  {
333  QDomElement infoElem = joinList.at( i ).toElement();
334  QgsVectorJoinInfo info;
335  info.joinFieldName = infoElem.attribute( QStringLiteral( "joinFieldName" ) );
336  info.joinLayerId = infoElem.attribute( QStringLiteral( "joinLayerId" ) );
337  info.targetFieldName = infoElem.attribute( QStringLiteral( "targetFieldName" ) );
338  info.memoryCache = infoElem.attribute( QStringLiteral( "memoryCache" ) ).toInt();
339  info.cacheDirty = true;
340 
341  info.joinFieldIndex = infoElem.attribute( QStringLiteral( "joinField" ) ).toInt(); //for compatibility with 1.x
342  info.targetFieldIndex = infoElem.attribute( QStringLiteral( "targetField" ) ).toInt(); //for compatibility with 1.x
343 
344  QDomElement subsetElem = infoElem.firstChildElement( QStringLiteral( "joinFieldsSubset" ) );
345  if ( !subsetElem.isNull() )
346  {
347  QStringList* fieldNames = new QStringList;
348  QDomNodeList fieldNodes = infoElem.elementsByTagName( QStringLiteral( "field" ) );
349  fieldNames->reserve( fieldNodes.count() );
350  for ( int i = 0; i < fieldNodes.count(); ++i )
351  *fieldNames << fieldNodes.at( i ).toElement().attribute( QStringLiteral( "name" ) );
352  info.setJoinFieldNamesSubset( fieldNames );
353  }
354 
355  if ( infoElem.attribute( QStringLiteral( "hasCustomPrefix" ) ).toInt() )
356  info.prefix = infoElem.attribute( QStringLiteral( "customPrefix" ) );
357  else
358  info.prefix = QString::null;
359 
360  addJoin( info );
361  }
362  }
363 }
364 
366 {
367  if ( !info )
368  return -1;
369 
370  int joinIndex = mVectorJoins.indexOf( *info );
371  if ( joinIndex == -1 )
372  return -1;
373 
374  for ( int i = 0; i < fields.count(); ++i )
375  {
376  if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
377  continue;
378 
379  if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
380  return i;
381  }
382  return -1;
383 }
384 
385 const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const
386 {
387  if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
388  return nullptr;
389 
390  int originIndex = fields.fieldOriginIndex( index );
391  int sourceJoinIndex = originIndex / 1000;
392  sourceFieldIndex = originIndex % 1000;
393 
394  if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
395  return nullptr;
396 
397  return &( mVectorJoins[sourceJoinIndex] );
398 }
399 
401 {
402  QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer( mLayer );
403  cloned->mVectorJoins = mVectorJoins;
404  return cloned;
405 }
406 
407 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
408 {
409  // TODO - check - this whole method is probably not needed anymore,
410  // since the cache handling is covered by joinedLayerModified()
411 
412  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
413  Q_ASSERT( joinedLayer );
414 
415  // recache the joined layer
416  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
417  {
418  if ( joinedLayer->id() == it->joinLayerId )
419  {
420  it->cachedAttributes.clear();
421  cacheJoinLayer( *it );
422  }
423  }
424 
425  emit joinedFieldsChanged();
426 }
427 
428 void QgsVectorLayerJoinBuffer::joinedLayerModified()
429 {
430  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
431  Q_ASSERT( joinedLayer );
432 
433  // recache the joined layer
434  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
435  {
436  if ( joinedLayer->id() == it->joinLayerId )
437  {
438  it->cacheDirty = true;
439  }
440  }
441 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:289
void writeXml(QDomNode &layer_node, QDomDocument &document) const
Saves mVectorJoins to xml under the layer node.
bool cacheDirty
True if the cached join attributes need to be updated.
Wrapper for iterator of features from vector data provider or vector layer.
static unsigned index
QString joinFieldName
Join field in the source layer.
Field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfields.h:47
FieldOrigin fieldOrigin(int fieldIdx) const
Get field&#39;s origin (value from an enumeration)
Definition: qgsfields.cpp:161
QString targetFieldName
Join field in the target layer.
void createJoinCaches()
Calls cacheJoinLayer() for all vector joins.
QString name
Definition: qgsfield.h:53
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
int joinFieldIndex
Join field index in the source layer. For backward compatibility with 1.x (x>=7)
const QgsVectorJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
void readXml(const QDomNode &layer_node)
Reads joins from project file.
Container of fields for a vector layer.
Definition: qgsfields.h:39
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:135
bool memoryCache
True if the join is cached in virtual memory.
int targetFieldIndex
Join field index in the target layer. For backward compatibility with 1.x (x>=7)
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:136
QgsVectorLayerJoinBuffer(QgsVectorLayer *layer=nullptr)
const QList< QgsVectorJoinInfo > vectorJoins() const
int count() const
Return number of items.
Definition: qgsfields.cpp:115
Manages joined fields for a vector layer.
QgsFields fields() const
Returns the list of fields of this layer.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:135
int joinedFieldsOffset(const QgsVectorJoinInfo *info, const QgsFields &fields)
Find out what is the first index of the join within fields.
int fieldOriginIndex(int fieldIdx) const
Get field&#39;s origin index (its meaning is specific to each type of origin)
Definition: qgsfields.cpp:169
QString prefix
An optional prefix.
void setJoinFieldNamesSubset(QStringList *fieldNamesSubset)
Set subset of fields to be used from joined layer.
bool addJoin(const QgsVectorJoinInfo &joinInfo)
Joins another vector layer to this layer.
int indexFromName(const QString &fieldName) const
Get the field index from the field name.
Definition: qgsfields.cpp:174
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const
Query the layer for features specified in request.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
bool removeJoin(const QString &joinLayerId)
Removes a vector layer join.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Append 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:45
QHash< QString, QgsAttributes > cachedAttributes
Cache for joined attributes to provide fast lookup (size is 0 if no memory caching) ...
static bool _hasCycleDFS(QgsVectorLayer *n, QHash< QgsVectorLayer *, int > &mark)
void updateFields(QgsFields &fields)
Updates field map with joined attributes.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:350
static QList< QgsVectorLayer * > _outEdges(QgsVectorLayer *vl)
static QVector< int > joinSubsetIndices(QgsVectorLayer *joinLayer, const QStringList &joinFieldsSubset)
Return a vector of indices for use in join based on field names from the layer.
void joinedFieldsChanged()
Emitted whenever the list of joined fields changes (e.g.
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:54
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
A vector of attributes.
Definition: qgsfeature.h:56
Represents a vector layer which manages a vector based data sets.
void updatedFields()
Is emitted, whenever the fields available from this layer have been changed.
void layerModified()
This signal is emitted when modifications has been done on layer.
QString joinLayerId
Source layer.
QStringList * joinFieldNamesSubset() const
Get subset of fields to be used from joined layer.
QgsField field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:140
QgsAttributes attributes
Definition: qgsfeature.h:141
QgsVectorLayerJoinBuffer * clone() const
Create a copy of the join buffer.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Set flags that affect how features will be fetched.