Fix reading/writing of mesh layer to a qgis project (fixes #18801) (PR #6869)

* fix guard header

* [bugfix] Fix reading/writing of mesh layer to a qgis project #18801

* fix copy-paste error

* fix copy-paste error

* extract decode source to derived classes

* remove raster providers from vector decode source

* reset testdata to master
This commit is contained in:
Peter Petrik 2018-05-03 12:10:54 +02:00 committed by Martin Dobias
parent 5c9366605a
commit 12183e98db
15 changed files with 513 additions and 289 deletions

View File

@ -100,6 +100,14 @@ QgsMeshLayer cannot be copied.
virtual bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const;
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
virtual bool readXml( const QDomNode &layer_node, QgsReadWriteContext &context );
virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const;
QString providerType() const;
%Docstring

View File

@ -1376,6 +1376,35 @@ project files.
%Docstring
Called by writeLayerXML(), used by children to write state specific to them to
project files.
%End
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
%Docstring
Called by writeLayerXML(), used by derived classes to encode provider's specific data
source to project files. Typically resolving absolute or relative paths, usernames and
passwords or drivers prefixes ("HDF5:")
:param source: data source to encode, typically :py:func:`QgsMapLayer.source()`
:param context: writing context (e.g. for conversion between relative and absolute paths)
:return: encoded source, typically to be written in the dom element "datasource"
.. versionadded:: 3.2
%End
virtual QString decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const;
%Docstring
Called by readLayerXML(), used by derived classes to decode provider's specific data
source from project files. Typically resolving absolute or relative paths, usernames and
passwords or drivers prefixes ("HDF5:")
:param source: data source to decode, typically read from layer's dom element "datasource"
:param dataProvider: string identification of data provider (e.g. "ogr"), typically read from layer's dom element
:param context: reading context (e.g. for conversion between relative and absolute paths)
:return: decoded source, typically to be used as the layer's datasource
.. versionadded:: 3.2
%End
void readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith = QString() );

View File

@ -748,6 +748,11 @@ Write vector layer specific state to project file Dom node.
Called by :py:func:`QgsMapLayer.writeXml()`
%End
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
virtual void resolveReferences( QgsProject *project );
%Docstring

View File

@ -347,6 +347,9 @@ In a world file, this is normally the first row (without the sign).
virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const;
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
};

View File

@ -25,6 +25,7 @@
#include "qgsmeshlayer.h"
#include "qgsmeshlayerrenderer.h"
#include "qgsproviderregistry.h"
#include "qgsreadwritecontext.h"
#include "qgstriangularmesh.h"
QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath,
@ -180,9 +181,84 @@ bool QgsMeshLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &e
return true;
}
QString QgsMeshLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
{
QString src( source );
if ( provider == QLatin1String( "mdal" ) )
{
src = context.pathResolver().readPath( src );
}
return src;
}
QString QgsMeshLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
{
QString src( source );
if ( providerType() == QLatin1String( "mdal" ) )
{
src = context.pathResolver().writePath( src );
}
return src;
}
bool QgsMeshLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &context )
{
Q_UNUSED( context );
QgsDebugMsgLevel( QStringLiteral( "Datasource in QgsMeshLayer::readXml: %1" ).arg( mDataSource.toLocal8Bit().data() ), 3 );
//process provider key
QDomNode pkeyNode = layer_node.namedItem( QStringLiteral( "provider" ) );
if ( pkeyNode.isNull() )
{
mProviderKey.clear();
}
else
{
QDomElement pkeyElt = pkeyNode.toElement();
mProviderKey = pkeyElt.text();
}
if ( !setDataProvider( mProviderKey ) )
{
return false;
}
return mValid; // should be true if read successfully
}
bool QgsMeshLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const
{
// first get the layer element so that we can append the type attribute
QDomElement mapLayerNode = layer_node.toElement();
if ( mapLayerNode.isNull() || ( "maplayer" != mapLayerNode.nodeName() ) )
{
QgsDebugMsgLevel( QStringLiteral( "can't find <maplayer>" ), 2 );
return false;
}
mapLayerNode.setAttribute( QStringLiteral( "type" ), QStringLiteral( "mesh" ) );
// add provider node
if ( mDataProvider )
{
QDomElement provider = document.createElement( QStringLiteral( "provider" ) );
QDomText providerText = document.createTextNode( providerType() );
provider.appendChild( providerText );
layer_node.appendChild( provider );
}
// renderer specific settings
QString errorMsg;
return writeSymbology( layer_node, document, errorMsg, context );
}
bool QgsMeshLayer::setDataProvider( QString const &provider )
{
Q_ASSERT( !mDataProvider ); //called from ctor
if ( mDataProvider )
delete mDataProvider;
mProviderKey = provider;
QString dataSource = mDataSource;

View File

@ -114,6 +114,10 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer
virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override SIP_FACTORY;
bool readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ) override;
bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override;
QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override;
QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override;
bool readXml( const QDomNode &layer_node, QgsReadWriteContext &context ) override;
bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
//! Return the provider type for this layer
QString providerType() const;

