Skip to content

Commit 12183e9

Browse files
PeterPetrikwonder-sk
authored andcommittedMay 3, 2018
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
1 parent 5c93666 commit 12183e9

15 files changed

+513
-289
lines changed
 

‎python/core/mesh/qgsmeshlayer.sip.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ QgsMeshLayer cannot be copied.
100100

101101
virtual bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const;
102102

103+
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
104+
105+
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
106+
107+
virtual bool readXml( const QDomNode &layer_node, QgsReadWriteContext &context );
108+
109+
virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const;
110+
103111

104112
QString providerType() const;
105113
%Docstring

‎python/core/qgsmaplayer.sip.in

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,35 @@ project files.
13761376
%Docstring
13771377
Called by writeLayerXML(), used by children to write state specific to them to
13781378
project files.
1379+
%End
1380+
1381+
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
1382+
%Docstring
1383+
Called by writeLayerXML(), used by derived classes to encode provider's specific data
1384+
source to project files. Typically resolving absolute or relative paths, usernames and
1385+
passwords or drivers prefixes ("HDF5:")
1386+
1387+
:param source: data source to encode, typically :py:func:`QgsMapLayer.source()`
1388+
:param context: writing context (e.g. for conversion between relative and absolute paths)
1389+
1390+
:return: encoded source, typically to be written in the dom element "datasource"
1391+
1392+
.. versionadded:: 3.2
1393+
%End
1394+
1395+
virtual QString decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const;
1396+
%Docstring
1397+
Called by readLayerXML(), used by derived classes to decode provider's specific data
1398+
source from project files. Typically resolving absolute or relative paths, usernames and
1399+
passwords or drivers prefixes ("HDF5:")
1400+
1401+
:param source: data source to decode, typically read from layer's dom element "datasource"
1402+
:param dataProvider: string identification of data provider (e.g. "ogr"), typically read from layer's dom element
1403+
:param context: reading context (e.g. for conversion between relative and absolute paths)
1404+
1405+
:return: decoded source, typically to be used as the layer's datasource
1406+
1407+
.. versionadded:: 3.2
13791408
%End
13801409

13811410
void readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith = QString() );

‎python/core/qgsvectorlayer.sip.in

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,11 @@ Write vector layer specific state to project file Dom node.
748748
Called by :py:func:`QgsMapLayer.writeXml()`
749749
%End
750750

751+
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
752+
753+
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
754+
755+
751756
virtual void resolveReferences( QgsProject *project );
752757

753758
%Docstring

‎python/core/raster/qgsrasterlayer.sip.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ In a world file, this is normally the first row (without the sign).
347347

348348
virtual bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const;
349349

350+
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
351+
352+
virtual QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const;
350353

351354
};
352355

‎src/core/mesh/qgsmeshlayer.cpp

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsmeshlayer.h"
2626
#include "qgsmeshlayerrenderer.h"
2727
#include "qgsproviderregistry.h"
28+
#include "qgsreadwritecontext.h"
2829
#include "qgstriangularmesh.h"
2930

3031
QgsMeshLayer::QgsMeshLayer( const QString &meshLayerPath,
@@ -180,9 +181,84 @@ bool QgsMeshLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString &e
180181
return true;
181182
}
182183

