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