Auxiliary layers may be created or loaded from auxiliary storage

This commit is contained in:
Blottiere Paul 2017-08-29 04:23:26 +01:00
parent de498314d2
commit a550a32719
10 changed files with 437 additions and 0 deletions

View File

@ -12,6 +12,55 @@
class QgsAuxiliaryLayer : QgsVectorLayer
{
%Docstring
Class allowing to manage the auxiliary storage for a vector layer
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsauxiliarystorage.h"
%End
public:
QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, const QgsVectorLayer *vlayer );
%Docstring
Constructor
\param pkField The primary key to use for joining
\param filename The database path
\param table The table name
\param vlayer The target vector layer in join definition
%End
virtual ~QgsAuxiliaryLayer();
%Docstring
Destructor
%End
QgsVectorLayerJoinInfo joinInfo() const;
%Docstring
Returns information to use for joining with primary key and so on.
:rtype: QgsVectorLayerJoinInfo
%End
bool save();
%Docstring
Commit changes and starts editing then.
:return: true if commit step passed, false otherwise
:rtype: bool
%End
};
class QgsAuxiliaryStorage
{
%Docstring
@ -95,6 +144,19 @@ class QgsAuxiliaryStorage
:rtype: bool
%End
QgsAuxiliaryLayer *createAuxiliaryLayer( const QgsField &field, const QgsVectorLayer *layer ) const;
%Docstring
Creates an auxiliary layer for a vector layer. A new table is created if
necessary. The primary key to use to construct the auxiliary layer is
given in parameter.
\param field The primary key to join
\param layer The vector layer for which the auxiliary layer has to be created
:return: A new auxiliary layer or a None if an error happened.
:rtype: QgsAuxiliaryLayer
%End
static QString extension();
%Docstring
Returns the extension used for auxiliary databases.

View File

@ -811,6 +811,14 @@ Returns the number of registered layers.
:rtype: bool
%End
QgsAuxiliaryStorage *auxiliaryStorage();
%Docstring
Returns the current auxiliary storage.
.. versionadded:: 3.0
:rtype: QgsAuxiliaryStorage
%End
signals:
void readProject( const QDomDocument & );
%Docstring

View File

@ -774,6 +774,39 @@ Return the provider type for this layer
:rtype: str
%End
bool loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage );
%Docstring
Loads the auxiliary layer for this vector layer. If there's no
corresponding table in the database, then nothing happens and false is
returned.
\param storage The auxiliary storage where to look for the table
:return: true if the auxiliary layer is well loaded, false otherwise
.. versionadded:: 3.0
:rtype: bool
%End
void setAuxiliaryLayer( QgsAuxiliaryLayer *layer = 0 );
%Docstring
Sets the current auxiliary layer. The auxiliary layer is automatically
put in editable mode and fields are updated. Moreover, a join is created
between the current layer and the auxiliary layer. Ownership is
transferred.
.. versionadded:: 3.0
%End
QgsAuxiliaryLayer *auxiliaryLayer();
%Docstring
Returns the current auxiliary layer.
.. versionadded:: 3.0
:rtype: QgsAuxiliaryLayer
%End
virtual bool readSymbology( const QDomNode &layerNode, QString &errorMessage, const QgsReadWriteContext &context );
%Docstring

View File

@ -573,6 +573,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsactionmanager.h
qgsactionscoperegistry.h
qgsanimatedicon.h
qgsauxiliarystorage.h
qgsbrowsermodel.h
qgscoordinatereferencesystem.h
qgscredentials.h

View File

