QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups 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  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->memoryCache );
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 }