Skip to content

Commit e60712e

Browse files
author
Hugo Mercier
committed
Add a provider for virtual layers
1 parent 13f4081 commit e60712e

24 files changed

+4132
-1
lines changed

python/core/core.sip

+1
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
%Include qgsvectorlayerfeatureiterator.sip
142142
%Include qgsvisibilitypresetcollection.sip
143143
%Include qgslayerdefinition.sip
144+
%Include qgsvirtuallayerdefinition.sip
144145

145146
%Include auth/qgsauthcertutils.sip
146147
%Include auth/qgsauthconfig.sip
+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* Class to manipulate the definition of a virtual layer
3+
*
4+
* It is used to extract parameters from an initial virtual layer definition as well as
5+
* to store the complete, expanded definition once types have been detected.
6+
*/
7+
class QgsVirtualLayerDefinition
8+
{
9+
%TypeHeaderCode
10+
#include <qgsvirtuallayerdefinition.h>
11+
%End
12+
public:
13+
/**
14+
* A SourceLayer is either a reference to a live layer in the registry
15+
* or all the parameters needed to load it (provider key, source, etc.)
16+
*/
17+
class SourceLayer
18+
{
19+
public:
20+
//! Constructor variant to build a live layer reference
21+
SourceLayer( const QString& name, const QString& ref );
22+
//! Constructor variant to build a layer with a provider and a source
23+
SourceLayer( const QString& name, const QString& source, const QString& provider, const QString& encoding );
24+
25+
//! Is it a live layer or not ?
26+
bool isReferenced() const;
27+
28+
//! The reference (id) of the live layer
29+
QString reference() const;
30+
31+
//! Name of the layer
32+
QString name() const;
33+
34+
//! Provider key
35+
QString provider() const;
36+
37+
//! The source url used by the provider to build the layer
38+
QString source() const;
39+
40+
//! Optional encoding for the provider
41+
QString encoding() const;
42+
};
43+
44+
//! Constructor with an optional file path
45+
QgsVirtualLayerDefinition( const QString& filePath = "" );
46+
47+
//! Constructor to build a definition from a QUrl
48+
//! The path part of the URL is extracted as well as the following optional keys:
49+
//! layer_ref=layer_id[:name] represents a live layer referenced by its ID. An optional name can be given
50+
//! layer=provider:source[:name[:encoding]] represents a layer given by its provider key, its source url (URL-encoded).
51+
//! An optional name and encoding can be given
52+
//! geometry=column_name[:type:srid] gives the definition of the geometry column.
53+
//! Type can be either a WKB type code or a string (point, linestring, etc.)
54+
//! srid is an integer
55+
//! uid=column_name is the name of a column with unique integer values.
56+
//! nogeometry is a flag to force the layer to be a non-geometry layer
57+
//! query=sql represents the SQL query. Must be URL-encoded
58+
//! field=column_name:[int|real|text] represents a field with its name and its type
59+
static QgsVirtualLayerDefinition fromUrl( const QUrl& url );
60+
61+
//! Convert the definition into a QUrl
62+
QUrl toUrl() const;
63+
64+
//! Convert into a QString that can be read by the virtual layer provider
65+
QString toString() const;
66+
67+
//! Add a live layer source layer
68+
void addSource( const QString& name, const QString ref );
69+
70+
//! Add a layer with a source, a provider and an encoding
71+
void addSource( const QString& name, const QString source, const QString& provider, const QString& encoding = "" );
72+
73+
//! List of source layers
74+
typedef QList<QgsVirtualLayerDefinition::SourceLayer> SourceLayers;
75+
76+
//! Get access to the source layers
77+
const SourceLayers& sourceLayers() const;
78+
79+
//! Get the SQL query
80+
QString query() const;
81+
//! Set the SQL query
82+
void setQuery( const QString& query );
83+
84+
//! Get the file path. May be empty
85+
QString filePath() const;
86+
//! Set the file path
87+
void setFilePath( const QString& filePath );
88+
89+
//! Get the name of the field with unique identifiers
90+
QString uid() const;
91+
//! Set the name of the field with unique identifiers
92+
void setUid( const QString& uid );
93+
94+
//! Get the name of the geometry field. Empty if no geometry field
95+
QString geometryField() const;
96+
//! Set the name of the geometry field
97+
void setGeometryField( const QString& geometryField );
98+
99+
//! Get the type of the geometry
100+
//! QgsWKBTypes::NoGeometry to hide any geometry
101+
//! QgsWKBTypes::Unknown for unknown types
102+
QgsWKBTypes::Type geometryWkbType() const;
103+
//! Set the type of the geometry
104+
void setGeometryWkbType( QgsWKBTypes::Type t );
105+
106+
//! Get the SRID of the geometry
107+
long geometrySrid() const;
108+
//! Set the SRID of the geometry
109+
void setGeometrySrid( long srid );
110+
111+
//! Get field definitions
112+
const QgsFields& fields() const;
113+
//! Set field definitions
114+
void setFields( const QgsFields& fields );
115+
116+
//! Convenience method to test if a given source layer is part of the definition
117+
bool hasSourceLayer( QString name ) const;
118+
119+
//! Convenience method to test whether the definition has referenced (live) layers
120+
bool hasReferencedLayers() const;
121+
122+
//! Convenient method to test if the geometry is defined (not NoGeometry and not Unknown)
123+
bool hasDefinedGeometry() const;
124+
};
125+

