Skip to content

Commit

Permalink
Allow to filter joined fields with a virtual layer
Browse files Browse the repository at this point in the history
  • Loading branch information
Hugo Mercier committed Jan 7, 2016
1 parent 4bb09d0 commit bbf2137
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 30 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -145,6 +145,7 @@
%Include qgsvisibilitypresetcollection.sip
%Include qgslayerdefinition.sip
%Include qgsvirtuallayerdefinition.sip
%Include qgsvirtuallayerdefinitionutils.sip
%Include qgsxmlutils.sip

%Include auth/qgsauthcertutils.sip
Expand Down
7 changes: 7 additions & 0 deletions python/core/layertree/qgslayertreeutils.sip
Expand Up @@ -44,4 +44,11 @@ class QgsLayerTreeUtils
static QString legendFilterByExpression( const QgsLayerTreeLayer& layer, bool* enabled = 0 );
//! Test if one of the layers in a group has an expression filter
static bool hasLegendFilterExpression( const QgsLayerTreeGroup& group );

//! Insert a QgsMapLayer just below another one
//! @param group the tree group where layers are (can be the root group)
//! @param refLayer the reference layer
//! @param layerToInsert the new layer to insert just below the reference layer
//! @returns the new tree layer
static QgsLayerTreeLayer* insertLayerBelow( QgsLayerTreeGroup* group, const QgsMapLayer* refLayer, QgsMapLayer* layerToInsert );
};
12 changes: 12 additions & 0 deletions python/core/qgsvirtuallayerdefinitionutils.sip
@@ -0,0 +1,12 @@
/**
* Utils class for QgsVirtualLayerDefinition
*/
class QgsVirtualLayerDefinitionUtils
{
%TypeHeaderCode
#include <qgsvirtuallayerdefinitionutils.h>
%End
public:
//! Get a virtual layer definition from a vector layer where vector joins are replaced by SQL LEFT JOINs
static QgsVirtualLayerDefinition fromJoinedLayer( QgsVectorLayer* joinedLayer );
};
82 changes: 52 additions & 30 deletions src/app/qgisapp.cpp
Expand Up @@ -230,6 +230,8 @@
#include "qgsmaprendererparalleljob.h"
#include "qgsversioninfo.h"
#include "qgslegendfilterbutton.h"
#include "qgsvirtuallayerdefinition.h"
#include "qgsvirtuallayerdefinitionutils.h"

#include "qgssublayersdialog.h"
#include "ogr/qgsopenvectorlayerdialog.h"
Expand Down Expand Up @@ -3793,37 +3795,11 @@ void QgisApp::replaceSelectedVectorLayer( const QString& uri, const QString& lay
return;

QgsMapLayerRegistry::instance()->addMapLayer( newLayer, /*addToLegend*/ false, /*takeOwnership*/ true );
// copy symbology, if possible
if ( oldLayer->geometryType() == newLayer->geometryType() )
{
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QDomElement rootNode = doc.createElement( "qgis" );
rootNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
doc.appendChild( rootNode );
QString errorMsg;
oldLayer->writeSymbology( rootNode, doc, errorMsg );
newLayer->readSymbology( rootNode, errorMsg );
}
duplicateVectorStyle( oldLayer, newLayer );

// get the index in its parent for the current layer
QgsLayerTreeLayer* inTree = QgsProject::instance()->layerTreeRoot()->findLayer( oldLayer->id() );
int idx = 0;
foreach ( QgsLayerTreeNode* vl, inTree->parent()->children() )
{
if ( vl->nodeType() == QgsLayerTreeNode::NodeLayer && static_cast<QgsLayerTreeLayer*>( vl )->layer() == oldLayer )
{
break;
}
idx++;
}
// insert the new layer
QgsLayerTreeGroup* parent = static_cast<QgsLayerTreeGroup*>( inTree->parent() ) ? static_cast<QgsLayerTreeGroup*>( inTree->parent() ) : QgsProject::instance()->layerTreeRoot();
parent->insertLayer( idx, newLayer );
// remove the current layer
// insert the new layer just below the old one
QgsLayerTreeUtils::insertLayerBelow( QgsProject::instance()->layerTreeRoot(), oldLayer, newLayer );
// and remove the old layer
QgsMapLayerRegistry::instance()->removeMapLayer( oldLayer );
} // QgisApp:replaceSelectedVectorLayer

Expand Down Expand Up @@ -7431,12 +7407,58 @@ QList<QgsMapLayer *> QgisApp::editableLayers( bool modified ) const
return editLayers;
}