@ -24,6 +24,44 @@
const QString AS_JOINFIELD = "ASPK";
const QString AS_EXTENSION = "qgd";
const QString AS_JOINPREFIX = "auxiliary_storage_";
QgsAuxiliaryLayer::QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, const QgsVectorLayer *vlayer )
: QgsVectorLayer( QString( "%1|layername=%2" ).arg( filename, table ), QString( "%1_auxiliarystorage" ).arg( table ), "ogr" )
, mLayer( vlayer )
{
// init join info
mJoinInfo.setPrefix( AS_JOINPREFIX );
mJoinInfo.setJoinLayer( this );
mJoinInfo.setJoinFieldName( AS_JOINFIELD );
mJoinInfo.setTargetFieldName( pkField );
mJoinInfo.setEditable( true );
mJoinInfo.setUpsertOnEdit( true );
mJoinInfo.setCascadedDelete( true );
}
QgsVectorLayerJoinInfo QgsAuxiliaryLayer::joinInfo() const
{
return mJoinInfo;
}
bool QgsAuxiliaryLayer::save()
{
bool rc = false;
if ( isEditable() )
{
rc = commitChanges();
}
startEditing();
return rc;
}
//
// QgsAuxiliaryStorage
//
QgsAuxiliaryStorage::QgsAuxiliaryStorage( const QgsProject &project, bool copy )
: mValid( false )
@ -92,6 +130,31 @@ bool QgsAuxiliaryStorage::save() const
}
}
QgsAuxiliaryLayer *QgsAuxiliaryStorage::createAuxiliaryLayer( const QgsField &field, const QgsVectorLayer *layer ) const
{
QgsAuxiliaryLayer *alayer = nullptr;
if ( mValid && layer && layer->isSpatial() )
{
const QString table( layer->id() );
sqlite3 *handler = openDB( currentFileName() );
if ( !tableExists( table, handler ) )
{
if ( !createTable( field.typeName(), table, handler ) )
{
close( handler );
return alayer;
}
}
alayer = new QgsAuxiliaryLayer( field.name(), currentFileName(), table, layer );
close( handler );
}
return alayer;
}
bool QgsAuxiliaryStorage::saveAs( const QString &filename ) const
{
if ( QFile::exists( filename ) )
@ -149,6 +212,16 @@ sqlite3 *QgsAuxiliaryStorage::openDB( const QString &filename )
return handler;
}
bool QgsAuxiliaryStorage::createTable( const QString &type, const QString &table, sqlite3 *handler )
{
const QString sql = QString( "CREATE TABLE IF NOT EXISTS '%1' ( '%2' %3 )" ).arg( table ).arg( AS_JOINFIELD ).arg( type );
if ( !exec( sql, handler ) )
return false;
return true;
}
sqlite3 *QgsAuxiliaryStorage::createDB( const QString &filename )
{
sqlite3 *handler = nullptr;
@ -169,6 +242,26 @@ sqlite3 *QgsAuxiliaryStorage::createDB( const QString &filename )
return handler;
}
bool QgsAuxiliaryStorage::tableExists( const QString &table, sqlite3 *handler )
{
const QString sql = QString( "SELECT 1 FROM sqlite_master WHERE type='table' AND name='%1'" ).arg( table );
int rows = 0;
int columns = 0;
char **results = nullptr;
const int rc = sqlite3_get_table( handler, sql.toStdString().c_str(), &results, &rows, &columns, nullptr );
if ( rc != SQLITE_OK )
{
debugMsg( sql, handler );
return false;
}
sqlite3_free_table( results );
if ( rows >= 1 )
return true;
return false;
}
sqlite3 *QgsAuxiliaryStorage::open( const QString &filename )
{
sqlite3 *handler = nullptr;

View File

@ -20,6 +20,7 @@
#include "qgis_core.h"
#include "qgsdatasourceuri.h"
#include "qgsvectorlayerjoininfo.h"
#include <sqlite3.h>
@ -27,6 +28,61 @@
class QgsProject;
/**
* \class QgsAuxiliaryLayer
*
* \ingroup core
*
* \brief Class allowing to manage the auxiliary storage for a vector layer
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsAuxiliaryLayer : public QgsVectorLayer
{
Q_OBJECT
public:
/**
* Constructor
*
* \param pkField The primary key to use for joining
* \param filename The database path
* \param table The table name
* \param vlayer The target vector layer in join definition
*/
QgsAuxiliaryLayer( const QString &pkField, const QString &filename, const QString &table, const QgsVectorLayer *vlayer );
/**
* Destructor
*/
virtual ~QgsAuxiliaryLayer() = default;
/**
* Copy constructor deactivated
*/
QgsAuxiliaryLayer( const QgsAuxiliaryLayer &rhs ) = delete;
QgsAuxiliaryLayer &operator=( QgsAuxiliaryLayer const &rhs ) = delete;
/**
* Returns information to use for joining with primary key and so on.
*/
QgsVectorLayerJoinInfo joinInfo() const;
/**
* Commit changes and starts editing then.
*
* \returns true if commit step passed, false otherwise
*/
bool save();
private:
QgsVectorLayerJoinInfo mJoinInfo;
const QgsVectorLayer *mLayer;
};
/**
* \class QgsAuxiliaryStorage
*
@ -102,6 +158,18 @@ class CORE_EXPORT QgsAuxiliaryStorage
*/
bool save() const;
/**
* Creates an auxiliary layer for a vector layer. A new table is created if
* necessary. The primary key to use to construct the auxiliary layer is
* given in parameter.
*
* \param field The primary key to join
* \param layer The vector layer for which the auxiliary layer has to be created
*
* \returns A new auxiliary layer or a nullptr if an error happened.
*/
QgsAuxiliaryLayer *createAuxiliaryLayer( const QgsField &field, const QgsVectorLayer *layer ) const;
/**
* Returns the extension used for auxiliary databases.
*/
@ -117,6 +185,8 @@ class CORE_EXPORT QgsAuxiliaryStorage
static sqlite3 *createDB( const QString &filename );
static sqlite3 *openDB( const QString &filename );
static void close( sqlite3 *handler );
static bool tableExists( const QString &table, sqlite3 *handler );
static bool createTable( const QString &type, const QString &table, sqlite3 *handler );
static bool exec( const QString &sql, sqlite3 *handler );
static void debugMsg( const QString &sql, sqlite3 *handler );

