I wrote a processing script that creates a layer and adds it to the current project:
class TestAlgorithm(QgsProcessingAlgorithm):
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFileDestination('output_layer', 'Save Output Layer', fileFilter='GeoPackage (*.gpkg *GPKG)'))
def processAlgorithm(self, parameters, context, model_feedback):
# Use a multi-step feedback, so that individual child algorithm progress reports are adjusted for the
# overall progress through the model
feedback = QgsProcessingMultiStepFeedback(1, model_feedback)
results = {}
outputs = {}
root = QgsProject.instance().layerTreeRoot()
Layer_1= QgsVectorLayer("Point", "Layer_1", "memory")
Layer_1.dataProvider().addAttributes([
QgsField("Field_1", QVariant.String),
QgsField("Field_2", QVariant.String)
])
Layer_1.updateFields()
CRS = QgsCoordinateReferenceSystem("EPSG:25832")
Layer_1.setCrs(CRS)
layer_list = [Layer_1]
params = {'LAYERS': layer_list,
'OUTPUT': parameters['output_layer'],
'OVERWRITE': False,
'SAVE_STYLES': False,
'SAVE_METADATA': True,
'SELECTED_FEATURES_ONLY': False}
ALG_1 = processing.run("native:package", params, is_child_algorithm=True)
layer_list = [l.GetName() for l in ogr.Open(ALG_1['OUTPUT'])]
for layer in layer_list:
source= os.path.join(ALG_1['OUTPUT'] + "|layername=" + layer)
export = QgsVectorLayer(source, 'export_layer', "ogr")
QgsProject.instance().addMapLayer(export, False)
root.insertLayer(0, export)
feedback.setCurrentStep(1)
if feedback.isCanceled():
return {}
return results
def name(self):
return 'Test'
def displayName(self):
"""
Returns the translated algorithm name, which should be used for any
user-visible display of the algorithm name.
"""
return 'Test'
def createInstance(self):
return TestAlgorithm()
After the layer has been added to the project, I want to add the following custom action to the context menu of this layer, to be able to switch quickly between different styles:
act = QAction(u"Load MyStyle1")
act2 = QAction(u"Load MyStyle2")
def run1():
layer = QgsProject.instance().mapLayersByName('export_layer')[0]
style_1 = '[folder path to my_style_1.qml]'
layer.loadNamedStyle(style_1)
layer.triggerRepaint()
act.triggered.connect(run1)
def run2():
layer = QgsProject.instance().mapLayersByName('export_layer')[0]
style_2 = '[folder path to my_style_2.qml]'
layer.loadNamedStyle(style_2)
layer.triggerRepaint()
act2.triggered.connect(run2)
layer = QgsProject.instance().mapLayersByName('export_layer')[0]
iface.addCustomActionForLayerType(act,'Load MyStyle1',QgsMapLayerType.VectorLayer,False)
iface.addCustomActionForLayerType(act2,'Load MyStyle2',QgsMapLayerType.VectorLayer,False)
iface.addCustomActionForLayer(act,layer)
iface.addCustomActionForLayer(act2,layer)
Both scripts work on their own, but I haven't succeeded to combine them into one. The problem seems to be that the export_layer
has to exist before the actions can be added. Where do I have to put the second part to make it work? Within the processing algorithm class or outside of it?
1 Answer 1
Here is an example of setting multiple layer post processors to loaded output layers, adding custom actions to the layers which will load 2 different styles (one for point layers & one for line layers) to each layer.
By the way, it is not recommended to manually load layers to the project in the processAlgorithm()
method of a processing algorithm. Instead, you should add them to the temporary layer store, then add them as layers to load on completion to the algorithms context
object. The example below shows how to do that.
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.PyQt.QtWidgets import QAction
from qgis.core import (QgsField, QgsProcessing, QgsProcessingAlgorithm,
QgsDataProvider, QgsProcessingParameterFileDestination,
QgsVectorLayer, QgsProcessingContext, QgsMapLayerType,
QgsProcessingLayerPostProcessorInterface,
Qgis)
from qgis.utils import iface
import processing
class AddLayerActions(QgsProcessingAlgorithm):
OUTPUT = 'OUTPUT'
# Empty dictionary to store the post processor class instances associated with each output layer id
post_processors = {}
def __init__(self):
super().__init__()
def name(self):
return "addactions"
def tr(self, text):
return QCoreApplication.translate("addactions", text)
def displayName(self):
return self.tr("Add Actions")
def group(self):
return self.tr("Examples")
def groupId(self):
return "examples"
def shortHelpString(self):
return self.tr("Adds menu actions to a layer")
def helpUrl(self):
return "https://qgis.org"
def createInstance(self):
return type(self)()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFileDestination(self.OUTPUT, 'Save Output Layer', fileFilter='GeoPackage (*.gpkg *GPKG)'))
def processAlgorithm(self, parameters, context, feedback):
layer_1 = QgsVectorLayer("Point?crs=epsg:4326", "Layer_1", "memory")
layer_2 = QgsVectorLayer("Linestring?crs=epsg:4326", "Layer_2", "memory")
layer_1.dataProvider().addAttributes([
QgsField("Field_1", QVariant.String),
QgsField("Field_2", QVariant.String)
])
layer_1.updateFields()
layer_2.dataProvider().addAttributes([
QgsField("Field_1", QVariant.String),
QgsField("Field_2", QVariant.String)
])
layer_2.updateFields()
params = {'LAYERS': [layer_1, layer_2],
'OUTPUT': parameters[self.OUTPUT],
'OVERWRITE': False,
'SAVE_STYLES': False,
'SAVE_METADATA': True,
'SELECTED_FEATURES_ONLY': False}
output_geopackage = processing.run("native:package", params, is_child_algorithm=True)
fileName = output_geopackage['OUTPUT']
layer = QgsVectorLayer(fileName, "test", "ogr")
subLayers = layer.dataProvider().subLayers()
for subLayer in subLayers:
name = subLayer.split(QgsDataProvider.SUBLAYER_SEPARATOR)[1]
uri = f"{fileName}|layername={name}"
# Create layer
sub_vlayer = QgsVectorLayer(uri, name, 'ogr')
feedback.pushInfo(f'{sub_vlayer.isValid()}')
if sub_vlayer.isValid():
# Add output layers to temporary layer store and add
# layer to load on completion to context object
context.temporaryLayerStore().addMapLayer(sub_vlayer)
context.addLayerToLoadOnCompletion(sub_vlayer.id(), QgsProcessingContext.LayerDetails(name,
context.project(),
name))
# Create post processor class instance and add to dictionary
pp = AddLayerAction.create()
self.post_processors[sub_vlayer.id()] = pp
# If layer will be loaded on completion, set its post processor
if context.willLoadLayerOnCompletion(sub_vlayer.id()):
context.layerToLoadOnCompletionDetails(sub_vlayer.id()).setPostProcessor(self.post_processors[sub_vlayer.id()])
return {self.OUTPUT: layer.id()}
class AddLayerAction(QgsProcessingLayerPostProcessorInterface):
instance = None
def load_style_1(self, layer):
if layer.geometryType() == Qgis.GeometryType.Point:
layer.loadNamedStyle('path/to/point_style_1.qml')
elif layer.geometryType() == Qgis.GeometryType.Line:
layer.loadNamedStyle('path/to/line_style_1.qml')
layer.triggerRepaint()
def load_style_2(self, layer):
if layer.geometryType() == Qgis.GeometryType.Point:
layer.loadNamedStyle('path/to/point_style_2.qml')
elif layer.geometryType() == Qgis.GeometryType.Line:
layer.loadNamedStyle('path/to/line_style_2.qml')
layer.triggerRepaint()
def postProcessLayer(self, layer, context, feedback):
# Reorder layers so that the point layer is always on top
project = context.project()
root_group = project.layerTreeRoot()
if layer.geometryType() == Qgis.GeometryType.Point:
lyr_node = root_group.findLayer(layer.id())
if lyr_node:
node_clone = lyr_node.clone()
root_group.insertChildNode(0, node_clone)
lyr_node.parent().removeChildNode(lyr_node)
elif layer.geometryType() == Qgis.GeometryType.Line:
lyr_node = root_group.findLayer(layer.id())
if lyr_node:
node_clone = lyr_node.clone()
root_group.insertChildNode(1, node_clone)
lyr_node.parent().removeChildNode(lyr_node)
# Create & add the custom layer actions
self.act1 = QAction("Load Style 1")
self.act2 = QAction("Load Style 2")
self.act1.triggered.connect(lambda: self.load_style_1(layer))
self.act2.triggered.connect(lambda: self.load_style_2(layer))
iface.addCustomActionForLayerType(self.act1, 'Load Style', QgsMapLayerType.VectorLayer, False)
iface.addCustomActionForLayerType(self.act2, 'Load Style', QgsMapLayerType.VectorLayer, False)
iface.addCustomActionForLayer(self.act1, layer)
iface.addCustomActionForLayer(self.act2, layer)
@staticmethod
def create() -> 'AddLayerAction':
AddLayerAction.instance = AddLayerAction()
return AddLayerAction.instance
Screencast showing result:
Acknowledgements:
The section of code to read the sub-layers from the output geopackage is from the PyQGIS Cheat sheet.
The post processor stuff is based on an old example from Nyall Dawson, which I have been using and sharing for a while now. There are other ways to do it but this has worked well for me.
-
1Wow, thanks a lot for your help! This looks like an awesome script and I will definetely use it to improve my code.Calidris– Calidris2025年07月30日 12:46:44 +00:00Commented Jul 30 at 12:46
Explore related questions
See similar questions with these tags.
act1 = QAction("Load MyStyle1", iface.mainWindow())
and then call it withact1.triggered.connect(run1)
? Theiface.mainWindow()
is passed toQAction
to register the action with the QGIS interface.iface.mainWindow()
what I was missing.