QGIS API Documentation  2.17.0-Master (0497e4a)
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 }
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