View File

@ -2246,6 +2246,22 @@ QList<QgsMapLayer *> QgsProject::addMapLayers(
if ( addToLegend )
emit legendLayersAdded( myResultList );
}
if ( mAuxiliaryStorage )
{
Q_FOREACH ( QgsMapLayer *mlayer, myResultList )
{
if ( mlayer->type() != QgsMapLayer::VectorLayer )
continue;
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mlayer );
if ( vl && vl->isSpatial() )
{
vl->loadAuxiliaryLayer( *mAuxiliaryStorage.get() );
}
}
}
return myResultList;
}
@ -2343,6 +2359,18 @@ void QgsProject::setTrustLayerMetadata( bool trust )
bool QgsProject::saveAuxiliaryStorage( const QString &filename )
{
Q_FOREACH ( QgsMapLayer *l, mapLayers().values() )
{
if ( l->type() != QgsMapLayer::VectorLayer )
continue;
QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( l );
if ( vl && vl->auxiliaryLayer() )
{
vl->auxiliaryLayer()->save();
}
}
if ( !filename.isEmpty() )
{
return mAuxiliaryStorage->saveAs( filename );
@ -2352,3 +2380,13 @@ bool QgsProject::saveAuxiliaryStorage( const QString &filename )
return mAuxiliaryStorage->saveAs( *this );
}
}
const QgsAuxiliaryStorage *QgsProject::auxiliaryStorage() const
{
return mAuxiliaryStorage.get();
}
QgsAuxiliaryStorage *QgsProject::auxiliaryStorage()
{
return mAuxiliaryStorage.get();
}

View File

@ -810,6 +810,20 @@ class CORE_EXPORT QgsProject : public QObject, public QgsExpressionContextGenera
*/
bool trustLayerMetadata() const { return mTrustLayerMetadata; }
/**
* Returns the current const auxiliary storage.
*
* \since QGIS 3.0
*/
const QgsAuxiliaryStorage *auxiliaryStorage() const SIP_SKIP;
/**
* Returns the current auxiliary storage.
*
* \since QGIS 3.0
*/
QgsAuxiliaryStorage *auxiliaryStorage();
signals:
//! emitted when project is being read
void readProject( const QDomDocument & );

View File

