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->fields().indexFromName( joinInfo.joinFieldName );
127 
128  if ( joinFieldIndex < 0 || joinFieldIndex >= cacheLayer->fields().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->fields();
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->fields();
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:70
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:310
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:185
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
QgsFields fields() const
Returns the list of fields of this layer.
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:356
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:177
void setName(const QString &name)
Set the field name.
Definition: qgsfield.cpp:100
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
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:333
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:235
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:285
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:40
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:338
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:325
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