void QgisApp::duplicateVectorStyle( QgsVectorLayer* srcLayer, QgsVectorLayer* destLayer )
{
// copy symbology, if possible
if ( srcLayer->geometryType() == destLayer->geometryType() )
{
QDomImplementation DomImplementation;
QDomDocumentType documentType =
DomImplementation.createDocumentType(
"qgis", "http://mrcc.com/qgis.dtd", "SYSTEM" );
QDomDocument doc( documentType );
QDomElement rootNode = doc.createElement( "qgis" );
rootNode.setAttribute( "version", QString( "%1" ).arg( QGis::QGIS_VERSION ) );
doc.appendChild( rootNode );
QString errorMsg;
srcLayer->writeSymbology( rootNode, doc, errorMsg );
destLayer->readSymbology( rootNode, errorMsg );
}
}

void QgisApp::layerSubsetString()
{
QgsVectorLayer *vlayer = qobject_cast<QgsVectorLayer *>( activeLayer() );
if ( !vlayer )
return;

if ( !vlayer->vectorJoins().isEmpty() )
{
if ( QMessageBox::question( NULL, tr( "Filter on joined fields" ),
tr( "You are about to set a subset filter on a layer that has joined fields. "
"Joined fields cannot be filtered, unless you convert the layer to a virtual layer first. "
"Would you like to create a virtual layer out of this layer first ?" ),
QMessageBox::Yes | QMessageBox::No ) == QMessageBox::Yes )
{
QgsVirtualLayerDefinition def = QgsVirtualLayerDefinitionUtils::fromJoinedLayer( vlayer );
QgsVectorLayer* newLayer = new QgsVectorLayer( def.toString(), vlayer->name() + " (virtual)", "virtual" );
if ( newLayer->isValid() )
{
duplicateVectorStyle( vlayer, newLayer );
QgsMapLayerRegistry::instance()->addMapLayer( newLayer, /*addToLegend*/ false, /*takeOwnership*/ true );
QgsLayerTreeUtils::insertLayerBelow( QgsProject::instance()->layerTreeRoot(), vlayer, newLayer );
mLayerTreeView->setCurrentLayer( newLayer );
// hide the old layer
QgsProject::instance()->layerTreeRoot()->findLayer( vlayer->id() )->setVisible( Qt::Unchecked );
vlayer = newLayer;
}
else
{
delete newLayer;
}
}
}

// launch the query builder
QgsQueryBuilder *qb = new QgsQueryBuilder( vlayer, this );
QString subsetBefore = vlayer->subsetString();
Expand Down
3 changes: 3 additions & 0 deletions src/app/qgisapp.h
Expand Up @@ -1402,6 +1402,9 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
/** Apply raster brightness */
void adjustBrightnessContrast( int delta, bool updateBrightness = true );

/** Copy a vector style from a layer to another one, if they have the same geometry type */
void duplicateVectorStyle( QgsVectorLayer* srcLayer, QgsVectorLayer* destLayer );

QgisAppStyleSheet *mStyleSheetBuilder;

// actions for menus and toolbars -----------------
Expand Down
1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -205,6 +205,7 @@ SET(QGIS_CORE_SRCS
qgsvectorsimplifymethod.cpp
qgsvisibilitypresetcollection.cpp
qgsvirtuallayerdefinition.cpp
qgsvirtuallayerdefinitionutils.cpp
qgsxmlutils.cpp
qgsslconnect.cpp
qgslocalec.cpp
Expand Down
18 changes: 18 additions & 0 deletions src/core/layertree/qgslayertreeutils.cpp
Expand Up @@ -394,3 +394,21 @@ bool QgsLayerTreeUtils::hasLegendFilterExpression( const QgsLayerTreeGroup& grou
}
return false;
}

QgsLayerTreeLayer* QgsLayerTreeUtils::insertLayerBelow( QgsLayerTreeGroup* group, const QgsMapLayer* refLayer, QgsMapLayer* layerToInsert )
{
// get the index of the reflayer
QgsLayerTreeLayer* inTree = group->findLayer( refLayer->id() );
int idx = 0;
foreach ( QgsLayerTreeNode* vl, inTree->parent()->children() )
{
if ( vl->nodeType() == QgsLayerTreeNode::NodeLayer && static_cast<QgsLayerTreeLayer*>( vl )->layer() == refLayer )
{
break;
}
idx++;
}
// insert the new layer
QgsLayerTreeGroup* parent = static_cast<QgsLayerTreeGroup*>( inTree->parent() ) ? static_cast<QgsLayerTreeGroup*>( inTree->parent() ) : group;
return parent->insertLayer( idx, layerToInsert );
}
8 changes: 8 additions & 0 deletions src/core/layertree/qgslayertreeutils.h
Expand Up @@ -27,6 +27,7 @@ class QStringList;
class QgsLayerTreeNode;
class QgsLayerTreeGroup;
class QgsLayerTreeLayer;
class QgsMapLayer;

/**
* Assorted functions for dealing with layer trees.
Expand Down Expand Up @@ -72,6 +73,13 @@ class CORE_EXPORT QgsLayerTreeUtils
static QString legendFilterByExpression( const QgsLayerTreeLayer& layer, bool* enabled = nullptr );
//! Test if one of the layers in a group has an expression filter
static bool hasLegendFilterExpression( const QgsLayerTreeGroup& group );

//! Insert a QgsMapLayer just below another one
//! @param group the tree group where layers are (can be the root group)
//! @param refLayer the reference layer
//! @param layerToInsert the new layer to insert just below the reference layer
//! @returns the new tree layer
static QgsLayerTreeLayer* insertLayerBelow( QgsLayerTreeGroup* group, const QgsMapLayer* refLayer, QgsMapLayer* layerToInsert );
};

#endif // QGSLAYERTREEUTILS_H
2 changes: 2 additions & 0 deletions src/core/qgsvirtuallayerdefinition.cpp
Expand Up @@ -19,6 +19,8 @@ email : hugo dot mercier at oslandia dot com
#include <QStringList>

#include "qgsvirtuallayerdefinition.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"

QgsVirtualLayerDefinition::QgsVirtualLayerDefinition( const QString& filePath ) :
mFilePath( filePath ),
Expand Down
80 changes: 80 additions & 0 deletions src/core/qgsvirtuallayerdefinitionutils.cpp
@@ -0,0 +1,80 @@
/***************************************************************************
qgsvirtuallayerdefinitionutils.cpp
begin : Jan 2016
copyright : (C) 2016 Hugo Mercier, Oslandia
email : hugo dot mercier at oslandia dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsvirtuallayerdefinitionutils.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"

QgsVirtualLayerDefinition QgsVirtualLayerDefinitionUtils::fromJoinedLayer( QgsVectorLayer* layer )
{
QgsVirtualLayerDefinition def;

QStringList leftJoins;
QStringList columns;

columns << "t.*"; // columns from the main layer

// look for the uid
const QgsFields& fields = layer->dataProvider()->fields();
{
QgsAttributeList pk = layer->dataProvider()->pkAttributeIndexes();
if ( pk.size() == 1 )
{
def.setUid( fields.field( pk[0] ).name() );
}
else
{
// find an uid name
QString uid = "uid";
while ( fields.fieldNameIndex( uid ) != -1 )
uid += "_"; // add "_" each time this name already exists

// add a column
columns << "t.rowid AS " + uid;
def.setUid( uid );
}
}

int joinIdx = 0;
foreach ( const QgsVectorJoinInfo& join, layer->vectorJoins() )
{
QString joinName = QString( "j%1" ).arg( ++joinIdx );
QString prefix = join.prefix.isEmpty() ? layer->name() + "_" : join.prefix;

leftJoins << QString( "LEFT JOIN %1 AS %2 ON t.\"%3\"=%2.\"%5\"" ).arg( join.joinLayerId ).arg( joinName ).arg( join.joinFieldName ).arg( join.targetFieldName );
if ( join.joinFieldNamesSubset() )
{
foreach ( const QString& f, *join.joinFieldNamesSubset() )
{
columns << joinName + "." + f + " AS " + prefix + f;
}
}
else
{
const QgsFields& joinedFields = layer->dataProvider()->fields();
for ( int i = 0; i < joinedFields.count(); i++ )
{
const QgsField& f = joinedFields.field( i );
columns << joinName + "." + f.name() + " AS " + prefix + f.name();
}
}
}

QString query = "SELECT " + columns.join( ", " ) + " FROM " + layer->id() + " AS t " + leftJoins.join( " " );
def.setQuery( query );

return def;
}
34 changes: 34 additions & 0 deletions src/core/qgsvirtuallayerdefinitionutils.h
@@ -0,0 +1,34 @@
/***************************************************************************
qgsvirtuallayerdefinitionutils.h
begin : Jan, 2016
copyright : (C) 2016 Hugo Mercier, Oslandia
email : hugo dot mercier at oslandia dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSVIRTUALLAYERDEFINITION_UTILS_H
#define QGSVIRTUALLAYERDEFINITION_UTILS_H

#include "qgsvirtuallayerdefinition.h"

class QgsVectorLayer;

/**
* Utils class for QgsVirtualLayerDefinition
*/
class CORE_EXPORT QgsVirtualLayerDefinitionUtils
{
public:
//! Get a virtual layer definition from a vector layer where vector joins are replaced by SQL LEFT JOINs
static QgsVirtualLayerDefinition fromJoinedLayer( QgsVectorLayer* joinedLayer );
};

#endif
3 changes: 3 additions & 0 deletions src/providers/virtual/qgsvirtuallayersourceselect.cpp
Expand Up @@ -107,6 +107,9 @@ QgsVirtualLayerSourceSelect::QgsVirtualLayerSourceSelect( QWidget* parent, Qt::W

apis->prepare();
mQueryEdit->lexer()->setAPIs( apis );

mQueryEdit->setWrapMode( QsciScintilla::WrapWord );

}

QgsVirtualLayerSourceSelect::~QgsVirtualLayerSourceSelect()
Expand Down

0 comments on commit bbf2137

Please sign in to comment.