View File

@ -41,6 +41,7 @@
#include "qgsmaplayer.h"
#include "qgsmaplayerlegend.h"
#include "qgsmaplayerstylemanager.h"
#include "qgsmeshlayer.h"
#include "qgspathresolver.h"
#include "qgsprojectfiletransform.h"
#include "qgsproject.h"
@ -228,182 +229,7 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCo
return false;
}
// TODO: this should go to providers
if ( provider == QLatin1String( "spatialite" ) )
{
QgsDataSourceUri uri( mDataSource );
uri.setDatabase( context.pathResolver().readPath( uri.database() ) );
mDataSource = uri.uri();
}
else if ( provider == QLatin1String( "ogr" ) )
{
QStringList theURIParts = mDataSource.split( '|' );
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
mDataSource = theURIParts.join( QStringLiteral( "|" ) );
}
else if ( provider == QLatin1String( "gpx" ) )
{
QStringList theURIParts = mDataSource.split( '?' );
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
mDataSource = theURIParts.join( QStringLiteral( "?" ) );
}
else if ( provider == QLatin1String( "delimitedtext" ) )
{
QUrl urlSource = QUrl::fromEncoded( mDataSource.toLatin1() );
if ( !mDataSource.startsWith( QLatin1String( "file:" ) ) )
{
QUrl file = QUrl::fromLocalFile( mDataSource.left( mDataSource.indexOf( '?' ) ) );
urlSource.setScheme( QStringLiteral( "file" ) );
urlSource.setPath( file.path() );
}
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().readPath( urlSource.toLocalFile() ) );
urlDest.setQueryItems( urlSource.queryItems() );
mDataSource = QString::fromLatin1( urlDest.toEncoded() );
}
else if ( provider == QLatin1String( "wms" ) )
{
// >>> BACKWARD COMPATIBILITY < 1.9
// For project file backward compatibility we must support old format:
// 1. mode: <url>
// example: http://example.org/wms?
// 2. mode: tiled=<width>;<height>;<resolution>;<resolution>...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=<count>,username=<name>,password=<password>,url=<url>
// example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache?
// example: featureCount=10,http://example.org/wms?
// example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms?
// This is modified version of old QgsWmsProvider::parseUri
// The new format has always params crs,format,layers,styles and that params
// should not appear in old format url -> use them to identify version
// XYZ tile layers do not need to contain crs,format params, but they have type=xyz
if ( !mDataSource.contains( QLatin1String( "type=" ) ) &&
!mDataSource.contains( QLatin1String( "crs=" ) ) && !mDataSource.contains( QLatin1String( "format=" ) ) )
{
QgsDebugMsg( "Old WMS URI format detected -> converting to new format" );
QgsDataSourceUri uri;
if ( !mDataSource.startsWith( QLatin1String( "http:" ) ) )
{
QStringList parts = mDataSource.split( ',' );
QStringListIterator iter( parts );
while ( iter.hasNext() )
{
QString item = iter.next();
if ( item.startsWith( QLatin1String( "username=" ) ) )
{
uri.setParam( QStringLiteral( "username" ), item.mid( 9 ) );
}
else if ( item.startsWith( QLatin1String( "password=" ) ) )
{
uri.setParam( QStringLiteral( "password" ), item.mid( 9 ) );
}
else if ( item.startsWith( QLatin1String( "tiled=" ) ) )
{
// in < 1.9 tiled= may apper in to variants:
// tiled=width;height - non tiled mode, specifies max width and max height
// tiled=width;height;resolutions-1;resolution2;... - tile mode
QStringList params = item.mid( 6 ).split( ';' );
if ( params.size() == 2 ) // non tiled mode
{
uri.setParam( QStringLiteral( "maxWidth" ), params.takeFirst() );
uri.setParam( QStringLiteral( "maxHeight" ), params.takeFirst() );
}
else if ( params.size() > 2 ) // tiled mode
{
// resolutions are no more needed and size limit is not used for tiles
// we have to tell to the provider however that it is tiled
uri.setParam( QStringLiteral( "tileMatrixSet" ), QLatin1String( "" ) );
}
}
else if ( item.startsWith( QLatin1String( "featureCount=" ) ) )
{
uri.setParam( QStringLiteral( "featureCount" ), item.mid( 13 ) );
}
else if ( item.startsWith( QLatin1String( "url=" ) ) )
{
uri.setParam( QStringLiteral( "url" ), item.mid( 4 ) );
}
else if ( item.startsWith( QLatin1String( "ignoreUrl=" ) ) )
{
uri.setParam( QStringLiteral( "ignoreUrl" ), item.mid( 10 ).split( ';' ) );
}
}
}
else
{
uri.setParam( QStringLiteral( "url" ), mDataSource );
}
mDataSource = uri.encodedUri();
// At this point, the URI is obviously incomplete, we add additional params
// in QgsRasterLayer::readXml
}
// <<< BACKWARD COMPATIBILITY < 1.9
}
else
{
bool handled = false;
if ( provider == QLatin1String( "gdal" ) )
{
if ( mDataSource.startsWith( QLatin1String( "NETCDF:" ) ) )
{
// NETCDF:filename:variable
// filename can be quoted with " as it can contain colons
QRegExp r( "NETCDF:(.+):([^:]+)" );
if ( r.exactMatch( mDataSource ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
mDataSource = "NETCDF:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( mDataSource.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
{
// HDF4_SDS:subdataset_type:file_name:subdataset_index
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
if ( r.exactMatch( mDataSource ) )
{
QString filename = r.cap( 2 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
mDataSource = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 3 );
handled = true;
}
}
else if ( mDataSource.startsWith( QLatin1String( "HDF5:" ) ) )
{
// HDF5:file_name:subdataset
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF5:(.+):([^:]+)" );
if ( r.exactMatch( mDataSource ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
mDataSource = "HDF5:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( mDataSource.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
{
// NITF_IM:0:filename
// RADARSAT_2_CALIB:?:filename
QRegExp r( "([^:]+):([^:]+):(.+)" );
if ( r.exactMatch( mDataSource ) )
{
mDataSource = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().readPath( r.cap( 3 ) );
handled = true;
}
}
}
if ( !handled )
mDataSource = context.pathResolver().readPath( mDataSource );
}
mDataSource = decodedSource( mDataSource, provider, context );
// Set the CRS from project file, asking the user if necessary.
// Make it the saved CRS to have WMS layer projected correctly.
@ -599,118 +425,11 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume
// data source
QDomElement dataSource = document.createElement( QStringLiteral( "datasource" ) );
QString src = source();
const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );
// TODO: what about postgres, mysql and others, they should not go through writePath()
if ( vlayer && vlayer->providerType() == QLatin1String( "spatialite" ) )
{
QgsDataSourceUri uri( src );
QString database = context.pathResolver().writePath( uri.database() );
uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() );
src = uri.uri();
}
else if ( vlayer && vlayer->providerType() == QLatin1String( "ogr" ) )
{
QStringList theURIParts = src.split( '|' );
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
src = theURIParts.join( QStringLiteral( "|" ) );
}
else if ( vlayer && vlayer->providerType() == QLatin1String( "gpx" ) )
{
QStringList theURIParts = src.split( '?' );
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
src = theURIParts.join( QStringLiteral( "?" ) );
}
else if ( vlayer && vlayer->providerType() == QLatin1String( "delimitedtext" ) )
{
QUrl urlSource = QUrl::fromEncoded( src.toLatin1() );
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().writePath( urlSource.toLocalFile() ) );
urlDest.setQueryItems( urlSource.queryItems() );
src = QString::fromLatin1( urlDest.toEncoded() );
}
else if ( vlayer && vlayer->providerType() == QLatin1String( "memory" ) )
{
// Refetch the source from the provider, because adding fields actually changes the source for this provider.
src = vlayer->dataProvider()->dataSourceUri();
}
else
{
bool handled = false;
if ( !vlayer )
{
const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( this );
// Update path for subdataset
if ( rlayer && rlayer->providerType() == QLatin1String( "gdal" ) )
{
if ( src.startsWith( QLatin1String( "NETCDF:" ) ) )
{
// NETCDF:filename:variable
// filename can be quoted with " as it can contain colons
QRegExp r( "NETCDF:(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "NETCDF:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
{
// HDF4_SDS:subdataset_type:file_name:subdataset_index
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 2 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 3 );
handled = true;
}
}
else if ( src.startsWith( QLatin1String( "HDF5:" ) ) )
{
// HDF5:file_name:subdataset
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF5:(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "HDF5:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
{
// NITF_IM:0:filename
// RADARSAT_2_CALIB:?:filename
QRegExp r( "([^:]+):([^:]+):(.+)" );
if ( r.exactMatch( src ) )
{
src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().writePath( r.cap( 3 ) );
handled = true;
}
}
}
}
if ( !handled )
src = context.pathResolver().writePath( src );
}
QString src = encodedSource( source(), context );
QDomText dataSourceText = document.createTextNode( src );
dataSource.appendChild( dataSourceText );
layerElement.appendChild( dataSource );
// layer name
QDomElement layerName = document.createElement( QStringLiteral( "layername" ) );
QDomText layerNameText = document.createTextNode( name() );
@ -846,7 +565,20 @@ bool QgsMapLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const
// NOP by default; children will over-ride with behavior specific to them
return true;
} // void QgsMapLayer::writeXml
}
QString QgsMapLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
{
Q_UNUSED( context );
return source;
}
QString QgsMapLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const
{
Q_UNUSED( context );
Q_UNUSED( dataProvider );
return source;
}
void QgsMapLayer::resolveReferences( QgsProject *project )
{

View File

@ -1197,6 +1197,33 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
virtual bool writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const;
/**
* Called by writeLayerXML(), used by derived classes to encode provider's specific data
* source to project files. Typically resolving absolute or relative paths, usernames and
* passwords or drivers prefixes ("HDF5:")
*
* \param source data source to encode, typically QgsMapLayer::source()
* \param context writing context (e.g. for conversion between relative and absolute paths)
* \return encoded source, typically to be written in the dom element "datasource"
*
* \since QGIS 3.2
*/
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
/**
* Called by readLayerXML(), used by derived classes to decode provider's specific data
* source from project files. Typically resolving absolute or relative paths, usernames and
* passwords or drivers prefixes ("HDF5:")
*
* \param source data source to decode, typically read from layer's dom element "datasource"
* \param dataProvider string identification of data provider (e.g. "ogr"), typically read from layer's dom element
* \param context reading context (e.g. for conversion between relative and absolute paths)
* \return decoded source, typically to be used as the layer's datasource
*
* \since QGIS 3.2
*/
virtual QString decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const;
/**
* Read custom properties from project file.
\param layerNode note to read from

View File

@ -48,6 +48,7 @@
#include "qgsprojectbadlayerhandler.h"
#include "qgssettings.h"
#include "qgsmaplayerlistutils.h"
#include "qgsmeshlayer.h"
#include "qgslayoutmanager.h"
#include "qgsmaplayerstore.h"
#include "qgsziputils.h"
@ -820,6 +821,10 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
{
mapLayer = new QgsRasterLayer;
}
else if ( type == QLatin1String( "mesh" ) )
{
mapLayer = new QgsMeshLayer;
}
else if ( type == QLatin1String( "plugin" ) )
{
QString typeName = layerElem.attribute( QStringLiteral( "name" ) );

View File

@ -1718,7 +1718,97 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node,
// renderer specific settings
QString errorMsg;
return writeSymbology( layer_node, document, errorMsg, context );
} // bool QgsVectorLayer::writeXml
}
QString QgsVectorLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
{
QString src( source );
// TODO: what about postgres, mysql and others, they should not go through writePath()
if ( providerType() == QLatin1String( "spatialite" ) )
{
QgsDataSourceUri uri( src );
QString database = context.pathResolver().writePath( uri.database() );
uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() );
src = uri.uri();
}
else if ( providerType() == QLatin1String( "ogr" ) )
{
QStringList theURIParts = src.split( '|' );
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
src = theURIParts.join( QStringLiteral( "|" ) );
}
else if ( providerType() == QLatin1String( "gpx" ) )
{
QStringList theURIParts = src.split( '?' );
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
src = theURIParts.join( QStringLiteral( "?" ) );
}
else if ( providerType() == QLatin1String( "delimitedtext" ) )
{
QUrl urlSource = QUrl::fromEncoded( src.toLatin1() );
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().writePath( urlSource.toLocalFile() ) );
urlDest.setQueryItems( urlSource.queryItems() );
src = QString::fromLatin1( urlDest.toEncoded() );
}
else if ( providerType() == QLatin1String( "memory" ) )
{
// Refetch the source from the provider, because adding fields actually changes the source for this provider.
src = dataProvider()->dataSourceUri();
}
else
{
src = context.pathResolver().writePath( src );
}
return src;
}
QString QgsVectorLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
{
QString src( source );
if ( provider == QLatin1String( "spatialite" ) )
{
QgsDataSourceUri uri( src );
uri.setDatabase( context.pathResolver().readPath( uri.database() ) );
src = uri.uri();
}
else if ( provider == QLatin1String( "ogr" ) )
{
QStringList theURIParts = src.split( '|' );
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
src = theURIParts.join( QStringLiteral( "|" ) );
}
else if ( provider == QLatin1String( "gpx" ) )
{
QStringList theURIParts = src.split( '?' );
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
src = theURIParts.join( QStringLiteral( "?" ) );
}
else if ( provider == QLatin1String( "delimitedtext" ) )
{
QUrl urlSource = QUrl::fromEncoded( src.toLatin1() );
if ( !src.startsWith( QLatin1String( "file:" ) ) )
{
QUrl file = QUrl::fromLocalFile( src.left( src.indexOf( '?' ) ) );
urlSource.setScheme( QStringLiteral( "file" ) );
urlSource.setPath( file.path() );
}
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().readPath( urlSource.toLocalFile() ) );
urlDest.setQueryItems( urlSource.queryItems() );
src = QString::fromLatin1( urlDest.toEncoded() );
}
else
{
src = context.pathResolver().readPath( src );
}
return src;
}
void QgsVectorLayer::resolveReferences( QgsProject *project )

View File

@ -764,6 +764,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
*/
bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override;
QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override;
/**
* Resolve references to other layers (kept as layer IDs after reading XML) into layer objects.
* \since QGIS 3.0

View File

@ -29,6 +29,7 @@ email : tim at linfiniti.com
#include "qgsmultibandcolorrenderer.h"
#include "qgspainting.h"
#include "qgspalettedrasterrenderer.h"
#include "qgspathresolver.h"
#include "qgsprojectfiletransform.h"
#include "qgsproviderregistry.h"
#include "qgsrasterdataprovider.h"
@ -41,6 +42,7 @@ email : tim at linfiniti.com
#include "qgsrasterrendererregistry.h"
#include "qgsrasterresamplefilter.h"
#include "qgsrastershader.h"
#include "qgsreadwritecontext.h"
#include "qgsrectangle.h"
#include "qgsrendercontext.h"
#include "qgssinglebandcolordatarenderer.h"
@ -1621,6 +1623,225 @@ bool QgsRasterLayer::writeXml( QDomNode &layer_node,
return writeSymbology( layer_node, document, errorMsg, context );
}
QString QgsRasterLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
{
QString src( source );
bool handled = false;
// Update path for subdataset
if ( providerType() == QLatin1String( "gdal" ) )
{
if ( src.startsWith( QLatin1String( "NETCDF:" ) ) )
{
// NETCDF:filename:variable
// filename can be quoted with " as it can contain colons
QRegExp r( "NETCDF:(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "NETCDF:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
{
// HDF4_SDS:subdataset_type:file_name:subdataset_index
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 2 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 3 );
handled = true;
}
}
else if ( src.startsWith( QLatin1String( "HDF5:" ) ) )
{
// HDF5:file_name:subdataset
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF5:(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "HDF5:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
{
// NITF_IM:0:filename
// RADARSAT_2_CALIB:?:filename
QRegExp r( "([^:]+):([^:]+):(.+)" );
if ( r.exactMatch( src ) )
{
src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().writePath( r.cap( 3 ) );
handled = true;
}
}
}
if ( !handled )
src = context.pathResolver().writePath( src );
return src;
}
QString QgsRasterLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
{
QString src( source );
if ( provider == QLatin1String( "wms" ) )
{
// >>> BACKWARD COMPATIBILITY < 1.9
// For project file backward compatibility we must support old format:
// 1. mode: <url>
// example: http://example.org/wms?
// 2. mode: tiled=<width>;<height>;<resolution>;<resolution>...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=<count>,username=<name>,password=<password>,url=<url>
// example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache?
// example: featureCount=10,http://example.org/wms?
// example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms?
// This is modified version of old QgsWmsProvider::parseUri
// The new format has always params crs,format,layers,styles and that params
// should not appear in old format url -> use them to identify version
// XYZ tile layers do not need to contain crs,format params, but they have type=xyz
if ( !src.contains( QLatin1String( "type=" ) ) &&
!src.contains( QLatin1String( "crs=" ) ) && !src.contains( QLatin1String( "format=" ) ) )
{
QgsDebugMsg( "Old WMS URI format detected -> converting to new format" );
QgsDataSourceUri uri;
if ( !src.startsWith( QLatin1String( "http:" ) ) )
{
QStringList parts = src.split( ',' );
QStringListIterator iter( parts );
while ( iter.hasNext() )
{
QString item = iter.next();
if ( item.startsWith( QLatin1String( "username=" ) ) )
{
uri.setParam( QStringLiteral( "username" ), item.mid( 9 ) );
}
else if ( item.startsWith( QLatin1String( "password=" ) ) )
{
uri.setParam( QStringLiteral( "password" ), item.mid( 9 ) );
}
else if ( item.startsWith( QLatin1String( "tiled=" ) ) )
{
// in < 1.9 tiled= may apper in to variants:
// tiled=width;height - non tiled mode, specifies max width and max height
// tiled=width;height;resolutions-1;resolution2;... - tile mode
QStringList params = item.mid( 6 ).split( ';' );
if ( params.size() == 2 ) // non tiled mode
{
uri.setParam( QStringLiteral( "maxWidth" ), params.takeFirst() );
uri.setParam( QStringLiteral( "maxHeight" ), params.takeFirst() );
}
else if ( params.size() > 2 ) // tiled mode
{
// resolutions are no more needed and size limit is not used for tiles
// we have to tell to the provider however that it is tiled
uri.setParam( QStringLiteral( "tileMatrixSet" ), QLatin1String( "" ) );
}
}
else if ( item.startsWith( QLatin1String( "featureCount=" ) ) )
{
uri.setParam( QStringLiteral( "featureCount" ), item.mid( 13 ) );
}
else if ( item.startsWith( QLatin1String( "url=" ) ) )
{
uri.setParam( QStringLiteral( "url" ), item.mid( 4 ) );
}
else if ( item.startsWith( QLatin1String( "ignoreUrl=" ) ) )
{
uri.setParam( QStringLiteral( "ignoreUrl" ), item.mid( 10 ).split( ';' ) );
}
}
}
else
{
uri.setParam( QStringLiteral( "url" ), src );
}
src = uri.encodedUri();
// At this point, the URI is obviously incomplete, we add additional params
// in QgsRasterLayer::readXml
}
// <<< BACKWARD COMPATIBILITY < 1.9
}
else
{
bool handled = false;
if ( provider == QLatin1String( "gdal" ) )
{
if ( src.startsWith( QLatin1String( "NETCDF:" ) ) )
{
// NETCDF:filename:variable
// filename can be quoted with " as it can contain colons
QRegExp r( "NETCDF:(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "NETCDF:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
{
// HDF4_SDS:subdataset_type:file_name:subdataset_index
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 2 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 3 );
handled = true;
}
}
else if ( src.startsWith( QLatin1String( "HDF5:" ) ) )
{
// HDF5:file_name:subdataset
// filename can be quoted with " as it can contain colons
QRegExp r( "HDF5:(.+):([^:]+)" );
if ( r.exactMatch( src ) )
{
QString filename = r.cap( 1 );
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
filename = filename.mid( 1, filename.length() - 2 );
src = "HDF5:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
handled = true;
}
}
else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
{
// NITF_IM:0:filename
// RADARSAT_2_CALIB:?:filename
QRegExp r( "([^:]+):([^:]+):(.+)" );
if ( r.exactMatch( src ) )
{
src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().readPath( r.cap( 3 ) );
handled = true;
}
}
}
if ( !handled )
src = context.pathResolver().readPath( src );
}
return src;
}
int QgsRasterLayer::width() const
{
if ( !mDataProvider ) return 0;

View File

@ -388,7 +388,8 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer
bool writeSymbology( QDomNode &, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override;
bool writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override;
bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override;
QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override;
private:
//! \brief Initialize default values
void init();

View File

@ -60,4 +60,3 @@ class QgsMdalProvider : public QgsMeshDataProvider
};
#endif //QGSMDALPROVIDER_H

View File

@ -47,10 +47,12 @@ class TestQgsMeshLayer : public QObject
void init() {} // will be called before each testfunction is executed.
void cleanup() {} // will be called after every testfunction.
void test_write_read_project();
void test_data_provider();
void test_extent();
};
void TestQgsMeshLayer::initTestCase()
{
// init QGIS's paths - true means that all path will be inited from prefix
@ -120,5 +122,24 @@ void TestQgsMeshLayer::test_extent()
QCOMPARE( mMdalLayer->dataProvider()->extent(), mMdalLayer->extent() );
}
void TestQgsMeshLayer::test_write_read_project()
{
QgsProject prj;
prj.addMapLayer( mMemoryLayer->clone() );
prj.addMapLayer( mMdalLayer->clone() );
QTemporaryFile f;
QVERIFY( f.open() );
f.close();
prj.setFileName( f.fileName() );
prj.write();
QgsProject prj2;
prj2.setFileName( f.fileName() );
QVERIFY( prj2.read() );
QVector<QgsMapLayer *> layers = prj2.layers<QgsMapLayer *>();
QVERIFY( layers.size() == 2 );
}
QGSTEST_MAIN( TestQgsMeshLayer )
#include "testqgsmeshlayer.moc"