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