Skip to content

Commit

Permalink
Add utility function QgsProjectUtils.updateLayerPath to bulk replace
Browse files Browse the repository at this point in the history
a file path with a new path for all matching layers in a project
  • Loading branch information
nyalldawson committed Aug 6, 2021
1 parent a31a674 commit 354d334
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 0 deletions.
8 changes: 8 additions & 0 deletions python/core/auto_generated/project/qgsprojectutils.sip.in
Expand Up @@ -28,6 +28,14 @@ Contains utility functions for working with QGIS projects.
Returns a list of all layers in the specified ``project`` which match the given ``path``.

This method can be used to retrieve a list of layers in a project associated with a file path.
%End

static bool updateLayerPath( QgsProject *project, const QString &oldPath, const QString &newPath );
%Docstring
Updates a ``project``, replacing the data source for all layers which match the given ``oldPath``
with sources which point to ``newPath``.

Returns ``True`` if any layers were updated as a result.
%End

};
Expand Down
17 changes: 17 additions & 0 deletions src/core/project/qgsprojectutils.cpp
Expand Up @@ -35,3 +35,20 @@ QList<QgsMapLayer *> QgsProjectUtils::layersMatchingPath( const QgsProject *proj
}
return layersList;
}

bool QgsProjectUtils::updateLayerPath( QgsProject *project, const QString &oldPath, const QString &newPath )
{
if ( !project )
return false;

bool res = false;
const QMap<QString, QgsMapLayer *> mapLayers( project->mapLayers() );
for ( QgsMapLayer *layer : mapLayers )
{
if ( QgsMapLayerUtils::layerSourceMatchesPath( layer, oldPath ) )
{
res = QgsMapLayerUtils::updateLayerSourcePath( layer, newPath ) || res;
}
}
return res;
}
8 changes: 8 additions & 0 deletions src/core/project/qgsprojectutils.h
Expand Up @@ -43,6 +43,14 @@ class CORE_EXPORT QgsProjectUtils
*/
static QList< QgsMapLayer * > layersMatchingPath( const QgsProject *project, const QString &path );

/**
* Updates a \a project, replacing the data source for all layers which match the given \a oldPath
* with sources which point to \a newPath.
*
* Returns TRUE if any layers were updated as a result.
*/
static bool updateLayerPath( QgsProject *project, const QString &oldPath, const QString &newPath );

};

#endif // QGSPROJECTUTILS_H
Expand Down
57 changes: 57 additions & 0 deletions tests/src/python/test_qgsprojectutils.py
Expand Up @@ -62,6 +62,63 @@ def test_layersMatchingPath(self):
self.assertCountEqual(QgsProjectUtils.layersMatchingPath(p, unitTestDataPath() + '/points.shp'), [layer1])
self.assertCountEqual(QgsProjectUtils.layersMatchingPath(p, unitTestDataPath() + '/mixed_layers.gpkg'), [gpkg1, gpkg2, rl])

def test_updateLayerPath(self):
"""
Test QgsProjectUtils.updateLayerPath
"""
self.assertFalse(QgsProjectUtils.updateLayerPath(None, '', ''))
self.assertFalse(QgsProjectUtils.updateLayerPath(None, 'aaaaa', 'bbbb'))
p = QgsProject()
self.assertFalse(QgsProjectUtils.updateLayerPath(p, 'aaaaa', 'bbbb'))

# add some layers to a project
# shapefile
layer1 = QgsVectorLayer(unitTestDataPath() + '/points.shp', 'l1')
self.assertTrue(layer1.isValid())
p.addMapLayer(layer1)

gpkg1 = QgsVectorLayer(unitTestDataPath() + '/mixed_layers.gpkg|layername=lines', 'l1')
self.assertTrue(gpkg1.isValid())
p.addMapLayer(gpkg1)

gpkg2 = QgsVectorLayer(unitTestDataPath() + '/mixed_layers.gpkg|layername=points', 'l1')
self.assertTrue(gpkg2.isValid())
p.addMapLayer(gpkg2)

# raster layer from gpkg
rl = QgsRasterLayer(f'GPKG:{unitTestDataPath()}/mixed_layers.gpkg:band1')
self.assertTrue(rl.isValid())
p.addMapLayer(rl)

memory_layer = QgsVectorLayer("Point?field=x:string", 'my layer', "memory")
old_memory_source = memory_layer.source()
p.addMapLayer(memory_layer)

self.assertFalse(QgsProjectUtils.updateLayerPath(p, '', ''))
self.assertFalse(QgsProjectUtils.updateLayerPath(p, 'aaa', 'bbb'))

# replace shapefile path
self.assertTrue(QgsProjectUtils.updateLayerPath(p, unitTestDataPath() + '/points.shp', unitTestDataPath() + '/points22.shp'))
self.assertEqual(layer1.source(), unitTestDataPath() + '/points22.shp')
self.assertEqual(gpkg1.source(), unitTestDataPath() + '/mixed_layers.gpkg|layername=lines')
self.assertEqual(gpkg2.source(), unitTestDataPath() + '/mixed_layers.gpkg|layername=points')
self.assertEqual(rl.source(), f'GPKG:{unitTestDataPath()}/mixed_layers.gpkg:band1')
self.assertEqual(memory_layer.source(), old_memory_source)
# should return false if we call again, no more matching paths
self.assertFalse(QgsProjectUtils.updateLayerPath(p, unitTestDataPath() + '/points.shp',
unitTestDataPath() + '/points22.shp'))

# replace geopackage path
self.assertTrue(QgsProjectUtils.updateLayerPath(p, unitTestDataPath() + '/mixed_layers.gpkg', unitTestDataPath() + '/mixed_layers22.gpkg'))
self.assertEqual(layer1.source(), unitTestDataPath() + '/points22.shp')
self.assertEqual(gpkg1.source(), unitTestDataPath() + '/mixed_layers22.gpkg|layername=lines')
self.assertEqual(gpkg2.source(), unitTestDataPath() + '/mixed_layers22.gpkg|layername=points')
self.assertEqual(rl.source(), f'GPKG:{unitTestDataPath()}/mixed_layers22.gpkg:band1')
self.assertEqual(memory_layer.source(), old_memory_source)
# should return false if we call again, no more matching paths
self.assertFalse(QgsProjectUtils.updateLayerPath(p, unitTestDataPath() + '/mixed_layers.gpkg',
unitTestDataPath() + '/mixed_layers22.gpkg'))


if __name__ == '__main__':
unittest.main()

0 comments on commit 354d334

Please sign in to comment.