@ -88,6 +88,7 @@
#include "qgsunittypes.h"
#include "qgstaskmanager.h"
#include "qgstransaction.h"
#include "qgsauxiliarystorage.h"
#include "diagram/qgsdiagram.h"
@ -139,6 +140,8 @@ QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath,
bool readExtentFromXml )
: QgsMapLayer( VectorLayer, baseName, vectorLayerPath )
, mProviderKey( providerKey )
, mAuxiliaryLayer( nullptr )
, mAuxiliaryLayerKey( QString() )
, mReadExtentFromXml( readExtentFromXml )
{
mActions = new QgsActionManager( this );
@ -1447,6 +1450,14 @@ bool QgsVectorLayer::readXml( const QDomNode &layer_node, const QgsReadWriteCont
}
}
// auxiliary layer
const QDomNode asNode = layer_node.namedItem( QStringLiteral( "auxiliaryLayer" ) );
const QDomElement asElem = asNode.toElement();
if ( !asElem.isNull() )
{
mAuxiliaryLayerKey = asElem.attribute( QStringLiteral( "key" ) );
}
return mValid; // should be true if read successfully
} // void QgsVectorLayer::readXml
@ -1667,6 +1678,15 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node,
writeStyleManager( layer_node, document );
// auxiliary layer
QDomElement asElem = document.createElement( QStringLiteral( "auxiliaryLayer" ) );
if ( mAuxiliaryLayer )
{
const QString pkField = mAuxiliaryLayer->joinInfo().targetFieldName();
asElem.setAttribute( QStringLiteral( "key" ), pkField );
}
layer_node.appendChild( asElem );
// renderer specific settings
QString errorMsg;
return writeSymbology( layer_node, document, errorMsg, context );
@ -4223,6 +4243,58 @@ QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &resultFlag
return loadNamedStyle( theURI, resultFlag, false );
}
bool QgsVectorLayer::loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage )
{
bool rc = false;
if ( isSpatial() && storage.isValid() && !mAuxiliaryLayerKey.isEmpty() )
{
QgsAuxiliaryLayer *alayer = nullptr;
int idx = fields().lookupField( mAuxiliaryLayerKey );
if ( idx >= 0 )
{
alayer = storage.createAuxiliaryLayer( fields().field( idx ), this );
if ( alayer )
{
setAuxiliaryLayer( alayer );
rc = true;
}
}
}
return rc;
}
void QgsVectorLayer::setAuxiliaryLayer( QgsAuxiliaryLayer *alayer )
{
if ( alayer )
{
if ( mAuxiliaryLayer )
removeJoin( mAuxiliaryLayer->id() );
addJoin( alayer->joinInfo() );
if ( !alayer->isEditable() )
alayer->startEditing();
}
mAuxiliaryLayer.reset( alayer );
updateFields();
}
const QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer() const
{
return mAuxiliaryLayer.get();
}
QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer()
{
return mAuxiliaryLayer.get();
}
QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &resultFlag, bool loadFromLocalDB )
{
QgsDataSourceUri dsUri( theURI );

View File

@ -70,6 +70,8 @@ class QgsVectorLayerFeatureCounter;
class QgsAbstractVectorLayerLabeling;
class QgsPoint;
class QgsFeedback;
class QgsAuxiliaryStorage;
class QgsAuxiliaryLayer;
typedef QList<int> QgsAttributeList;
typedef QSet<int> QgsAttributeIds;
@ -784,6 +786,44 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag SIP_OUT ) override;
/**
* Loads the auxiliary layer for this vector layer. If there's no
* corresponding table in the database, then nothing happens and false is
* returned.
*
* \param storage The auxiliary storage where to look for the table
*
* \returns true if the auxiliary layer is well loaded, false otherwise
*
* \since QGIS 3.0
*/
bool loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage );
/**
* Sets the current auxiliary layer. The auxiliary layer is automatically
* put in editable mode and fields are updated. Moreover, a join is created
* between the current layer and the auxiliary layer. Ownership is
* transferred.
*
* \since QGIS 3.0
*
*/
void setAuxiliaryLayer( QgsAuxiliaryLayer *layer = nullptr );
/**
* Returns the current auxiliary layer.
*
* \since QGIS 3.0
*/
QgsAuxiliaryLayer *auxiliaryLayer();
/**
* Returns the current const auxiliary layer.
*
* \since QGIS 3.0
*/
const QgsAuxiliaryLayer *auxiliaryLayer() const SIP_SKIP;
/**
* Read the symbology for the current layer from the Dom node supplied.
* \param layerNode node that will contain the symbology definition for this layer.
* \param errorMessage reference to string that will be updated with any error messages
@ -2145,6 +2185,12 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
mutable bool mValidExtent = false;
mutable bool mLazyExtent = true;
//! Auxiliary layer
std::unique_ptr<QgsAuxiliaryLayer> mAuxiliaryLayer;
//! Key to use to join auxiliary layer
QString mAuxiliaryLayerKey;
// Features in renderer classes counted
bool mSymbolFeatureCounted = false;