QGIS API Documentation  2.15.0-Master (02a0ebe)
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  emit joinedFieldsChanged();
95  return true;
96 }
97 
98 
100 {
101  QMutexLocker locker( &mMutex );
102  bool res = false;
103  for ( int i = 0; i < mVectorJoins.size(); ++i )
104  {
105  if ( mVectorJoins.at( i ).joinLayerId == joinLayerId )
106  {
107  mVectorJoins.removeAt( i );
108  res = true;
109  }
110  }
111 
112  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinLayerId ) ) )
113  {
114  disconnect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ) );
115  }
116 
117  emit joinedFieldsChanged();
118  return res;
119 }
120 
121 void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorJoinInfo& joinInfo )
122 {
123  //memory cache not required or already done
124  if ( !joinInfo.memoryCache || !joinInfo.cacheDirty )
125  {
126  return;
127  }
128 
129  QgsVectorLayer* cacheLayer = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo.joinLayerId ) );
130  if ( cacheLayer )
131  {
132  int joinFieldIndex;
133  if ( joinInfo.joinFieldName.isEmpty() )
134  joinFieldIndex = joinInfo.joinFieldIndex; //for compatibility with 1.x
135  else
136  joinFieldIndex = cacheLayer->fields().indexFromName( joinInfo.joinFieldName );
137 
138  if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().count() )
139  return;
140 
141  joinInfo.cachedAttributes.clear();
142 
143  QgsFeatureRequest request;
145 
146  // maybe user requested just a subset of layer's attributes
147  // so we do not have to cache everything
148  bool hasSubset = joinInfo.joinFieldNamesSubset();
149  QVector<int> subsetIndices;
150  if ( hasSubset )
151  {
152  subsetIndices = joinSubsetIndices( cacheLayer, *joinInfo.joinFieldNamesSubset() );
153 
154  // we need just subset of attributes - but make sure to include join field name
155  QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
156  if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
157  cacheLayerAttrs.append( joinFieldIndex );
158  request.setSubsetOfAttributes( cacheLayerAttrs );
159  }
160 
161  QgsFeatureIterator fit = cacheLayer->getFeatures( request );
162  QgsFeature f;
163  while ( fit.nextFeature( f ) )
164  {
165  QgsAttributes attrs = f.attributes();
166  QString key = attrs.at( joinFieldIndex ).toString();
167  if ( hasSubset )
168  {
169  QgsAttributes subsetAttrs( subsetIndices.count() );
170  for ( int i = 0; i < subsetIndices.count(); ++i )
171  subsetAttrs[i] = attrs.at( subsetIndices.at( i ) );
172  joinInfo.cachedAttributes.insert( key, subsetAttrs );
173  }
174  else
175  {
176  QgsAttributes attrs2 = attrs;
177  attrs2.remove( joinFieldIndex ); // skip the join field to avoid double field names (fields often have the same name)
178  joinInfo.cachedAttributes.insert( key, attrs2 );
179  }
180  }
181  joinInfo.cacheDirty = false;
182  }
183 }
184 
185 
187 {
188  QVector<int> subsetIndices;
189  const QgsFields& fields = joinLayer->fields();
190  for ( int i = 0; i < joinFieldsSubset.count(); ++i )
191  {
192  QString joinedFieldName = joinFieldsSubset.at( i );
193  int index = fields.fieldNameIndex( joinedFieldName );
194  if ( index != -1 )
195  {
196  subsetIndices.append( index );
197  }
198  else
199  {
200  QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
201  }
202  }
203 
204  return subsetIndices;
205 }
206 
208 {
209  QString prefix;
210 
212  for ( int joinIdx = 0 ; joinIt != mVectorJoins.constEnd(); ++joinIt, ++joinIdx )
213  {
214  QgsVectorLayer* joinLayer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) );
215  if ( !joinLayer )
216  {
217  continue;
218  }
219 
220  const QgsFields& joinFields = joinLayer->fields();
221  QString joinFieldName;
222  if ( joinIt->joinFieldName.isEmpty() && joinIt->joinFieldIndex >= 0 && joinIt->joinFieldIndex < joinFields.count() )
223  joinFieldName = joinFields.field( joinIt->joinFieldIndex ).name(); //for compatibility with 1.x
224  else
225  joinFieldName = joinIt->joinFieldName;
226 
227  QSet<QString> subset;
228  bool hasSubset = false;
229  if ( joinIt->joinFieldNamesSubset() )
230  {
231  hasSubset = true;
232  subset = QSet<QString>::fromList( *joinIt->joinFieldNamesSubset() );
233  }
234 
235  if ( joinIt->prefix.isNull() )
236  {
237  prefix = joinLayer->name() + '_';
238  }
239  else
240  {
241  prefix = joinIt->prefix;
242  }
243 
244  for ( int idx = 0; idx < joinFields.count(); ++idx )
245  {
246  // if using just a subset of fields, filter some of them out
247  if ( hasSubset && !subset.contains( joinFields.at( idx ).name() ) )
248  continue;
249 
250  //skip the join field to avoid double field names (fields often have the same name)
251  // when using subset of field, use all the selected fields
252  if ( hasSubset || joinFields.at( idx ).name() != joinFieldName )
253  {
254  QgsField f = joinFields.at( idx );
255  f.setName( prefix + f.name() );
256  fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx*1000 ) );
257  }
258  }
259  }
260 }
261 
263 {
264  QMutexLocker locker( &mMutex );
265  QList< QgsVectorJoinInfo >::iterator joinIt = mVectorJoins.begin();
266  for ( ; joinIt != mVectorJoins.end(); ++joinIt )
267  {
268  if ( joinIt->memoryCache && joinIt->cacheDirty )
269  cacheJoinLayer( *joinIt );
270 
271  // make sure we are connected to the joined layer
272  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) ) )
273  {
274  connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
275  connect( vl, SIGNAL( layerModified() ), this, SLOT( joinedLayerModified() ), Qt::UniqueConnection );
276  }
277  }
278 }
279 
280 
281 void QgsVectorLayerJoinBuffer::writeXml( QDomNode& layer_node, QDomDocument& document ) const
282 {
283  QDomElement vectorJoinsElem = document.createElement( "vectorjoins" );
284  layer_node.appendChild( vectorJoinsElem );
286  for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
287  {
288  QDomElement joinElem = document.createElement( "join" );
289 
290  if ( joinIt->targetFieldName.isEmpty() )
291  joinElem.setAttribute( "targetField", joinIt->targetFieldIndex ); //for compatibility with 1.x
292  else
293  joinElem.setAttribute( "targetFieldName", joinIt->targetFieldName );
294 
295  joinElem.setAttribute( "joinLayerId", joinIt->joinLayerId );
296  if ( joinIt->joinFieldName.isEmpty() )
297  joinElem.setAttribute( "joinField", joinIt->joinFieldIndex ); //for compatibility with 1.x
298  else
299  joinElem.setAttribute( "joinFieldName", joinIt->joinFieldName );
300 
301  joinElem.setAttribute( "memoryCache", joinIt->memoryCache );
302 
303  if ( joinIt->joinFieldNamesSubset() )
304  {
305  QDomElement subsetElem = document.createElement( "joinFieldsSubset" );
306  Q_FOREACH ( const QString& fieldName, *joinIt->joinFieldNamesSubset() )
307  {
308  QDomElement fieldElem = document.createElement( "field" );
309  fieldElem.setAttribute( "name", fieldName );
310  subsetElem.appendChild( fieldElem );
311  }
312 
313  joinElem.appendChild( subsetElem );
314  }
315 
316  if ( !joinIt->prefix.isNull() )
317  {
318  joinElem.setAttribute( "customPrefix", joinIt->prefix );
319  joinElem.setAttribute( "hasCustomPrefix", 1 );
320  }
321 
322  vectorJoinsElem.appendChild( joinElem );
323  }
324 }
325 
327 {
328  mVectorJoins.clear();
329  QDomElement vectorJoinsElem = layer_node.firstChildElement( "vectorjoins" );
330  if ( !vectorJoinsElem.isNull() )
331  {
332  QDomNodeList joinList = vectorJoinsElem.elementsByTagName( "join" );
333  for ( int i = 0; i < joinList.size(); ++i )
334  {
335  QDomElement infoElem = joinList.at( i ).toElement();
336  QgsVectorJoinInfo info;
337  info.joinFieldName = infoElem.attribute( "joinFieldName" );
338  info.joinLayerId = infoElem.attribute( "joinLayerId" );
339  info.targetFieldName = infoElem.attribute( "targetFieldName" );
340  info.memoryCache = infoElem.attribute( "memoryCache" ).toInt();
341  info.cacheDirty = true;
342 
343  info.joinFieldIndex = infoElem.attribute( "joinField" ).toInt(); //for compatibility with 1.x
344  info.targetFieldIndex = infoElem.attribute( "targetField" ).toInt(); //for compatibility with 1.x
345 
346  QDomElement subsetElem = infoElem.firstChildElement( "joinFieldsSubset" );
347  if ( !subsetElem.isNull() )
348  {
349  QStringList* fieldNames = new QStringList;
350  QDomNodeList fieldNodes = infoElem.elementsByTagName( "field" );
351  for ( int i = 0; i < fieldNodes.count(); ++i )
352  *fieldNames << fieldNodes.at( i ).toElement().attribute( "name" );
353  info.setJoinFieldNamesSubset( fieldNames );
354  }
355 
356  if ( infoElem.attribute( "hasCustomPrefix" ).toInt() )
357  info.prefix = infoElem.attribute( "customPrefix" );
358  else
359  info.prefix = QString::null;
360 
361  addJoin( info );
362  }
363  }
364 }
365 
367 {
368  if ( !info )
369  return -1;
370 
371  int joinIndex = mVectorJoins.indexOf( *info );
372  if ( joinIndex == -1 )
373  return -1;
374 
375  for ( int i = 0; i < fields.count(); ++i )
376  {
377  if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
378  continue;
379 
380  if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
381  return i;
382  }
383  return -1;
384 }
385 
386 const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const
387 {
388  if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
389  return nullptr;
390 
391  int originIndex = fields.fieldOriginIndex( index );
392  int sourceJoinIndex = originIndex / 1000;
393  sourceFieldIndex = originIndex % 1000;
394 
395  if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
396  return nullptr;
397 
398  return &( mVectorJoins[sourceJoinIndex] );
399 }
400 
402 {
403  QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer( mLayer );
404  cloned->mVectorJoins = mVectorJoins;
405  return cloned;
406 }
407 
408 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
409 {
410  // TODO - check - this whole method is probably not needed anymore,
411  // since the cache handling is covered by joinedLayerModified()
412 
413  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
414  Q_ASSERT( joinedLayer );
415 
416  // recache the joined layer
417  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
418  {
419  if ( joinedLayer->id() == it->joinLayerId )
420  {
421  it->cachedAttributes.clear();
422  cacheJoinLayer( *it );
423  }
424  }
425 
426  emit joinedFieldsChanged();
427 }
428 
429 void QgsVectorLayerJoinBuffer::joinedLayerModified()
430 {
431  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
432  Q_ASSERT( joinedLayer );
433 
434  // recache the joined layer
435  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
436  {
437  if ( joinedLayer->id() == it->joinLayerId )
438  {
439  it->cacheDirty = true;
440  }
441  }
442 }
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
const QgsField & field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:390
const QList< QgsVectorJoinInfo > vectorJoins() const
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:201
QString targetFieldName
Join field in the target layer.
void createJoinCaches()
Calls cacheJoinLayer() for all vector joins.
QString name() const
Get the display name of the layer.
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
QgsFields fields() const
Returns the list of fields of this layer.
QObject * sender() 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:534
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
int joinFieldIndex
Join field index in the source layer.
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:193
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:120
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)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
int size() const
QgsMapLayer * mapLayer(const QString &theLayerId)
Retrieve a pointer to a loaded layer by id.
Manages joined fields for a vector layer.
const QgsVectorJoinInfo * joinForFieldIndex(int index, const QgsFields &fields, int &sourceFieldIndex) const
Finds the vector join for a layer field index.
int joinedFieldsOffset(const QgsVectorJoinInfo *info, const QgsFields &fields)
Find out what is the first index of the join within fields.
QgsVectorLayerJoinBuffer * clone() const
Create a copy of the join buffer.
int indexOf(const T &value, int from) const
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)
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
void setAttribute(const QString &name, const QString &value)
QString name() const
Gets the name of the field.
Definition: qgsfield.cpp:84
int toInt(bool *ok, int base) const
bool isEmpty() const
bool isEmpty() const
void remove(int i)
int fieldOriginIndex(int fieldIdx) const
Get field&#39;s origin index (its meaning is specific to each type of origin)
Definition: qgsfield.cpp:419
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:309
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
int count() const
Return number of items.
Definition: qgsfield.cpp:365
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
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.cpp:385
void pop_back()
int indexFromName(const QString &name) const
Look up field&#39;s index from name. Returns -1 on error.
Definition: qgsfield.cpp:424
void clear()
iterator end()
const T value(const Key &key) const
bool contains(const T &value) const
bool isNull() const
const T & at(int i) const
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) ...
void writeXml(QDomNode &layer_node, QDomDocument &document) const
Saves mVectorJoins to xml under the layer node.
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)
FieldOrigin fieldOrigin(int fieldIdx) const
Get field&#39;s origin (value from an enumeration)
Definition: qgsfield.cpp:411
void joinedFieldsChanged()
Emitted whenever the list of joined fields changes (e.g.
QStringList * joinFieldNamesSubset() const
Get subset of fields to be used from joined layer.
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.
iterator begin()
QDomNode at(int index) const