184+
QString QgsMeshLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
185+
{
186+
QString src( source );
187+
if ( provider == QLatin1String( "mdal" ) )
188+
{
189+
src = context.pathResolver().readPath( src );
190+
}
191+
return src;
192+
}
193+
194+
QString QgsMeshLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
195+
{
196+
QString src( source );
197+
if ( providerType() == QLatin1String( "mdal" ) )
198+
{
199+
src = context.pathResolver().writePath( src );
200+
}
201+
return src;
202+
}
203+
204+
bool QgsMeshLayer::readXml( const QDomNode &layer_node, QgsReadWriteContext &context )
205+
{
206+
Q_UNUSED( context );
207+
208+
QgsDebugMsgLevel( QStringLiteral( "Datasource in QgsMeshLayer::readXml: %1" ).arg( mDataSource.toLocal8Bit().data() ), 3 );
209+
210+
//process provider key
211+
QDomNode pkeyNode = layer_node.namedItem( QStringLiteral( "provider" ) );
212+
213+
if ( pkeyNode.isNull() )
214+
{
215+
mProviderKey.clear();
216+
}
217+
else
218+
{
219+
QDomElement pkeyElt = pkeyNode.toElement();
220+
mProviderKey = pkeyElt.text();
221+
}
222+
223+
if ( !setDataProvider( mProviderKey ) )
224+
{
225+
return false;
226+
}
227+
228+
return mValid; // should be true if read successfully
229+
}
230+
231+
bool QgsMeshLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const
232+
{
233+
// first get the layer element so that we can append the type attribute
234+
QDomElement mapLayerNode = layer_node.toElement();
235+
236+
if ( mapLayerNode.isNull() || ( "maplayer" != mapLayerNode.nodeName() ) )
237+
{
238+
QgsDebugMsgLevel( QStringLiteral( "can't find <maplayer>" ), 2 );
239+
return false;
240+
}
241+
242+
mapLayerNode.setAttribute( QStringLiteral( "type" ), QStringLiteral( "mesh" ) );
243+
244+
// add provider node
245+
if ( mDataProvider )
246+
{
247+
QDomElement provider = document.createElement( QStringLiteral( "provider" ) );
248+
QDomText providerText = document.createTextNode( providerType() );
249+
provider.appendChild( providerText );
250+
layer_node.appendChild( provider );
251+
}
252+
253+
// renderer specific settings
254+
QString errorMsg;
255+
return writeSymbology( layer_node, document, errorMsg, context );
256+
}
257+
183258
bool QgsMeshLayer::setDataProvider( QString const &provider )
184259
{
185-
Q_ASSERT( !mDataProvider ); //called from ctor
260+
if ( mDataProvider )
261+
delete mDataProvider;
186262

187263
mProviderKey = provider;
188264
QString dataSource = mDataSource;

‎src/core/mesh/qgsmeshlayer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ class CORE_EXPORT QgsMeshLayer : public QgsMapLayer
114114
virtual QgsMapLayerRenderer *createMapRenderer( QgsRenderContext &rendererContext ) override SIP_FACTORY;
115115
bool readSymbology( const QDomNode &node, QString &errorMessage, QgsReadWriteContext &context ) override;
116116
bool writeSymbology( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override;
117+
QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override;
118+
QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override;
119+
bool readXml( const QDomNode &layer_node, QgsReadWriteContext &context ) override;
120+
bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
117121

118122
//! Return the provider type for this layer
119123
QString providerType() const;

‎src/core/qgsmaplayer.cpp

Lines changed: 17 additions & 285 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#include "qgsmaplayer.h"
4242
#include "qgsmaplayerlegend.h"
4343
#include "qgsmaplayerstylemanager.h"
44+
#include "qgsmeshlayer.h"
4445
#include "qgspathresolver.h"
4546
#include "qgsprojectfiletransform.h"
4647
#include "qgsproject.h"
@@ -228,182 +229,7 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, QgsReadWriteCo
228229
return false;
229230
}
230231

231-
// TODO: this should go to providers
232-
if ( provider == QLatin1String( "spatialite" ) )
233-
{
234-
QgsDataSourceUri uri( mDataSource );
235-
uri.setDatabase( context.pathResolver().readPath( uri.database() ) );
236-
mDataSource = uri.uri();
237-
}
238-
else if ( provider == QLatin1String( "ogr" ) )
239-
{
240-
QStringList theURIParts = mDataSource.split( '|' );
241-
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
242-
mDataSource = theURIParts.join( QStringLiteral( "|" ) );
243-
}
244-
else if ( provider == QLatin1String( "gpx" ) )
245-
{
246-
QStringList theURIParts = mDataSource.split( '?' );
247-
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
248-
mDataSource = theURIParts.join( QStringLiteral( "?" ) );
249-
}
250-
else if ( provider == QLatin1String( "delimitedtext" ) )
251-
{
252-
QUrl urlSource = QUrl::fromEncoded( mDataSource.toLatin1() );
253-
254-
if ( !mDataSource.startsWith( QLatin1String( "file:" ) ) )
255-
{
256-
QUrl file = QUrl::fromLocalFile( mDataSource.left( mDataSource.indexOf( '?' ) ) );
257-
urlSource.setScheme( QStringLiteral( "file" ) );
258-
urlSource.setPath( file.path() );
259-
}
260-
261-
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().readPath( urlSource.toLocalFile() ) );
262-
urlDest.setQueryItems( urlSource.queryItems() );
263-
mDataSource = QString::fromLatin1( urlDest.toEncoded() );
264-
}
265-
else if ( provider == QLatin1String( "wms" ) )
266-
{
267-
// >>> BACKWARD COMPATIBILITY < 1.9
268-
// For project file backward compatibility we must support old format:
269-
// 1. mode: <url>
270-
// example: http://example.org/wms?
271-
// 2. mode: tiled=<width>;<height>;<resolution>;<resolution>...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=<count>,username=<name>,password=<password>,url=<url>
272-
// example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache?
273-
// example: featureCount=10,http://example.org/wms?
274-
// example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms?
275-
// This is modified version of old QgsWmsProvider::parseUri
276-
// The new format has always params crs,format,layers,styles and that params
277-
// should not appear in old format url -> use them to identify version
278-
// XYZ tile layers do not need to contain crs,format params, but they have type=xyz
279-
if ( !mDataSource.contains( QLatin1String( "type=" ) ) &&
280-
!mDataSource.contains( QLatin1String( "crs=" ) ) && !mDataSource.contains( QLatin1String( "format=" ) ) )
281-
{
282-
QgsDebugMsg( "Old WMS URI format detected -> converting to new format" );
283-
QgsDataSourceUri uri;
284-
if ( !mDataSource.startsWith( QLatin1String( "http:" ) ) )
285-
{
286-
QStringList parts = mDataSource.split( ',' );
287-
QStringListIterator iter( parts );
288-
while ( iter.hasNext() )
289-
{
290-
QString item = iter.next();
291-
if ( item.startsWith( QLatin1String( "username=" ) ) )
292-
{
293-
uri.setParam( QStringLiteral( "username" ), item.mid( 9 ) );
294-
}
295-
else if ( item.startsWith( QLatin1String( "password=" ) ) )
296-
{
297-
uri.setParam( QStringLiteral( "password" ), item.mid( 9 ) );
298-
}
299-
else if ( item.startsWith( QLatin1String( "tiled=" ) ) )
300-
{
301-
// in < 1.9 tiled= may apper in to variants:
302-
// tiled=width;height - non tiled mode, specifies max width and max height
303-
// tiled=width;height;resolutions-1;resolution2;... - tile mode
304-
305-
QStringList params = item.mid( 6 ).split( ';' );
306-
307-
if ( params.size() == 2 ) // non tiled mode
308-
{
309-
uri.setParam( QStringLiteral( "maxWidth" ), params.takeFirst() );
310-
uri.setParam( QStringLiteral( "maxHeight" ), params.takeFirst() );
311-
}
312-
else if ( params.size() > 2 ) // tiled mode
313-
{
314-
// resolutions are no more needed and size limit is not used for tiles
315-
// we have to tell to the provider however that it is tiled
316-
uri.setParam( QStringLiteral( "tileMatrixSet" ), QLatin1String( "" ) );
317-
}
318-
}
319-
else if ( item.startsWith( QLatin1String( "featureCount=" ) ) )
320-
{
321-
uri.setParam( QStringLiteral( "featureCount" ), item.mid( 13 ) );
322-
}
323-
else if ( item.startsWith( QLatin1String( "url=" ) ) )
324-
{
325-
uri.setParam( QStringLiteral( "url" ), item.mid( 4 ) );
326-
}
327-
else if ( item.startsWith( QLatin1String( "ignoreUrl=" ) ) )
328-
{
329-
uri.setParam( QStringLiteral( "ignoreUrl" ), item.mid( 10 ).split( ';' ) );
330-
}
331-
}
332-
}
333-
else
334-
{
335-
uri.setParam( QStringLiteral( "url" ), mDataSource );
336-
}
337-
mDataSource = uri.encodedUri();
338-
// At this point, the URI is obviously incomplete, we add additional params
339-
// in QgsRasterLayer::readXml
340-
}
341-
// <<< BACKWARD COMPATIBILITY < 1.9
342-
}
343-
else
344-
{
345-
bool handled = false;
346-
347-
if ( provider == QLatin1String( "gdal" ) )
348-
{
349-
if ( mDataSource.startsWith( QLatin1String( "NETCDF:" ) ) )
350-
{
351-
// NETCDF:filename:variable
352-
// filename can be quoted with " as it can contain colons
353-
QRegExp r( "NETCDF:(.+):([^:]+)" );
354-
if ( r.exactMatch( mDataSource ) )
355-
{
356-
QString filename = r.cap( 1 );
357-
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
358-
filename = filename.mid( 1, filename.length() - 2 );
359-
mDataSource = "NETCDF:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
360-
handled = true;
361-
}
362-
}
363-
else if ( mDataSource.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
364-
{
365-
// HDF4_SDS:subdataset_type:file_name:subdataset_index
366-
// filename can be quoted with " as it can contain colons
367-
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
368-
if ( r.exactMatch( mDataSource ) )
369-
{
370-
QString filename = r.cap( 2 );
371-
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
372-
filename = filename.mid( 1, filename.length() - 2 );
373-
mDataSource = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 3 );
374-
handled = true;
375-
}
376-
}
377-
else if ( mDataSource.startsWith( QLatin1String( "HDF5:" ) ) )
378-
{
379-
// HDF5:file_name:subdataset
380-
// filename can be quoted with " as it can contain colons
381-
QRegExp r( "HDF5:(.+):([^:]+)" );
382-
if ( r.exactMatch( mDataSource ) )
383-
{
384-
QString filename = r.cap( 1 );
385-
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
386-
filename = filename.mid( 1, filename.length() - 2 );
387-
mDataSource = "HDF5:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
388-
handled = true;
389-
}
390-
}
391-
else if ( mDataSource.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
392-
{
393-
// NITF_IM:0:filename
394-
// RADARSAT_2_CALIB:?:filename
395-
QRegExp r( "([^:]+):([^:]+):(.+)" );
396-
if ( r.exactMatch( mDataSource ) )
397-
{
398-
mDataSource = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().readPath( r.cap( 3 ) );
399-
handled = true;
400-
}
401-
}
402-
}
403-
404-
if ( !handled )
405-
mDataSource = context.pathResolver().readPath( mDataSource );
406-
}
232+
mDataSource = decodedSource( mDataSource, provider, context );
407233

408234
// Set the CRS from project file, asking the user if necessary.
409235
// Make it the saved CRS to have WMS layer projected correctly.
@@ -599,118 +425,11 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume
599425

600426
// data source
601427
QDomElement dataSource = document.createElement( QStringLiteral( "datasource" ) );
602-
603-
QString src = source();
604-
605-
const QgsVectorLayer *vlayer = qobject_cast<const QgsVectorLayer *>( this );
606-
// TODO: what about postgres, mysql and others, they should not go through writePath()
607-
if ( vlayer && vlayer->providerType() == QLatin1String( "spatialite" ) )
608-
{
609-
QgsDataSourceUri uri( src );
610-
QString database = context.pathResolver().writePath( uri.database() );
611-
uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() );
612-
src = uri.uri();
613-
}
614-
else if ( vlayer && vlayer->providerType() == QLatin1String( "ogr" ) )
615-
{
616-
QStringList theURIParts = src.split( '|' );
617-
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
618-
src = theURIParts.join( QStringLiteral( "|" ) );
619-
}
620-
else if ( vlayer && vlayer->providerType() == QLatin1String( "gpx" ) )
621-
{
622-
QStringList theURIParts = src.split( '?' );
623-
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
624-
src = theURIParts.join( QStringLiteral( "?" ) );
625-
}
626-
else if ( vlayer && vlayer->providerType() == QLatin1String( "delimitedtext" ) )
627-
{
628-
QUrl urlSource = QUrl::fromEncoded( src.toLatin1() );
629-
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().writePath( urlSource.toLocalFile() ) );
630-
urlDest.setQueryItems( urlSource.queryItems() );
631-
src = QString::fromLatin1( urlDest.toEncoded() );
632-
}
633-
else if ( vlayer && vlayer->providerType() == QLatin1String( "memory" ) )
634-
{
635-
// Refetch the source from the provider, because adding fields actually changes the source for this provider.
636-
src = vlayer->dataProvider()->dataSourceUri();
637-
}
638-
else
639-
{
640-
bool handled = false;
641-
642-
if ( !vlayer )
643-
{
644-
const QgsRasterLayer *rlayer = qobject_cast<const QgsRasterLayer *>( this );
645-
// Update path for subdataset
646-
if ( rlayer && rlayer->providerType() == QLatin1String( "gdal" ) )
647-
{
648-
if ( src.startsWith( QLatin1String( "NETCDF:" ) ) )
649-
{
650-
// NETCDF:filename:variable
651-
// filename can be quoted with " as it can contain colons
652-
QRegExp r( "NETCDF:(.+):([^:]+)" );
653-
if ( r.exactMatch( src ) )
654-
{
655-
QString filename = r.cap( 1 );
656-
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
657-
filename = filename.mid( 1, filename.length() - 2 );
658-
src = "NETCDF:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
659-
handled = true;
660-
}
661-
}
662-
else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
663-
{
664-
// HDF4_SDS:subdataset_type:file_name:subdataset_index
665-
// filename can be quoted with " as it can contain colons
666-
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
667-
if ( r.exactMatch( src ) )
668-
{
669-
QString filename = r.cap( 2 );
670-
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
671-
filename = filename.mid( 1, filename.length() - 2 );
672-
src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 3 );
673-
handled = true;
674-
}
675-
}
676-
else if ( src.startsWith( QLatin1String( "HDF5:" ) ) )
677-
{
678-
// HDF5:file_name:subdataset
679-
// filename can be quoted with " as it can contain colons
680-
QRegExp r( "HDF5:(.+):([^:]+)" );
681-
if ( r.exactMatch( src ) )
682-
{
683-
QString filename = r.cap( 1 );
684-
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
685-
filename = filename.mid( 1, filename.length() - 2 );
686-
src = "HDF5:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
687-
handled = true;
688-
}
689-
}
690-
else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
691-
{
692-
// NITF_IM:0:filename
693-
// RADARSAT_2_CALIB:?:filename
694-
QRegExp r( "([^:]+):([^:]+):(.+)" );
695-
if ( r.exactMatch( src ) )
696-
{
697-
src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().writePath( r.cap( 3 ) );
698-
handled = true;
699-
}
700-
}
701-
}
702-
}
703-
704-
if ( !handled )
705-
src = context.pathResolver().writePath( src );
706-
}
707-
428+
QString src = encodedSource( source(), context );
708429
QDomText dataSourceText = document.createTextNode( src );
709430
dataSource.appendChild( dataSourceText );
710-
711431
layerElement.appendChild( dataSource );
712432