src/core/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ SET(QGIS_CORE_SRCS
202202
qgsvectorlayerundocommand.cpp
203203
qgsvectorsimplifymethod.cpp
204204
qgsvisibilitypresetcollection.cpp
205+
qgsvirtuallayerdefinition.cpp
205206
qgsxmlutils.cpp
206207
qgsslconnect.cpp
207208
qgslocalec.cpp
+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
/***************************************************************************
2+
qgsvirtuallayerdefinition.cpp
3+
begin : December 2015
4+
copyright : (C) 2015 Hugo Mercier, Oslandia
5+
email : hugo dot mercier at oslandia dot com
6+
***************************************************************************/
7+
8+
/***************************************************************************
9+
* *
10+
* This program is free software; you can redistribute it and/or modify *
11+
* it under the terms of the GNU General Public License as published by *
12+
* the Free Software Foundation; either version 2 of the License, or *
13+
* (at your option) any later version. *
14+
* *
15+
***************************************************************************/
16+
17+
#include <QUrl>
18+
#include <QRegExp>
19+
#include <QStringList>
20+
21+
#include "qgsvirtuallayerdefinition.h"
22+
23+
QgsVirtualLayerDefinition::QgsVirtualLayerDefinition( const QString& filePath ) :
24+
mFilePath( filePath ),
25+
mGeometryWkbType( QgsWKBTypes::Unknown ),
26+
mGeometrySrid( 0 )
27+
{
28+
}
29+
30+
QgsVirtualLayerDefinition QgsVirtualLayerDefinition::fromUrl( const QUrl& url )
31+
{
32+
QgsVirtualLayerDefinition def;
33+
34+
def.setFilePath( url.path() );
35+
36+
// regexp for column name
37+
const QString columnNameRx( "[a-zA-Z_\x80-\xFF][a-zA-Z0-9_\x80-\xFF]*" );
38+
39+
QgsFields fields;
40+
41+
int layerIdx = 0;
42+
QList<QPair<QString, QString> > items = url.queryItems();
43+
for ( int i = 0; i < items.size(); i++ )
44+
{
45+
QString key = items.at( i ).first;
46+
QString value = items.at( i ).second;
47+
if ( key == "layer_ref" )
48+
{
49+
layerIdx++;
50+
// layer id, with optional layer_name
51+
int pos = value.indexOf( ':' );
52+
QString layerId, vlayerName;
53+
if ( pos == -1 )
54+
{
55+
layerId = value;
56+
vlayerName = QString( "vtab%1" ).arg( layerIdx );
57+
}
58+
else
59+
{
60+
layerId = value.left( pos );
61+
vlayerName = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
62+
}
63+
// add the layer to the list
64+
def.addSource( vlayerName, layerId );
65+
}
66+
else if ( key == "layer" )
67+
{
68+
layerIdx++;
69+
// syntax: layer=provider:url_encoded_source_URI(:name(:encoding)?)?
70+
int pos = value.indexOf( ':' );
71+
if ( pos != -1 )
72+
{
73+
QString providerKey, source, vlayerName, encoding = "UTF-8";
74+
75+
providerKey = value.left( pos );
76+
int pos2 = value.indexOf( ':', pos + 1 );
77+
if ( pos2 != -1 )
78+
{
79+
source = QUrl::fromPercentEncoding( value.mid( pos + 1, pos2 - pos - 1 ).toUtf8() );
80+
int pos3 = value.indexOf( ':', pos2 + 1 );
81+
if ( pos3 != -1 )
82+
{
83+
vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1, pos3 - pos2 - 1 ).toUtf8() );
84+
encoding = value.mid( pos3 + 1 );
85+
}
86+
else
87+
{
88+
vlayerName = QUrl::fromPercentEncoding( value.mid( pos2 + 1 ).toUtf8() );
89+
}
90+
}
91+
else
92+
{
93+
source = QUrl::fromPercentEncoding( value.mid( pos + 1 ).toUtf8() );
94+
vlayerName = QString( "vtab%1" ).arg( layerIdx );
95+
}
96+
97+
def.addSource( vlayerName, source, providerKey, encoding );
98+
}
99+
}
100+
else if ( key == "geometry" )
101+
{
102+
// geometry field definition, optional
103+
// geometry_column(:wkb_type:srid)?
104+
QRegExp reGeom( "(" + columnNameRx + ")(?::([a-zA-Z0-9]+):(\\d+))?" );
105+
int pos = reGeom.indexIn( value );
106+
if ( pos >= 0 )
107+
{
108+
def.setGeometryField( reGeom.cap( 1 ) );
109+
if ( reGeom.captureCount() > 1 )
110+
{
111+
// not used by the spatialite provider for now ...
112+
QgsWKBTypes::Type wkbType = QgsWKBTypes::parseType( reGeom.cap( 2 ) );
113+
if ( wkbType == QgsWKBTypes::Unknown )
114+
{
115+
wkbType = static_cast<QgsWKBTypes::Type>( reGeom.cap( 2 ).toLong() );
116+
}
117+
def.setGeometryWkbType( wkbType );
118+
def.setGeometrySrid( reGeom.cap( 3 ).toLong() );
119+
}
120+
}
121+
}
122+
else if ( key == "nogeometry" )
123+
{
124+
def.setGeometryWkbType( QgsWKBTypes::NoGeometry );
125+
}
126+
else if ( key == "uid" )
127+
{
128+
def.setUid( value );
129+
}
130+
else if ( key == "query" )
131+
{
132+
// url encoded query
133+
def.setQuery( value );
134+
}
135+
else if ( key == "field" )
136+
{
137+
// field_name:type (int, real, text)
138+
QRegExp reField( "(" + columnNameRx + "):(int|real|text)" );
139+
int pos = reField.indexIn( value );
140+
if ( pos >= 0 )
141+
{
142+
QString fieldName( reField.cap( 1 ) );
143+
QString fieldType( reField.cap( 2 ) );
144+
if ( fieldType == "int" )
145+
{
146+
fields.append( QgsField( fieldName, QVariant::Int, fieldType ) );
147+
}
148+
else if ( fieldType == "real" )
149+
{
150+
fields.append( QgsField( fieldName, QVariant::Double, fieldType ) );
151+
}
152+
if ( fieldType == "text" )
153+
{
154+
fields.append( QgsField( fieldName, QVariant::String, fieldType ) );
155+
}
156+
}
157+
}
158+
}
159+
def.setFields( fields );
160+
161+
return def;
162+
}
163+
164+
QUrl QgsVirtualLayerDefinition::toUrl() const
165+
{
166+
QUrl url;
167+
url.setPath( filePath() );
168+
169+
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
170+
{
171+
if ( l.isReferenced() )
172+
url.addQueryItem( "layer_ref", QString( "%1:%2" ).arg( l.reference() ).arg( l.name() ) );
173+
else
174+
url.addQueryItem( "layer", QString( "%1:%4:%2:%3" ) // the order is important, since the 4th argument may contain '%2' as well
175+
.arg( l.provider() )
176+
.arg( QString( QUrl::toPercentEncoding( l.name() ) ) )
177+
.arg( l.encoding() )
178+
.arg( QString( QUrl::toPercentEncoding( l.source() ) ) ) );
179+
}
180+
181+
if ( !query().isEmpty() )
182+
{
183+
url.addQueryItem( "query", query() );
184+
}
185+
186+
if ( !uid().isEmpty() )
187+
url.addQueryItem( "uid", uid() );
188+
189+
if ( geometryWkbType() == QgsWKBTypes::NoGeometry )
190+
url.addQueryItem( "nogeometry", "" );
191+
else if ( !geometryField().isEmpty() )
192+
{
193+
if ( hasDefinedGeometry() )
194+
url.addQueryItem( "geometry", QString( "%1:%2:%3" ).arg( geometryField() ). arg( geometryWkbType() ).arg( geometrySrid() ).toUtf8() );
195+
else
196+
url.addQueryItem( "geometry", geometryField() );
197+
}
198+
199+
for ( int i = 0; i < fields().count(); i++ )
200+
{
201+
const QgsField& f = fields()[i];
202+
if ( f.type() == QVariant::Int )
203+
url.addQueryItem( "field", f.name() + ":int" );
204+
else if ( f.type() == QVariant::Double )
205+
url.addQueryItem( "field", f.name() + ":real" );
206+
else if ( f.type() == QVariant::String )
207+
url.addQueryItem( "field", f.name() + ":text" );
208+
}
209+
210+
return url;
211+
}
212+
213+
QString QgsVirtualLayerDefinition::toString() const
214+
{
215+
return QString( toUrl().toEncoded() );
216+
}
217+
218+
void QgsVirtualLayerDefinition::addSource( const QString& name, const QString ref )
219+
{
220+
mSourceLayers.append( SourceLayer( name, ref ) );
221+
}
222+
223+
void QgsVirtualLayerDefinition::addSource( const QString& name, const QString source, const QString& provider, const QString& encoding )
224+
{
225+
mSourceLayers.append( SourceLayer( name, source, provider, encoding ) );
226+
}
227+
228+
bool QgsVirtualLayerDefinition::hasSourceLayer( QString name ) const
229+
{
230+
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
231+
{
232+
if ( l.name() == name )
233+
{
234+
return true;
235+
}
236+
}
237+
return false;
238+
}
239+
240+
bool QgsVirtualLayerDefinition::hasReferencedLayers() const
241+
{
242+
foreach ( const QgsVirtualLayerDefinition::SourceLayer& l, sourceLayers() )
243+
{
244+
if ( l.isReferenced() )
245+
{
246+
return true;
247+
}
248+
}
249+
return false;
250+
}

0 commit comments

Comments
 (0)