QGIS API Documentation  2.14.0-Essen
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  mVectorJoins.push_back( joinInfo );
66 
67  // run depth-first search to detect cycles in the graph of joins between layers.
68  // any cycle would cause infinite recursion when updating fields
70  if ( mLayer && _hasCycleDFS( mLayer, markDFS ) )
71  {
72  // we have to reject this one
73  mVectorJoins.pop_back();
74  return false;
75  }
76 
77  //cache joined layer to virtual memory if specified by user
78  if ( joinInfo.memoryCache )
79  {
80  cacheJoinLayer( mVectorJoins.last() );
81  }
82 
83  // Wait for notifications about changed fields in joined layer to propagate them.
84  // During project load the joined layers possibly do not exist yet so the connection will not be created,
85  // but then QgsProject makes sure to call createJoinCaches() which will do the connection.
86  // Unique connection makes sure we do not respond to one layer's update more times (in case of multiple join)
87  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo.joinLayerId ) ) )
88  {
89  connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
90  }
91 
92  emit joinedFieldsChanged();
93  return true;
94 }
95 
96 
98 {
99  bool res = false;
100  for ( int i = 0; i < mVectorJoins.size(); ++i )
101  {
102  if ( mVectorJoins.at( i ).joinLayerId == joinLayerId )
103  {
104  mVectorJoins.removeAt( i );
105  res = true;
106  }
107  }
108 
109  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinLayerId ) ) )
110  {
111  disconnect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ) );
112  }
113 
114  emit joinedFieldsChanged();
115  return res;
116 }
117 
118 void QgsVectorLayerJoinBuffer::cacheJoinLayer( QgsVectorJoinInfo& joinInfo )
119 {
120  //memory cache not required or already done
121  if ( !joinInfo.memoryCache || !joinInfo.cachedAttributes.isEmpty() )
122  {
123  return;
124  }
125 
126  QgsVectorLayer* cacheLayer = dynamic_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinInfo.joinLayerId ) );
127  if ( cacheLayer )
128  {
129  int joinFieldIndex;
130  if ( joinInfo.joinFieldName.isEmpty() )
131  joinFieldIndex = joinInfo.joinFieldIndex; //for compatibility with 1.x
132  else
133  joinFieldIndex = cacheLayer->fields().indexFromName( joinInfo.joinFieldName );
134 
135  if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().count() )
136  return;
137 
138  joinInfo.cachedAttributes.clear();
139 
140  QgsFeatureRequest request;
142 
143  // maybe user requested just a subset of layer's attributes
144  // so we do not have to cache everything
145  bool hasSubset = joinInfo.joinFieldNamesSubset();
146  QVector<int> subsetIndices;
147  if ( hasSubset )
148  {
149  subsetIndices = joinSubsetIndices( cacheLayer, *joinInfo.joinFieldNamesSubset() );
150 
151  // we need just subset of attributes - but make sure to include join field name
152  QgsAttributeList cacheLayerAttrs = subsetIndices.toList();
153  if ( !cacheLayerAttrs.contains( joinFieldIndex ) )
154  cacheLayerAttrs.append( joinFieldIndex );
155  request.setSubsetOfAttributes( cacheLayerAttrs );
156  }
157 
158  QgsFeatureIterator fit = cacheLayer->getFeatures( request );
159  QgsFeature f;
160  while ( fit.nextFeature( f ) )
161  {
162  QgsAttributes attrs = f.attributes();
163  QString key = attrs.at( joinFieldIndex ).toString();
164  if ( hasSubset )
165  {
166  QgsAttributes subsetAttrs( subsetIndices.count() );
167  for ( int i = 0; i < subsetIndices.count(); ++i )
168  subsetAttrs[i] = attrs.at( subsetIndices.at( i ) );
169  joinInfo.cachedAttributes.insert( key, subsetAttrs );
170  }
171  else
172  {
173  QgsAttributes attrs2 = attrs;
174  attrs2.remove( joinFieldIndex ); // skip the join field to avoid double field names (fields often have the same name)
175  joinInfo.cachedAttributes.insert( key, attrs2 );
176  }
177  }
178  }
179 }
180 
181 
183 {
184  QVector<int> subsetIndices;
185  const QgsFields& fields = joinLayer->fields();
186  for ( int i = 0; i < joinFieldsSubset.count(); ++i )
187  {
188  QString joinedFieldName = joinFieldsSubset.at( i );
189  int index = fields.fieldNameIndex( joinedFieldName );
190  if ( index != -1 )
191  {
192  subsetIndices.append( index );
193  }
194  else
195  {
196  QgsDebugMsg( "Join layer subset field not found: " + joinedFieldName );
197  }
198  }
199 
200  return subsetIndices;
201 }
202 
204 {
205  QString prefix;
206 
208  for ( int joinIdx = 0 ; joinIt != mVectorJoins.constEnd(); ++joinIt, ++joinIdx )
209  {
210  QgsVectorLayer* joinLayer = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) );
211  if ( !joinLayer )
212  {
213  continue;
214  }
215 
216  const QgsFields& joinFields = joinLayer->fields();
217  QString joinFieldName;
218  if ( joinIt->joinFieldName.isEmpty() && joinIt->joinFieldIndex >= 0 && joinIt->joinFieldIndex < joinFields.count() )
219  joinFieldName = joinFields.field( joinIt->joinFieldIndex ).name(); //for compatibility with 1.x
220  else
221  joinFieldName = joinIt->joinFieldName;
222 
223  QSet<QString> subset;
224  bool hasSubset = false;
225  if ( joinIt->joinFieldNamesSubset() )
226  {
227  hasSubset = true;
228  subset = QSet<QString>::fromList( *joinIt->joinFieldNamesSubset() );
229  }
230 
231  if ( joinIt->prefix.isNull() )
232  {
233  prefix = joinLayer->name() + '_';
234  }
235  else
236  {
237  prefix = joinIt->prefix;
238  }
239 
240  for ( int idx = 0; idx < joinFields.count(); ++idx )
241  {
242  // if using just a subset of fields, filter some of them out
243  if ( hasSubset && !subset.contains( joinFields[idx].name() ) )
244  continue;
245 
246  //skip the join field to avoid double field names (fields often have the same name)
247  // when using subset of field, use all the selected fields
248  if ( hasSubset || joinFields[idx].name() != joinFieldName )
249  {
250  QgsField f = joinFields[idx];
251  f.setName( prefix + f.name() );
252  fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx*1000 ) );
253  }
254  }
255  }
256 }
257 
259 {
260  QList< QgsVectorJoinInfo >::iterator joinIt = mVectorJoins.begin();
261  for ( ; joinIt != mVectorJoins.end(); ++joinIt )
262  {
263  cacheJoinLayer( *joinIt );
264 
265  // make sure we are connected to the joined layer
266  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) ) )
267  connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
268  }
269 }
270 
271 
272 void QgsVectorLayerJoinBuffer::writeXml( QDomNode& layer_node, QDomDocument& document ) const
273 {
274  QDomElement vectorJoinsElem = document.createElement( "vectorjoins" );
275  layer_node.appendChild( vectorJoinsElem );
277  for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
278  {
279  QDomElement joinElem = document.createElement( "join" );
280 
281  if ( joinIt->targetFieldName.isEmpty() )
282  joinElem.setAttribute( "targetField", joinIt->targetFieldIndex ); //for compatibility with 1.x
283  else
284  joinElem.setAttribute( "targetFieldName", joinIt->targetFieldName );
285 
286  joinElem.setAttribute( "joinLayerId", joinIt->joinLayerId );
287  if ( joinIt->joinFieldName.isEmpty() )
288  joinElem.setAttribute( "joinField", joinIt->joinFieldIndex ); //for compatibility with 1.x
289  else
290  joinElem.setAttribute( "joinFieldName", joinIt->joinFieldName );
291 
292  joinElem.setAttribute( "memoryCache", joinIt->memoryCache );
293 
294  if ( joinIt->joinFieldNamesSubset() )
295  {
296  QDomElement subsetElem = document.createElement( "joinFieldsSubset" );
297  Q_FOREACH ( const QString& fieldName, *joinIt->joinFieldNamesSubset() )
298  {
299  QDomElement fieldElem = document.createElement( "field" );
300  fieldElem.setAttribute( "name", fieldName );
301  subsetElem.appendChild( fieldElem );
302  }
303 
304  joinElem.appendChild( subsetElem );
305  }
306 
307  if ( !joinIt->prefix.isNull() )
308  {
309  joinElem.setAttribute( "customPrefix", joinIt->prefix );
310  joinElem.setAttribute( "hasCustomPrefix", 1 );
311  }
312 
313  vectorJoinsElem.appendChild( joinElem );
314  }
315 }
316 
318 {
319  mVectorJoins.clear();
320  QDomElement vectorJoinsElem = layer_node.firstChildElement( "vectorjoins" );
321  if ( !vectorJoinsElem.isNull() )
322  {
323  QDomNodeList joinList = vectorJoinsElem.elementsByTagName( "join" );
324  for ( int i = 0; i < joinList.size(); ++i )
325  {
326  QDomElement infoElem = joinList.at( i ).toElement();
327  QgsVectorJoinInfo info;
328  info.joinFieldName = infoElem.attribute( "joinFieldName" );
329  info.joinLayerId = infoElem.attribute( "joinLayerId" );
330  info.targetFieldName = infoElem.attribute( "targetFieldName" );
331  info.memoryCache = infoElem.attribute( "memoryCache" ).toInt();
332 
333  info.joinFieldIndex = infoElem.attribute( "joinField" ).toInt(); //for compatibility with 1.x
334  info.targetFieldIndex = infoElem.attribute( "targetField" ).toInt(); //for compatibility with 1.x
335 
336  QDomElement subsetElem = infoElem.firstChildElement( "joinFieldsSubset" );
337  if ( !subsetElem.isNull() )
338  {
339  QStringList* fieldNames = new QStringList;
340  QDomNodeList fieldNodes = infoElem.elementsByTagName( "field" );
341  for ( int i = 0; i < fieldNodes.count(); ++i )
342  *fieldNames << fieldNodes.at( i ).toElement().attribute( "name" );
343  info.setJoinFieldNamesSubset( fieldNames );
344  }
345 
346  if ( infoElem.attribute( "hasCustomPrefix" ).toInt() )
347  info.prefix = infoElem.attribute( "customPrefix" );
348  else
349  info.prefix = QString::null;
350 
351  addJoin( info );
352  }
353  }
354 }
355 
357 {
358  if ( !info )
359  return -1;
360 
361  int joinIndex = mVectorJoins.indexOf( *info );
362  if ( joinIndex == -1 )
363  return -1;
364 
365  for ( int i = 0; i < fields.count(); ++i )
366  {
367  if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
368  continue;
369 
370  if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
371  return i;
372  }
373  return -1;
374 }
375 
376 const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const
377 {
378  if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
379  return nullptr;
380 
381  int originIndex = fields.fieldOriginIndex( index );
382  int sourceJoinIndex = originIndex / 1000;
383  sourceFieldIndex = originIndex % 1000;
384 
385  if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
386  return nullptr;
387 
388  return &( mVectorJoins[sourceJoinIndex] );
389 }
390 
392 {
393  QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer( mLayer );
394  cloned->mVectorJoins = mVectorJoins;
395  return cloned;
396 }
397 
398 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
399 {
400  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
401  Q_ASSERT( joinedLayer );
402 
403  // recache the joined layer
404  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
405  {
406  if ( joinedLayer->id() == it->joinLayerId )
407  {
408  it->cachedAttributes.clear();
409  cacheJoinLayer( *it );
410  }
411  }
412 
413  emit joinedFieldsChanged();
414 }
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:195
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:503
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:187
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.
const char * name() const
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
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) ...
bool isEmpty() const
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