713-
714433
// layer name
715434
QDomElement layerName = document.createElement( QStringLiteral( "layername" ) );
716435
QDomText layerNameText = document.createTextNode( name() );
@@ -846,7 +565,20 @@ bool QgsMapLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const
846565
// NOP by default; children will over-ride with behavior specific to them
847566

848567
return true;
849-
} // void QgsMapLayer::writeXml
568+
}
569+
570+
QString QgsMapLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
571+
{
572+
Q_UNUSED( context );
573+
return source;
574+
}
575+
576+
QString QgsMapLayer::decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const
577+
{
578+
Q_UNUSED( context );
579+
Q_UNUSED( dataProvider );
580+
return source;
581+
}
850582

851583
void QgsMapLayer::resolveReferences( QgsProject *project )
852584
{

‎src/core/qgsmaplayer.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,33 @@ class CORE_EXPORT QgsMapLayer : public QObject
11971197
*/
11981198
virtual bool writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const;
11991199

1200+
/**
1201+
* Called by writeLayerXML(), used by derived classes to encode provider's specific data
1202+
* source to project files. Typically resolving absolute or relative paths, usernames and
1203+
* passwords or drivers prefixes ("HDF5:")
1204+
*
1205+
* \param source data source to encode, typically QgsMapLayer::source()
1206+
* \param context writing context (e.g. for conversion between relative and absolute paths)
1207+
* \return encoded source, typically to be written in the dom element "datasource"
1208+
*
1209+
* \since QGIS 3.2
1210+
*/
1211+
virtual QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const;
1212+
1213+
/**
1214+
* Called by readLayerXML(), used by derived classes to decode provider's specific data
1215+
* source from project files. Typically resolving absolute or relative paths, usernames and
1216+
* passwords or drivers prefixes ("HDF5:")
1217+
*
1218+
* \param source data source to decode, typically read from layer's dom element "datasource"
1219+
* \param dataProvider string identification of data provider (e.g. "ogr"), typically read from layer's dom element
1220+
* \param context reading context (e.g. for conversion between relative and absolute paths)
1221+
* \return decoded source, typically to be used as the layer's datasource
1222+
*
1223+
* \since QGIS 3.2
1224+
*/
1225+
virtual QString decodedSource( const QString &source, const QString &dataProvider, const QgsReadWriteContext &context ) const;
1226+
12001227
/**
12011228
* Read custom properties from project file.
12021229
\param layerNode note to read from

‎src/core/qgsproject.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
#include "qgsprojectbadlayerhandler.h"
4949
#include "qgssettings.h"
5050
#include "qgsmaplayerlistutils.h"
51+
#include "qgsmeshlayer.h"
5152
#include "qgslayoutmanager.h"
5253
#include "qgsmaplayerstore.h"
5354
#include "qgsziputils.h"
@@ -820,6 +821,10 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
820821
{
821822
mapLayer = new QgsRasterLayer;
822823
}
824+
else if ( type == QLatin1String( "mesh" ) )
825+
{
826+
mapLayer = new QgsMeshLayer;
827+
}
823828
else if ( type == QLatin1String( "plugin" ) )
824829
{
825830
QString typeName = layerElem.attribute( QStringLiteral( "name" ) );

‎src/core/qgsvectorlayer.cpp

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,97 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node,
17181718
// renderer specific settings
17191719
QString errorMsg;
17201720
return writeSymbology( layer_node, document, errorMsg, context );
1721-
} // bool QgsVectorLayer::writeXml
1721+
}
1722+
1723+
QString QgsVectorLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
1724+
{
1725+
QString src( source );
1726+
1727+
// TODO: what about postgres, mysql and others, they should not go through writePath()
1728+
if ( providerType() == QLatin1String( "spatialite" ) )
1729+
{
1730+
QgsDataSourceUri uri( src );
1731+
QString database = context.pathResolver().writePath( uri.database() );
1732+
uri.setConnection( uri.host(), uri.port(), database, uri.username(), uri.password() );
1733+
src = uri.uri();
1734+
}
1735+
else if ( providerType() == QLatin1String( "ogr" ) )
1736+
{
1737+
QStringList theURIParts = src.split( '|' );
1738+
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
1739+
src = theURIParts.join( QStringLiteral( "|" ) );
1740+
}
1741+
else if ( providerType() == QLatin1String( "gpx" ) )
1742+
{
1743+
QStringList theURIParts = src.split( '?' );
1744+
theURIParts[0] = context.pathResolver().writePath( theURIParts[0] );
1745+
src = theURIParts.join( QStringLiteral( "?" ) );
1746+
}
1747+
else if ( providerType() == QLatin1String( "delimitedtext" ) )
1748+
{
1749+
QUrl urlSource = QUrl::fromEncoded( src.toLatin1() );
1750+
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().writePath( urlSource.toLocalFile() ) );
1751+
urlDest.setQueryItems( urlSource.queryItems() );
1752+
src = QString::fromLatin1( urlDest.toEncoded() );
1753+
}
1754+
else if ( providerType() == QLatin1String( "memory" ) )
1755+
{
1756+
// Refetch the source from the provider, because adding fields actually changes the source for this provider.
1757+
src = dataProvider()->dataSourceUri();
1758+
}
1759+
else
1760+
{
1761+
src = context.pathResolver().writePath( src );
1762+
}
1763+
1764+
return src;
1765+
}
1766+
1767+
QString QgsVectorLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
1768+
{
1769+
QString src( source );
1770+
1771+
if ( provider == QLatin1String( "spatialite" ) )
1772+
{
1773+
QgsDataSourceUri uri( src );
1774+
uri.setDatabase( context.pathResolver().readPath( uri.database() ) );
1775+
src = uri.uri();
1776+
}
1777+
else if ( provider == QLatin1String( "ogr" ) )
1778+
{
1779+
QStringList theURIParts = src.split( '|' );
1780+
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
1781+
src = theURIParts.join( QStringLiteral( "|" ) );
1782+
}
1783+
else if ( provider == QLatin1String( "gpx" ) )
1784+
{
1785+
QStringList theURIParts = src.split( '?' );
1786+
theURIParts[0] = context.pathResolver().readPath( theURIParts[0] );
1787+
src = theURIParts.join( QStringLiteral( "?" ) );
1788+
}
1789+
else if ( provider == QLatin1String( "delimitedtext" ) )
1790+
{
1791+
QUrl urlSource = QUrl::fromEncoded( src.toLatin1() );
1792+
1793+
if ( !src.startsWith( QLatin1String( "file:" ) ) )
1794+
{
1795+
QUrl file = QUrl::fromLocalFile( src.left( src.indexOf( '?' ) ) );
1796+
urlSource.setScheme( QStringLiteral( "file" ) );
1797+
urlSource.setPath( file.path() );
1798+
}
1799+
1800+
QUrl urlDest = QUrl::fromLocalFile( context.pathResolver().readPath( urlSource.toLocalFile() ) );
1801+
urlDest.setQueryItems( urlSource.queryItems() );
1802+
src = QString::fromLatin1( urlDest.toEncoded() );
1803+
}
1804+
else
1805+
{
1806+
src = context.pathResolver().readPath( src );
1807+
}
1808+
1809+
return src;
1810+
}
1811+
17221812

17231813

17241814
void QgsVectorLayer::resolveReferences( QgsProject *project )

‎src/core/qgsvectorlayer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
764764
*/
765765
bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
766766

767+
QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override;
768+
QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override;
769+
767770
/**
768771
* Resolve references to other layers (kept as layer IDs after reading XML) into layer objects.
769772
* \since QGIS 3.0

‎src/core/raster/qgsrasterlayer.cpp

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ email : tim at linfiniti.com
2929
#include "qgsmultibandcolorrenderer.h"
3030
#include "qgspainting.h"
3131
#include "qgspalettedrasterrenderer.h"
32+
#include "qgspathresolver.h"
3233
#include "qgsprojectfiletransform.h"
3334
#include "qgsproviderregistry.h"
3435
#include "qgsrasterdataprovider.h"
@@ -41,6 +42,7 @@ email : tim at linfiniti.com
4142
#include "qgsrasterrendererregistry.h"
4243
#include "qgsrasterresamplefilter.h"
4344
#include "qgsrastershader.h"
45+
#include "qgsreadwritecontext.h"
4446
#include "qgsrectangle.h"
4547
#include "qgsrendercontext.h"
4648
#include "qgssinglebandcolordatarenderer.h"
@@ -1621,6 +1623,225 @@ bool QgsRasterLayer::writeXml( QDomNode &layer_node,
16211623
return writeSymbology( layer_node, document, errorMsg, context );
16221624
}
16231625

1626+
QString QgsRasterLayer::encodedSource( const QString &source, const QgsReadWriteContext &context ) const
1627+
{
1628+
QString src( source );
1629+
bool handled = false;
1630+
1631+
// Update path for subdataset
1632+
if ( providerType() == QLatin1String( "gdal" ) )
1633+
{
1634+
if ( src.startsWith( QLatin1String( "NETCDF:" ) ) )
1635+
{
1636+
// NETCDF:filename:variable
1637+
// filename can be quoted with " as it can contain colons
1638+
QRegExp r( "NETCDF:(.+):([^:]+)" );
1639+
if ( r.exactMatch( src ) )
1640+
{
1641+
QString filename = r.cap( 1 );
1642+
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
1643+
filename = filename.mid( 1, filename.length() - 2 );
1644+
src = "NETCDF:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
1645+
handled = true;
1646+
}
1647+
}
1648+
else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
1649+
{
1650+
// HDF4_SDS:subdataset_type:file_name:subdataset_index
1651+
// filename can be quoted with " as it can contain colons
1652+
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
1653+
if ( r.exactMatch( src ) )
1654+
{
1655+
QString filename = r.cap( 2 );
1656+
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
1657+
filename = filename.mid( 1, filename.length() - 2 );
1658+
src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 3 );
1659+
handled = true;
1660+
}
1661+
}
1662+
else if ( src.startsWith( QLatin1String( "HDF5:" ) ) )
1663+
{
1664+
// HDF5:file_name:subdataset
1665+
// filename can be quoted with " as it can contain colons
1666+
QRegExp r( "HDF5:(.+):([^:]+)" );
1667+
if ( r.exactMatch( src ) )
1668+
{
1669+
QString filename = r.cap( 1 );
1670+
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
1671+
filename = filename.mid( 1, filename.length() - 2 );
1672+
src = "HDF5:\"" + context.pathResolver().writePath( filename ) + "\":" + r.cap( 2 );
1673+
handled = true;
1674+
}
1675+
}
1676+
else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
1677+
{
1678+
// NITF_IM:0:filename
1679+
// RADARSAT_2_CALIB:?:filename
1680+
QRegExp r( "([^:]+):([^:]+):(.+)" );
1681+
if ( r.exactMatch( src ) )
1682+
{
1683+
src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().writePath( r.cap( 3 ) );
1684+
handled = true;
1685+
}
1686+
}
1687+
}
1688+
1689+
if ( !handled )
1690+
src = context.pathResolver().writePath( src );
1691+
1692+
return src;
1693+
}
1694+
1695+
QString QgsRasterLayer::decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const
1696+
{
1697+
QString src( source );
1698+
1699+
if ( provider == QLatin1String( "wms" ) )
1700+
{
1701+
// >>> BACKWARD COMPATIBILITY < 1.9
1702+
// For project file backward compatibility we must support old format:
1703+
// 1. mode: <url>
1704+
// example: http://example.org/wms?
1705+
// 2. mode: tiled=<width>;<height>;<resolution>;<resolution>...,ignoreUrl=GetMap;GetFeatureInfo,featureCount=<count>,username=<name>,password=<password>,url=<url>
1706+
// example: tiled=256;256;0.703;0.351,url=http://example.org/tilecache?
1707+
// example: featureCount=10,http://example.org/wms?
1708+
// example: ignoreUrl=GetMap;GetFeatureInfo,username=cimrman,password=jara,url=http://example.org/wms?
1709+
// This is modified version of old QgsWmsProvider::parseUri
1710+
// The new format has always params crs,format,layers,styles and that params
1711+
// should not appear in old format url -> use them to identify version
1712+
// XYZ tile layers do not need to contain crs,format params, but they have type=xyz
1713+
if ( !src.contains( QLatin1String( "type=" ) ) &&
1714+
!src.contains( QLatin1String( "crs=" ) ) && !src.contains( QLatin1String( "format=" ) ) )
1715+
{
1716+
QgsDebugMsg( "Old WMS URI format detected -> converting to new format" );
1717+
QgsDataSourceUri uri;
1718+
if ( !src.startsWith( QLatin1String( "http:" ) ) )
1719+
{
1720+
QStringList parts = src.split( ',' );
1721+
QStringListIterator iter( parts );
1722+
while ( iter.hasNext() )
1723+
{
1724+
QString item = iter.next();
1725+
if ( item.startsWith( QLatin1String( "username=" ) ) )
1726+
{
1727+
uri.setParam( QStringLiteral( "username" ), item.mid( 9 ) );
1728+
}
1729+
else if ( item.startsWith( QLatin1String( "password=" ) ) )
1730+
{
1731+
uri.setParam( QStringLiteral( "password" ), item.mid( 9 ) );
1732+
}
1733+
else if ( item.startsWith( QLatin1String( "tiled=" ) ) )
1734+
{
1735+
// in < 1.9 tiled= may apper in to variants:
1736+
// tiled=width;height - non tiled mode, specifies max width and max height
1737+
// tiled=width;height;resolutions-1;resolution2;... - tile mode
1738+
1739+
QStringList params = item.mid( 6 ).split( ';' );
1740+
1741+
if ( params.size() == 2 ) // non tiled mode
1742+
{
1743+
uri.setParam( QStringLiteral( "maxWidth" ), params.takeFirst() );
1744+
uri.setParam( QStringLiteral( "maxHeight" ), params.takeFirst() );
1745+
}
1746+
else if ( params.size() > 2 ) // tiled mode
1747+
{
1748+
// resolutions are no more needed and size limit is not used for tiles
1749+
// we have to tell to the provider however that it is tiled
1750+
uri.setParam( QStringLiteral( "tileMatrixSet" ), QLatin1String( "" ) );
1751+
}
1752+
}
1753+
else if ( item.startsWith( QLatin1String( "featureCount=" ) ) )
1754+
{
1755+
uri.setParam( QStringLiteral( "featureCount" ), item.mid( 13 ) );
1756+
}
1757+
else if ( item.startsWith( QLatin1String( "url=" ) ) )
1758+
{
1759+
uri.setParam( QStringLiteral( "url" ), item.mid( 4 ) );
1760+
}
1761+
else if ( item.startsWith( QLatin1String( "ignoreUrl=" ) ) )
1762+
{
1763+
uri.setParam( QStringLiteral( "ignoreUrl" ), item.mid( 10 ).split( ';' ) );
1764+
}
1765+
}
1766+
}
1767+
else
1768+
{
1769+
uri.setParam( QStringLiteral( "url" ), src );
1770+
}
1771+
src = uri.encodedUri();
1772+
// At this point, the URI is obviously incomplete, we add additional params
1773+
// in QgsRasterLayer::readXml
1774+
}
1775+
// <<< BACKWARD COMPATIBILITY < 1.9
1776+
}
1777+
else
1778+
{
1779+
bool handled = false;
1780+
1781+
if ( provider == QLatin1String( "gdal" ) )
1782+
{
1783+
if ( src.startsWith( QLatin1String( "NETCDF:" ) ) )
1784+
{
1785+
// NETCDF:filename:variable
1786+
// filename can be quoted with " as it can contain colons
1787+
QRegExp r( "NETCDF:(.+):([^:]+)" );
1788+
if ( r.exactMatch( src ) )
1789+
{
1790+
QString filename = r.cap( 1 );
1791+
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
1792+
filename = filename.mid( 1, filename.length() - 2 );
1793+
src = "NETCDF:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
1794+
handled = true;
1795+
}
1796+
}
1797+
else if ( src.startsWith( QLatin1String( "HDF4_SDS:" ) ) )
1798+
{
1799+
// HDF4_SDS:subdataset_type:file_name:subdataset_index
1800+
// filename can be quoted with " as it can contain colons
1801+
QRegExp r( "HDF4_SDS:([^:]+):(.+):([^:]+)" );
1802+
if ( r.exactMatch( src ) )
1803+
{
1804+
QString filename = r.cap( 2 );
1805+
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
1806+
filename = filename.mid( 1, filename.length() - 2 );
1807+
src = "HDF4_SDS:" + r.cap( 1 ) + ":\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 3 );
1808+
handled = true;
1809+
}
1810+
}
1811+
else if ( src.startsWith( QLatin1String( "HDF5:" ) ) )
1812+
{
1813+
// HDF5:file_name:subdataset
1814+
// filename can be quoted with " as it can contain colons
1815+
QRegExp r( "HDF5:(.+):([^:]+)" );
1816+
if ( r.exactMatch( src ) )
1817+
{
1818+
QString filename = r.cap( 1 );
1819+
if ( filename.startsWith( '"' ) && filename.endsWith( '"' ) )
1820+
filename = filename.mid( 1, filename.length() - 2 );
1821+
src = "HDF5:\"" + context.pathResolver().readPath( filename ) + "\":" + r.cap( 2 );
1822+
handled = true;
1823+
}
1824+
}
1825+
else if ( src.contains( QRegExp( "^(NITF_IM|RADARSAT_2_CALIB):" ) ) )
1826+
{
1827+
// NITF_IM:0:filename
1828+
// RADARSAT_2_CALIB:?:filename
1829+
QRegExp r( "([^:]+):([^:]+):(.+)" );
1830+
if ( r.exactMatch( src ) )
1831+
{
1832+
src = r.cap( 1 ) + ':' + r.cap( 2 ) + ':' + context.pathResolver().readPath( r.cap( 3 ) );
1833+
handled = true;
1834+
}
1835+
}
1836+
}
1837+
1838+
if ( !handled )
1839+
src = context.pathResolver().readPath( src );
1840+
}
1841+
1842+
return src;
1843+
}
1844+
16241845
int QgsRasterLayer::width() const
16251846
{
16261847
if ( !mDataProvider ) return 0;

‎src/core/raster/qgsrasterlayer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,8 @@ class CORE_EXPORT QgsRasterLayer : public QgsMapLayer
388388
bool writeSymbology( QDomNode &, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override;
389389
bool writeStyle( QDomNode &node, QDomDocument &doc, QString &errorMessage, const QgsReadWriteContext &context ) const override;
390390
bool writeXml( QDomNode &layer_node, QDomDocument &doc, const QgsReadWriteContext &context ) const override;
391-
391+
QString encodedSource( const QString &source, const QgsReadWriteContext &context ) const override;
392+
QString decodedSource( const QString &source, const QString &provider, const QgsReadWriteContext &context ) const override;
392393
private:
393394
//! \brief Initialize default values
394395
void init();

‎src/providers/mdal/qgsmdalprovider.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,3 @@ class QgsMdalProvider : public QgsMeshDataProvider
6060
};
6161

6262
#endif //QGSMDALPROVIDER_H
63-

‎tests/src/core/testqgsmeshlayer.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ class TestQgsMeshLayer : public QObject
4747
void init() {} // will be called before each testfunction is executed.
4848
void cleanup() {} // will be called after every testfunction.
4949

50+
void test_write_read_project();
5051
void test_data_provider();
5152
void test_extent();
5253
};
5354

55+
5456
void TestQgsMeshLayer::initTestCase()
5557
{
5658
// init QGIS's paths - true means that all path will be inited from prefix
@@ -120,5 +122,24 @@ void TestQgsMeshLayer::test_extent()
120122
QCOMPARE( mMdalLayer->dataProvider()->extent(), mMdalLayer->extent() );
121123
}
122124

125+
void TestQgsMeshLayer::test_write_read_project()
126+
{
127+
QgsProject prj;
128+
prj.addMapLayer( mMemoryLayer->clone() );
129+
prj.addMapLayer( mMdalLayer->clone() );
130+
131+
QTemporaryFile f;
132+
QVERIFY( f.open() );
133+
f.close();
134+
prj.setFileName( f.fileName() );
135+
prj.write();
136+
137+
QgsProject prj2;
138+
prj2.setFileName( f.fileName() );
139+
QVERIFY( prj2.read() );
140+
QVector<QgsMapLayer *> layers = prj2.layers<QgsMapLayer *>();
141+
QVERIFY( layers.size() == 2 );
142+
}
143+
123144
QGSTEST_MAIN( TestQgsMeshLayer )
124145
#include "testqgsmeshlayer.moc"

0 commit comments

Comments
 (0)
Please sign in to comment.