QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 
34 static QList<QgsVectorLayer*> _outEdges( QgsVectorLayer* vl )
35 {
36  QList<QgsVectorLayer*> lst;
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 
45 static bool _hasCycleDFS( QgsVectorLayer* n, QHash<QgsVectorLayer*, int>& mark )
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
69  QHash<QgsVectorLayer*, int> markDFS;
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 
95 void QgsVectorLayerJoinBuffer::removeJoin( const QString& joinLayerId )
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  const 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 
175 QVector<int> QgsVectorLayerJoinBuffer::joinSubsetIndices( QgsVectorLayer* joinLayer, const QStringList& joinFieldsSubset )
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 
200  QList< QgsVectorJoinInfo>::const_iterator joinIt = mVectorJoins.constBegin();
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  if ( joinFields[idx].name() != joinFieldName )
241  {
242  QgsField f = joinFields[idx];
243  f.setName( prefix + f.name() );
244  fields.append( f, QgsFields::OriginJoin, idx + ( joinIdx*1000 ) );
245  }
246  }
247  }
248 }
249 
251 {
252  QList< QgsVectorJoinInfo >::iterator joinIt = mVectorJoins.begin();
253  for ( ; joinIt != mVectorJoins.end(); ++joinIt )
254  {
255  cacheJoinLayer( *joinIt );
256 
257  // make sure we are connected to the joined layer
258  if ( QgsVectorLayer* vl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( joinIt->joinLayerId ) ) )
259  connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
260  }
261 }
262 
263 
264 void QgsVectorLayerJoinBuffer::writeXml( QDomNode& layer_node, QDomDocument& document ) const
265 {
266  QDomElement vectorJoinsElem = document.createElement( "vectorjoins" );
267  layer_node.appendChild( vectorJoinsElem );
268  QList< QgsVectorJoinInfo >::const_iterator joinIt = mVectorJoins.constBegin();
269  for ( ; joinIt != mVectorJoins.constEnd(); ++joinIt )
270  {
271  QDomElement joinElem = document.createElement( "join" );
272 
273  if ( joinIt->targetFieldName.isEmpty() )
274  joinElem.setAttribute( "targetField", joinIt->targetFieldIndex ); //for compatibility with 1.x
275  else
276  joinElem.setAttribute( "targetFieldName", joinIt->targetFieldName );
277 
278  joinElem.setAttribute( "joinLayerId", joinIt->joinLayerId );
279  if ( joinIt->joinFieldName.isEmpty() )
280  joinElem.setAttribute( "joinField", joinIt->joinFieldIndex ); //for compatibility with 1.x
281  else
282  joinElem.setAttribute( "joinFieldName", joinIt->joinFieldName );
283 
284  joinElem.setAttribute( "memoryCache", !joinIt->cachedAttributes.isEmpty() );
285 
286  if ( joinIt->joinFieldNamesSubset() )
287  {
288  QDomElement subsetElem = document.createElement( "joinFieldsSubset" );
289  foreach ( QString fieldName, *joinIt->joinFieldNamesSubset() )
290  {
291  QDomElement fieldElem = document.createElement( "field" );
292  fieldElem.setAttribute( "name", fieldName );
293  subsetElem.appendChild( fieldElem );
294  }
295 
296  joinElem.appendChild( subsetElem );
297  }
298 
299  if ( !joinIt->prefix.isNull() )
300  {
301  joinElem.setAttribute( "customPrefix", joinIt->prefix );
302  joinElem.setAttribute( "hasCustomPrefix", 1 );
303  }
304 
305  vectorJoinsElem.appendChild( joinElem );
306  }
307 }
308 
309 void QgsVectorLayerJoinBuffer::readXml( const QDomNode& layer_node )
310 {
311  mVectorJoins.clear();
312  QDomElement vectorJoinsElem = layer_node.firstChildElement( "vectorjoins" );
313  if ( !vectorJoinsElem.isNull() )
314  {
315  QDomNodeList joinList = vectorJoinsElem.elementsByTagName( "join" );
316  for ( int i = 0; i < joinList.size(); ++i )
317  {
318  QDomElement infoElem = joinList.at( i ).toElement();
319  QgsVectorJoinInfo info;
320  info.joinFieldName = infoElem.attribute( "joinFieldName" );
321  info.joinLayerId = infoElem.attribute( "joinLayerId" );
322  info.targetFieldName = infoElem.attribute( "targetFieldName" );
323  info.memoryCache = infoElem.attribute( "memoryCache" ).toInt();
324 
325  info.joinFieldIndex = infoElem.attribute( "joinField" ).toInt(); //for compatibility with 1.x
326  info.targetFieldIndex = infoElem.attribute( "targetField" ).toInt(); //for compatibility with 1.x
327 
328  QDomElement subsetElem = infoElem.firstChildElement( "joinFieldsSubset" );
329  if ( !subsetElem.isNull() )
330  {
331  QStringList* fieldNames = new QStringList;
332  QDomNodeList fieldNodes = infoElem.elementsByTagName( "field" );
333  for ( int i = 0; i < fieldNodes.count(); ++i )
334  *fieldNames << fieldNodes.at( i ).toElement().attribute( "name" );
335  info.setJoinFieldNamesSubset( fieldNames );
336  }
337 
338  if ( infoElem.attribute( "hasCustomPrefix" ).toInt() )
339  info.prefix = infoElem.attribute( "customPrefix" );
340  else
341  info.prefix = QString::null;
342 
343  addJoin( info );
344  }
345  }
346 }
347 
349 {
350  if ( !info )
351  return -1;
352 
353  int joinIndex = mVectorJoins.indexOf( *info );
354  if ( joinIndex == -1 )
355  return -1;
356 
357  for ( int i = 0; i < fields.count(); ++i )
358  {
359  if ( fields.fieldOrigin( i ) != QgsFields::OriginJoin )
360  continue;
361 
362  if ( fields.fieldOriginIndex( i ) / 1000 == joinIndex )
363  return i;
364  }
365  return -1;
366 }
367 
368 const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index, const QgsFields& fields, int& sourceFieldIndex ) const
369 {
370  if ( fields.fieldOrigin( index ) != QgsFields::OriginJoin )
371  return 0;
372 
373  int originIndex = fields.fieldOriginIndex( index );
374  int sourceJoinIndex = originIndex / 1000;
375  sourceFieldIndex = originIndex % 1000;
376 
377  if ( sourceJoinIndex < 0 || sourceJoinIndex >= mVectorJoins.count() )
378  return 0;
379 
380  return &( mVectorJoins[sourceJoinIndex] );
381 }
382 
384 {
385  QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer( mLayer );
386  cloned->mVectorJoins = mVectorJoins;
387  return cloned;
388 }
389 
390 void QgsVectorLayerJoinBuffer::joinedLayerUpdatedFields()
391 {
392  QgsVectorLayer* joinedLayer = qobject_cast<QgsVectorLayer*>( sender() );
393  Q_ASSERT( joinedLayer );
394 
395  // recache the joined layer
396  for ( QgsVectorJoinList::iterator it = mVectorJoins.begin(); it != mVectorJoins.end(); ++it )
397  {
398  if ( joinedLayer->id() == it->joinLayerId )
399  {
400  it->cachedAttributes.clear();
401  cacheJoinLayer( *it );
402  }
403  }
404 
405  emit joinedFieldsChanged();
406 }
const QString & name() const
Gets the name of the field.
Definition: qgsfield.cpp:59
const QList< QgsVectorJoinInfo > & vectorJoins() const
Wrapper for iterator of features from vector data provider or vector layer.
static unsigned index
QString joinFieldName
Join field in the source layer.
QString targetFieldName
Join field in the target layer.
void createJoinCaches()
Calls cacheJoinLayer() for all vector joins.
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
const QgsField & field(int fieldIdx) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.h:229
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:234
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
int joinFieldIndex
Join field index in the source layer.
static QgsMapLayerRegistry * instance()
Definition: qgssingleton.h:23
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:172
QgsVectorLayerJoinBuffer(QgsVectorLayer *layer=0)
const QgsField & at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfield.h:227
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:113
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.
const QString & name() const
Get the display name of the layer.
QString prefix
An optional prefix.
void setJoinFieldNamesSubset(QStringList *fieldNamesSubset)
Set subset of fields to be used from joined layer.
bool addJoin(const QgsVectorJoinInfo &joinInfo)
Joins another vector layer to this layer.
int fieldOriginIndex(int fieldIdx) const
Get field's origin index (its meaning is specific to each type of origin)
Definition: qgsfield.h:236
This class wraps a request for features to a vector layer (or directly its vector data provider)...
QList< int > QgsAttributeList
const QgsAttributes & attributes() const
Definition: qgsfeature.h:142
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:177
QString id() const
Get this layer's unique ID, this ID is used to access this layer from map layer registry.
Definition: qgsmaplayer.cpp:98
int count() const
Return number of items.
Definition: qgsfield.h:214
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:33
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.h:239
void setName(const QString &nam)
Set the field name.
Definition: qgsfield.cpp:89
QHash< QString, QgsAttributes > cachedAttributes
Cache for joined attributes to provide fast lookup (size is 0 if no memory caching) ...
QVector< QVariant > QgsAttributes
Definition: qgsfeature.h:100
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.
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.
FieldOrigin fieldOrigin(int fieldIdx) const
Get field's origin (value from an enumeration)
Definition: qgsfield.cpp:218
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.
const QgsFields & pendingFields() const
returns field list in the to-be-committed state
field comes from a joined layer (originIndex / 1000 = index of the join, originIndex % 1000 = index w...
Definition: qgsfield.h:180
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
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.