Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add a provider for virtual layers
  • Loading branch information
Hugo Mercier committed Dec 18, 2015
1 parent 13f4081 commit e60712e
Show file tree
Hide file tree
Showing 24 changed files with 4,132 additions and 1 deletion.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -141,6 +141,7 @@
%Include qgsvectorlayerfeatureiterator.sip
%Include qgsvisibilitypresetcollection.sip
%Include qgslayerdefinition.sip
%Include qgsvirtuallayerdefinition.sip

%Include auth/qgsauthcertutils.sip
%Include auth/qgsauthconfig.sip
Expand Down
125 changes: 125 additions & 0 deletions python/core/qgsvirtuallayerdefinition.sip
@@ -0,0 +1,125 @@
/**
* Class to manipulate the definition of a virtual layer
*
* It is used to extract parameters from an initial virtual layer definition as well as
* to store the complete, expanded definition once types have been detected.
*/
class QgsVirtualLayerDefinition
{
%TypeHeaderCode
#include <qgsvirtuallayerdefinition.h>
%End
public:
/**
* A SourceLayer is either a reference to a live layer in the registry
* or all the parameters needed to load it (provider key, source, etc.)
*/
class SourceLayer
{
public:
//! Constructor variant to build a live layer reference
SourceLayer( const QString& name, const QString& ref );
//! Constructor variant to build a layer with a provider and a source
SourceLayer( const QString& name, const QString& source, const QString& provider, const QString& encoding );

//! Is it a live layer or not ?
bool isReferenced() const;

//! The reference (id) of the live layer
QString reference() const;

//! Name of the layer
QString name() const;

//! Provider key
QString provider() const;

//! The source url used by the provider to build the layer
QString source() const;

//! Optional encoding for the provider
QString encoding() const;
};

//! Constructor with an optional file path
QgsVirtualLayerDefinition( const QString& filePath = "" );

//! Constructor to build a definition from a QUrl
//! The path part of the URL is extracted as well as the following optional keys:
//! layer_ref=layer_id[:name] represents a live layer referenced by its ID. An optional name can be given
//! layer=provider:source[:name[:encoding]] represents a layer given by its provider key, its source url (URL-encoded).
//! An optional name and encoding can be given
//! geometry=column_name[:type:srid] gives the definition of the geometry column.
//! Type can be either a WKB type code or a string (point, linestring, etc.)
//! srid is an integer
//! uid=column_name is the name of a column with unique integer values.
//! nogeometry is a flag to force the layer to be a non-geometry layer
//! query=sql represents the SQL query. Must be URL-encoded
//! field=column_name:[int|real|text] represents a field with its name and its type
static QgsVirtualLayerDefinition fromUrl( const QUrl& url );

//! Convert the definition into a QUrl
QUrl toUrl() const;

//! Convert into a QString that can be read by the virtual layer provider
QString toString() const;

//! Add a live layer source layer
void addSource( const QString& name, const QString ref );

//! Add a layer with a source, a provider and an encoding
void addSource( const QString& name, const QString source, const QString& provider, const QString& encoding = "" );

//! List of source layers
typedef QList<QgsVirtualLayerDefinition::SourceLayer> SourceLayers;

//! Get access to the source layers
const SourceLayers& sourceLayers() const;

//! Get the SQL query
QString query() const;
//! Set the SQL query
void setQuery( const QString& query );

//! Get the file path. May be empty
QString filePath() const;
//! Set the file path
void setFilePath( const QString& filePath );

//! Get the name of the field with unique identifiers
QString uid() const;
//! Set the name of the field with unique identifiers
void setUid( const QString& uid );

//! Get the name of the geometry field. Empty if no geometry field
QString geometryField() const;
//! Set the name of the geometry field
void setGeometryField( const QString& geometryField );

//! Get the type of the geometry
//! QgsWKBTypes::NoGeometry to hide any geometry
//! QgsWKBTypes::Unknown for unknown types
QgsWKBTypes::Type geometryWkbType() const;
//! Set the type of the geometry
void setGeometryWkbType( QgsWKBTypes::Type t );

//! Get the SRID of the geometry
long geometrySrid() const;
//! Set the SRID of the geometry
void setGeometrySrid( long srid );

//! Get field definitions
const QgsFields& fields() const;
//! Set field definitions
void setFields( const QgsFields& fields );

//! Convenience method to test if a given source layer is part of the definition
bool hasSourceLayer( QString name ) const;

//! Convenience method to test whether the definition has referenced (live) layers
bool hasReferencedLayers() const;

//! Convenient method to test if the geometry is defined (not NoGeometry and not Unknown)
bool hasDefinedGeometry() const;
};

