QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 "qgsmaplayerregistry.h"
21 #include "qgsvectordataprovider.h"
22 
23 #include <QDomElement>
24 
26  : mLayer( layer )
27 {
28 }
29 
31 {
32 }
33 
35 {
37  Q_FOREACH ( const QgsVectorJoinInfo& info, vl->vectorJoins() )
38  {
39  if ( QgsVectorLayer* joinVl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( info.joinLayerId ) ) )
40  lst << joinVl;
41  }
42  return lst;
43 }
44 
46 {
47  if ( mark.value( n ) == 1 ) // temporary
48  return true;
49  if ( mark.value( n ) == 0 ) // not visited
50  {
51  mark[n] = 1; // temporary
52  Q_FOREACH ( QgsVectorLayer* m, _outEdges( n ) )
53  {
54  if ( _hasCycleDFS( m, mark ) )
55  return true;
56  }
57  mark[n] = 2; // permanent
58  }
59  return false;
60 }
61 
62 
64 {
65  QMutexLocker locker( &mMutex );
66  mVectorJoins.push_back( joinInfo );
67 
68  // run depth-first search to detect cycles in the graph of joins between layers.
69  // any cycle would cause infinite recursion when updating fields
71  if ( mLayer && _hasCycleDFS( mLayer, markDFS ) )
72  {
73  // we have to reject this one
74  mVectorJoins.pop_back();
75  return false;
76  }
77 
78  //cache joined layer to virtual memory if specified by user
79  if ( joinInfo.memoryCache )
80  {
81  cacheJoinLayer( mVectorJoins.last() );
82  }
83 
84  // Wait for notifications about changed fields in joined layer to propagate them.
85  // During project load the joined layers possibly do not exist yet so the connection will not be created,
86  // but then QgsProject makes sure to call createJoinCaches() which will do the connection.
87  // Unique connection makes sure we do not respond to one layer's update more times (in case of multiple join)
88  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo.joinLayerId ) ) )
89  {
90  connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
91  connect( vl, SIGNAL( layerModified() ), this, SLOT( joinedLayerModified() ), Qt::UniqueConnection );
92  }
93 
94  locker.unlock();
95 
96  emit joinedFieldsChanged();
97  return true;
98 }
99 
100 
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, SIGNAL( updatedFields() ), this, SLOT( 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 
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.fieldNameIndex( 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 
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, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
277  connect( vl, SIGNAL( layerModified() ), this, SLOT( joinedLayerModified() ), Qt::UniqueConnection );
278  }
279  }
280 }
281 
282 
283 void QgsVectorLayerJoinBuffer::writeXml( QDomNode& layer_node, QDomDocument& document ) const
284 {
285  QDomElement vectorJoinsElem = document.createElement( "vectorjoins" );
286  layer_node.appendChild( vectorJoinsElem );
288  for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
289  {
290  QDomElement joinElem = document.createElement( "join" );
291 
292  if ( joinIt->targetFieldName.isEmpty() )
293  joinElem.setAttribute( "targetField", joinIt->targetFieldIndex ); //for compatibility with 1.x
294  else
295  joinElem.setAttribute( "targetFieldName", joinIt->targetFieldName );
296 
297  joinElem.setAttribute( "joinLayerId", joinIt->joinLayerId );
298  if ( joinIt->joinFieldName.isEmpty() )
299  joinElem.setAttribute( "joinField", joinIt->joinFieldIndex ); //for compatibility with 1.x
300  else
301  joinElem.setAttribute( "joinFieldName", joinIt->joinFieldName );
302 
303  joinElem.setAttribute( "memoryCache", joinIt->memoryCache );
304 
305  if ( joinIt->joinFieldNamesSubset() )
306  {
307  QDomElement subsetElem = document.createElement( "joinFieldsSubset" );
308  Q_FOREACH ( const QString& fieldName, *joinIt->joinFieldNamesSubset() )
309  {
310  QDomElement fieldElem = document.createElement( "field" );
311  fieldElem.setAttribute( "name", fieldName );
312  subsetElem.appendChild( fieldElem );
313  }
314 
315  joinElem.appendChild( subsetElem );
316  }
317 
318  if ( !joinIt->prefix.isNull() )
319  {
320  joinElem.setAttribute( "customPrefix", joinIt->prefix );
321  joinElem.setAttribute( "hasCustomPrefix", 1 );
322  }
323 
324  vectorJoinsElem.appendChild( joinElem );
325  }
326 }
327 
329 {
330  mVectorJoins.clear();
331  QDomElement vectorJoinsElem = layer_node.firstChildElement( "vectorjoins" );
332  if ( !vectorJoinsElem.isNull() )
333  {
334  QDomNodeList joinList = vectorJoinsElem.elementsByTagName( "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( "joinFieldName" );
340  info.joinLayerId = infoElem.attribute( "joinLayerId" );
341  info.targetFieldName = infoElem.attribute( "targetFieldName" );
342  info.memoryCache = infoElem.attribute( "memoryCache" ).toInt();
343  info.cacheDirty = true;
344 
345  info.joinFieldIndex = infoElem.attribute( "joinField" ).toInt(); //for compatibility with 1.x
346  info.targetFieldIndex = infoElem.attribute( "targetField" ).toInt(); //for compatibility with 1.x
347 
348  QDomElement subsetElem = infoElem.firstChildElement( "joinFieldsSubset" );
349  if ( !subsetElem.isNull() )
350  {
351  QStringList* fieldNames = new QStringList;
352  QDomNodeList fieldNodes = infoElem.elementsByTagName( "field" );
353  for ( int i = 0; i < fieldNodes.count(); ++i )
354  *fieldNames << fieldNodes.at( i ).toElement().attribute( "name" );
355  info.setJoinFieldNamesSubset( fieldNames );
356  }
357 
358  if ( infoElem.attribute( "hasCustomPrefix" ).toInt() )
359  info.prefix = infoElem.attribute( "customPrefix" );
360  else
361  info.prefix = QString::null;
362 
363  addJoin( info );
364  }
365  }
366 }
367 
369 {
370  if ( !info )
371  return -1;
372 
373  int joinIndex = mVectorJoins.indexOf( *info );
374  if ( joinIndex == -1 )
375  return -1;
376 
377  for ( int i = 0; i < fields.count(); ++i )
378  {
379  if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
380  continue;
381 
382  if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
383  return i;
384  }
385  return -1;
386 }
387 
388 const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const
389 {
390  if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
391  return nullptr;
392 
393  int originIndex = fields.fieldOriginIndex( index );
394  int sourceJoinIndex = originIndex / 1000;
395  sourceFieldIndex = originIndex % 1000;
396 
397  if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
398  return nullptr;
399 
400  return &( mVectorJoins[sourceJoinIndex] );
401 }
402 
404 {
405  QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer( mLayer );
406  cloned->mVectorJoins = mVectorJoins;
407  return cloned;
408 }
409 
410 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
411 {
412  // TODO - check - this whole method is probably not needed anymore,
413  // since the cache handling is covered by joinedLayerModified()
414 
415  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
416  Q_ASSERT( joinedLayer );
417 
418  // recache the joined layer
419  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
420  {
421  if ( joinedLayer->id() == it->joinLayerId )
422  {
423  it->cachedAttributes.clear();
424  cacheJoinLayer( *it );
425  }
426  }
427 
428  emit joinedFieldsChanged();
429 }
430 
431 void QgsVectorLayerJoinBuffer::joinedLayerModified()
432 {
433  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
434  Q_ASSERT( joinedLayer );
435 
436  // recache the joined layer
437  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
438  {
439  if ( joinedLayer->id() == it->joinLayerId )
440  {
441  it->cacheDirty = true;
442  }
443  }
444 }
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.
void clear()
Wrapper for iterator of features from vector data provider or vector layer.
QDomNodeList elementsByTagName(const QString &tagname) const
static unsigned index
iterator insert(const Key &key, const T &value)
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: qgsfield.h:260
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
FieldOrigin fieldOrigin(int fieldIdx) const
Get field&#39;s origin (value from an enumeration)
Definition: qgsfield.cpp:448
QString targetFieldName
Join field in the target layer.
void createJoinCaches()
Calls cacheJoinLayer() for all vector joins.
QString name
Definition: qgsfield.h:52
QDomNode appendChild(const QDomNode &newChild)
void append(const T &value)
void push_back(const T &value)
QString attribute(const QString &name, const QString &defValue) const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QObject * sender() const
QgsMapLayer * mapLayer(const QString &theLayerId) const
Retrieve a pointer to a registered layer by layer ID.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
int joinFieldIndex
Join field index in the source layer.
const QgsVectorJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
const T & at(int i) const
void removeAt(int i)
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: qgsfield.h:252
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:133
bool memoryCache
True if the join is cached in virtual memory.
int targetFieldIndex
Join field index in the target layer.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
QgsVectorLayerJoinBuffer(QgsVectorLayer *layer=nullptr)
const QList< QgsVectorJoinInfo > vectorJoins() const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
int count() const
Return number of items.
Definition: qgsfield.cpp:402
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:422
int size() const
Manages joined fields for a vector layer.
QgsFields fields() const
Returns the list of fields of this layer.
int joinedFieldsOffset(const QgsVectorJoinInfo *info, const QgsFields &fields)
Find out what is the first index of the join within fields.
int indexOf(const T &value, int from) const
int fieldOriginIndex(int fieldIdx) const
Get field&#39;s origin index (its meaning is specific to each type of origin)
Definition: qgsfield.cpp:456
QDomElement toElement() const
QString prefix
An optional prefix.
void setJoinFieldNamesSubset(QStringList *fieldNamesSubset)
Set subset of fields to be used from joined layer.
int count() const
int count(const T &value) const
bool addJoin(const QgsVectorJoinInfo &joinInfo)
Joins another vector layer to this layer.
void append(const T &value)
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
void setAttribute(const QString &name, const QString &value)
int toInt(bool *ok, int base) const
bool isEmpty() const
bool isEmpty() const
void remove(int i)
This class wraps a request for features to a vector layer (or directly its vector data provider)...
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: qgsfield.cpp:346
QgsFeatureRequest & setFlags(const QgsFeatureRequest::Flags &flags)
Set flags that affect how features will be fetched.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:44
void pop_back()
void clear()
iterator end()
const T value(const Key &key) const
int fieldNameIndex(const QString &fieldName) const
Look up field&#39;s index from name also looks up case-insensitive if there is no match otherwise...
Definition: qgsfield.cpp:571
bool contains(const T &value) const
bool isNull() const
const T & at(int i) const
int indexFromName(const QString &name) const
Look up field&#39;s index from name. Returns -1 on error.
Definition: qgsfield.cpp:461
QList< T > toList() const
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.
QDomElement firstChildElement(const QString &tagName) const
T & last()
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.
int count(const T &value) const
QSet< T > fromList(const QList< T > &list)
void joinedFieldsChanged()
Emitted whenever the list of joined fields changes (e.g.
QString name
Read property of QString layerName.
Definition: qgsmaplayer.h:53
const QgsField & field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:427
int size() const
const_iterator constEnd() const
QDomElement createElement(const QString &tagName)
bool nextFeature(QgsFeature &f)
const_iterator constBegin() const
Geometry is not required. It may still be returned if e.g. required for a filter condition.
A vector of attributes.
Definition: qgsfeature.h:115
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
Represents a vector layer which manages a vector based data sets.
QString joinLayerId
Source layer.
QStringList * joinFieldNamesSubset() const
Get subset of fields to be used from joined layer.
iterator begin()
QgsVectorLayerJoinBuffer * clone() const
Create a copy of the join buffer.
QDomNode at(int index) const