I have a QGIS project with multiple polygon layers organized inside a group. Using my Processing script, I select that group by name and then copy certain layers from it into a newly created group. The layers to copy are chosen based on an option I provide as input to the script. Additionally, the script applies styles to these copied layers from .qml files stored in a specified folder.
The script generally works, but I’m encountering a few issues:
- Intermittent crashes: When I run the script repeatedly, sometimes QGIS just crashes without any clear error message. It doesn’t happen every time.
- Layer visibility toggling issues: When I try to toggle visibility (hide/show) of individual layers in the new group, the visibility state doesn’t update immediately. Instead, I have to hide and then show the entire group for the changes to take effect on the individual layers.
- Visual gap under last layer: After the layers are copied into the new group, I notice a visual gap or space under the last layer in the layer panel (see attached screenshot - between pp and group F).
My code:
import os from qgis.PyQt.QtCore import QCoreApplication from qgis.core import ( QgsProcessing, QgsProcessingException, QgsProcessingAlgorithm, QgsProcessingParameterString, QgsProcessingParameterEnum, QgsProject, QgsLayerTreeLayer, QgsVectorLayer)
class UrAlgorithm(QgsProcessingAlgorithm):
INPUT_GROUP = 'INPUT_GROUP'
OPTION = 'OPTION'
STYLE_FOLDER = 'STYLE_FOLDER'
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return UrAlgorithm()
def name(self):
return 'Change_test'
def displayName(self):
return self.tr('Change_test')
def group(self):
return self.tr('Test')
def groupId(self):
return 'test'
def shortHelpString(self):
return self.tr("Copies certain layers from one group to a new group as memory layers, based on the selected option. "
"Also adds styles from a specified folder, if there are matching .qml files.")
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterString(
self.INPUT_GROUP,
self.tr('Group name'),
defaultValue='group'
)
)
self.addParameter(
QgsProcessingParameterEnum(
self.OPTION,
self.tr('Options'),
options=['A', 'B', 'C', 'D', 'E', 'F'],
defaultValue=0
)
)
self.addParameter(
QgsProcessingParameterString(
self.STYLE_FOLDER,
self.tr('File with .qml'),
defaultValue=r''
)
)
def copy_layer(self, orig_layer, feedback, style_folder, option):
try:
feats = list(orig_layer.getFeatures())
crs = orig_layer.crs().authid()
geometry_type = orig_layer.geometryType()
geometry_str = {0: "Point", 1: "LineString", 2: "Polygon"}.get(geometry_type, "Polygon")
uri = f"{geometry_str}?crs={crs}"
mem_layer = QgsVectorLayer(uri, orig_layer.name(), "memory")
mem_layer_dp = mem_layer.dataProvider()
mem_layer_dp.addAttributes(orig_layer.fields())
mem_layer.updateFields()
mem_layer_dp.addFeatures(feats)
mem_layer.setName(orig_layer.name())
# name of the style file
style_file_name = f"{orig_layer.name().lower()}.qml"
# exeptions
if option == 'B' and orig_layer.name().lower() == 'aa1':
style_file_name = "aa1_2.qml"
style_path = os.path.join(style_folder, style_file_name)
if os.path.exists(style_path):
success = mem_layer.loadNamedStyle(style_path)
mem_layer.triggerRepaint()
else:
feedback.pushInfo(self.tr(f"⚠Style for layer '{orig_layer.name()}' not found, skiped.⚠"))
return mem_layer
except Exception as e:
raise QgsProcessingException(self.tr(f"⚠Error while copying '{orig_layer.name()}': {str(e)}"))
def processAlgorithm(self, parameters, context, feedback):
input_group_name = self.parameterAsString(parameters, self.INPUT_GROUP, context)
option_index = self.parameterAsEnum(parameters, self.OPTION, context)
style_folder = self.parameterAsString(parameters, self.STYLE_FOLDER, context)
option_keys = ['A', 'B', 'C', 'D', 'E', 'F']
selected_option = option_keys[option_index]
option_map = {
'A': ('A', ['aa1', 'bb', 'cc', 'dd', 'ee', 'ff', 'gg', 'hh', 'ii']),
'B': ('B', ['aa1', 'ee', 'ff', 'jj', 'kk', 'll']),
'C': ('C', ['ee', 'ff', 'pv', 'ps', 'pm', 'pp']),
'D': ('D', ['cc', 'ee', 'ff', 'mm', 'nn', 'oo']),
'E': ('E', ['cc', 'ee', 'ff', 'pp', 'rr', 'ss']),
'F': ('F', ['ee', 'ff', 'tt', 'uu', 'vv'])
}
output_group_name, desired_order = option_map[selected_option]
root = QgsProject.instance().layerTreeRoot()
input_group = root.findGroup(input_group_name)
if input_group is None:
raise QgsProcessingException(self.tr(f"⚠group '{input_group_name}' does not exsist."))
if root.findGroup(output_group_name) is not None:
feedback.reportError(self.tr(f"⚠group '{output_group_name}' already exsists."), fatalError=True)
return {}
output_group = root.addGroup(output_group_name)
feedback.pushInfo(self.tr(f"Group created: {output_group_name}"))
layer_dict = {}
for child in input_group.children():
if isinstance(child, QgsLayerTreeLayer):
layer = child.layer()
if layer is not None:
layer_dict[layer.name()] = layer
layers_copied = 0
project = QgsProject.instance()
for layer_name in desired_order:
if layer_name in layer_dict:
try:
new_layer = self.copy_layer(layer_dict[layer_name], feedback, style_folder, selected_option)
project.addMapLayer(new_layer, False)
output_group.addLayer(new_layer)
feedback.pushInfo(self.tr(f"Copy: {layer_name}"))
layers_copied += 1
except QgsProcessingException as e:
feedback.reportError(str(e), fatalError=True)
return {}
else:
feedback.pushInfo(self.tr(f"⚠Layer '{layer_name}' not found, skip.⚠"))
feedback.pushInfo(self.tr(f"No. of layers copied: {layers_copied}"))
return {}
I have translated the code into English to the best of my ability. However, due to language differences and possible changes in layer, group, or variable names, there may be inaccuracies or errors in the translation.