1 change: 1 addition & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -202,6 +202,7 @@ SET(QGIS_CORE_SRCS
qgsvectorlayerundocommand.cpp
qgsvectorsimplifymethod.cpp
qgsvisibilitypresetcollection.cpp
qgsvirtuallayerdefinition.cpp
qgsxmlutils.cpp
qgsslconnect.cpp
qgslocalec.cpp
Expand Down
250 changes: 250 additions & 0 deletions src/core/qgsvirtuallayerdefinition.cpp
@@ -0,0 +1,250 @@
/***************************************************************************
qgsvirtuallayerdefinition.cpp
begin : December 2015
copyright : (C) 2015 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 <QUrl>
#include <QRegExp>
#include <QStringList>

#include "qgsvirtuallayerdefinition.h"

QgsVirtualLayerDefinition::QgsVirtualLayerDefinition( const QString& filePath ) :
mFilePath( filePath ),
mGeometryWkbType( QgsWKBTypes::Unknown ),
mGeometrySrid( 0 )
{
}

QgsVirtualLayerDefinition QgsVirtualLayerDefinition::fromUrl( const QUrl& url )
{
QgsVirtualLayerDefinition def;

def.setFilePath( url.path() );

// regexp for column name
const QString columnNameRx( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" );

QgsFields fields;

int layerIdx = 0;
QList<QPair<QString, QString> > items = url.queryItems();
for ( int i = 0; i < items.size(); i++ )
{
QString key = items.at( i ).first;
QString value = items.at( i ).second;
if ( key == "layer_ref" )
{
layerIdx++;
// layer id, with optional layer_name
int pos = value.indexOf( ':' );
QString layerId, vlayerName;
if ( pos == -1 )
{
layerId = value;
vlayerName = QString( "vtab%1" ).arg( layerIdx );
}
else
{
layerId = value.left( pos );
vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
}
// add the layer to the list
def.addSource( vlayerName, layerId );
}
else if ( key == "layer" )
{
layerIdx++;
// syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
int pos = value.indexOf( ':' );
if ( pos != -1 )
{
QString providerKey, source, vlayerName, encoding = "UTF-8";

providerKey = value.left( pos );
int pos2 = value.indexOf( ':', pos + 1 );
if ( pos2 != -1 )
{
source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
int pos3 = value.indexOf( ':', pos2 + 1 );
if ( pos3 != -1 )
{
vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
encoding = value.mid( pos3 + 1 );
}
else
{
vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
}
}
else
{
source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
vlayerName = QString( "vtab%1" ).arg( layerIdx );
}

def.addSource( vlayerName, source, providerKey, encoding );
}
}
else if ( key == "geometry" )
{
// geometry field definition, optional
// geometry_column(:wkb_type:srid)?
QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
int pos = reGeom.indexIn( value );
if ( pos >= 0 )
{
def.setGeometryField( reGeom.cap( 1 ) );
if ( reGeom.captureCount() > 1 )
{
// not used by the spatialite provider for now ...
QgsWKBTypes::Type wkbType = QgsWKBTypes::parseType( reGeom.cap( 2 ) );
if ( wkbType == QgsWKBTypes::Unknown )
{
wkbType = static_cast<QgsWKBTypes::Type>( reGeom.cap( 2 ).toLong() );
}
def.setGeometryWkbType( wkbType );
def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
}
}
}
else if ( key == "nogeometry" )
{
def.setGeometryWkbType( QgsWKBTypes::NoGeometry );
}
else if ( key == "uid" )
{
def.setUid( value );
}
else if ( key == "query" )
{
// url encoded query
def.setQuery( value );
}
else if ( key == "field" )
{
// field_name:type (int, real, text)
QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
int pos = reField.indexIn( value );
if ( pos >= 0 )
{
QString fieldName( reField.cap( 1 ) );
QString fieldType( reField.cap( 2 ) );
if ( fieldType == "int" )
{
fields.append( QgsField( fieldName, QVariant::Int, fieldType ) );
}
else if ( fieldType == "real" )
{
fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
}
if ( fieldType == "text" )
{
fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
}
}
}
}
def.setFields( fields );

return def;
}

QUrl QgsVirtualLayerDefinition::toUrl() const
{
QUrl url;
url.setPath( filePath() );

foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
{
if ( l.isReferenced() )
url.addQueryItem( "layer_ref", QString( "%1:%2" ).arg( l.reference() ).arg( l.name() ) );
else
url.addQueryItem( "layer", QString( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
.arg( l.provider() )
.arg( QString( QUrl::toPercentEncoding( l.name() ) ) )
.arg( l.encoding() )
.arg( QString( QUrl::toPercentEncoding( l.source() ) ) ) );
}

if ( !query().isEmpty() )
{
url.addQueryItem( "query", query() );
}

if ( !uid().isEmpty() )
url.addQueryItem( "uid", uid() );

if ( geometryWkbType() == QgsWKBTypes::NoGeometry )
url.addQueryItem( "nogeometry", "" );
else if ( !geometryField().isEmpty() )
{
if ( hasDefinedGeometry() )
url.addQueryItem( "geometry", QString( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
else
url.addQueryItem( "geometry", geometryField() );
}

for ( int i = 0; i < fields().count(); i++ )
{
const QgsField& f = fields()[i];
if ( f.type() == QVariant::Int )
url.addQueryItem( "field", f.name() + ":int" );
else if ( f.type() == QVariant::Double )
url.addQueryItem( "field", f.name() + ":real" );
else if ( f.type() == QVariant::String )
url.addQueryItem( "field", f.name() + ":text" );
}

return url;
}

QString QgsVirtualLayerDefinition::toString() const
{
return QString( toUrl().toEncoded() );
}

void QgsVirtualLayerDefinition::addSource( const QString& name, const QString ref )
{
mSourceLayers.append( SourceLayer( name, ref ) );
}

void QgsVirtualLayerDefinition::addSource( const QString& name, const QString source, const QString& provider, const QString& encoding )
{
mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
}

bool QgsVirtualLayerDefinition::hasSourceLayer( QString name ) const
{
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
{
if ( l.name() == name )
{
return true;
}
}
return false;
}

bool QgsVirtualLayerDefinition::hasReferencedLayers() const
{
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
{
if ( l.isReferenced() )
{
return true;
}
}
return false;
}

0 comments on commit e60712e

Please sign in to comment.