2

I am trying to perform some categorization from an existing vector layer such that after performing the calculations, a new vector layer is created with just the new fields created. After running my script, no error message shows up, but then no layer is seen either. What is missing so that I can see the newly created layer and for it to get loaded up to the Mapcancas? Simply: Nothing appears after it runs. No new layer was seen. How do I fix it? Here is my code:

def processAlgorithm(self, parameters, context, feedback):       
            
    rawz_layer = self.parameterAsVectorLayer(parameters, self.INPUT_LAYER, context)
    
    if rawz_layer is None:
        raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
    rawz_fields = rawz_layer.fields() 

   
    '''Counter for the progress bar'''
    total = rawz_layer.featureCount()
    parts = 100/total
            
    # Get indices of P_SIZES and LENGTH fields
    p_size_index = rawz_fields.indexFromName(self.parameterAsString(parameters, self.PIPE_KEY, context))
    length_index = rawz_fields.indexFromName(self.parameterAsString(parameters, self.LENGTH_KEY, context))
    
    # Pipe Sizes
    size_pipe = [4, 5, 6, 8, 10, 12, 15, 18, 21, 24, 30, 36, 42]
    # Pipe Types with Prices
    single_wall_price = [2, 3, 4, 5, 8, 10, 20, 30, 40, 50, 80, 100, 150]
    smooth_wall_price = [3, 4, 5, 7, 10, 13, 20, 30, 45, 60, 90, 150, 250]
    clay_wall_price = [12, 15, 18, 25, 30, 40, 55, 80, 110, 140, 250, 450, 600]
    concrete_wall_price = [15, 20, 25, 40, 50, 70, 100, 150, 200, 250, 400, 600, 800]       
    
    #define new fields        
    out_fields = QgsFields()
    #append fields        
    out_fields.append(QgsField('N_SIZES', QVariant.Int))
    out_fields.append(QgsField('FREQUENCY', QVariant.Int))
    out_fields.append(QgsField('TOTAL_FEET', QVariant.Double))
    out_fields.append(QgsField('SING_EST', QVariant.Double))
    out_fields.append(QgsField('SING_POSB', QVariant.Double))
    out_fields.append(QgsField('SMOT_EST', QVariant.Double))
    out_fields.append(QgsField('SMOT_POSB', QVariant.Double))
    out_fields.append(QgsField('CLAY_EST', QVariant.Double))
    out_fields.append(QgsField('CLAY_POSB', QVariant.Double))
    out_fields.append(QgsField('CONC_EST', QVariant.Double))
    out_fields.append(QgsField('CONC_POSB', QVariant.Double))
                            
    '''load data from layer "raw_layer" '''
    feedback.setProgressText(self.tr("Loading network layer\n "))
    
    # Initialize lists for output attributes
    n_sizes = []
    n_freq = []
    total_length = []
    est_sing = []
    posb_sing = []
    est_smt = []
    posb_smt = []
    est_clay = []
    posb_clay = []
    est_conc = []
    posb_conc = []
    
    # Loop through features and group by pipe size
    groups = {}
    for feature in rawz_layer.getFeatures():
        p_size = feature.attributes()[p_size_index]
        length = feature.attributes()[length_index]

        # Add feature to corresponding pipe size group
        if p_size not in groups:
            groups[p_size] = {'freq': 0, 'length': 0}
        groups[p_size]['freq'] += 1
        groups[p_size]['length'] += length

    # Loop through pipe size groups and calculate output attributes
    for size in size_pipe:
        if size in groups:
            n_sizes.append(size)
            n_freq.append(groups[size]['freq'])
            total_length.append(groups[size]['length'])
            est_sing.append(groups[size]['length'] * single_wall_price[size_pipe.index(size)])
            posb_sing.append(est_sing[-1] * 0.9)
            est_smt.append(groups[size]['length'] * smooth_wall_price[size_pipe.index(size)])
            posb_smt.append(est_smt[-1] * 0.9)
            est_clay.append(groups[size]['length'] * clay_wall_price[size_pipe.index(size)])
            posb_clay.append(est_clay[-1] * 0.9)
            est_conc.append(groups[size]['length'] * concrete_wall_price[size_pipe.index(size)])
            posb_conc.append(est_conc[-1] * 0.9)
    
    # Create output layer and add output fields
    output_layer = QgsVectorLayer('Point?crs=' + rawz_layer.crs().authid(), self.OUTPUT_LAYER, 'memory')
    output_layer_provider = output_layer.dataProvider()
    output_layer_provider.addAttributes(out_fields)
    output_layer.updateFields()

    # Use random colors to create categorized symbol for each group/category
    color_list = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF', '#800000', '#008000', '#000080', '#808000', '#008080', '#800080', '#C0C0C0']
    categories = []
    i = 0
    for n_size in n_sizes:
        symbol = QgsSymbol.defaultSymbol(output_layer.geometryType())
        symbol.setColor(QColor(color_list[i % len(color_list)]))
        category = QgsRendererCategory(n_size, symbol, str(n_size))
        categories.append(category)
        i += 1
    renderer = QgsCategorizedSymbolRenderer('n_sizes', categories)
    output_layer.setRenderer(renderer)

    # Add features to output layer with new fields of attribute values
    features = []
    for i, n_size in enumerate(n_sizes):
        feature = QgsFeature()
        feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(0, i)))
        feature.setAttributes([
            n_sizes[i],
            n_freq[i],
            total_length[i],
            est_sing[i],
            posb_sing[i],
            est_smt[i],
            posb_smt[i],
            est_clay[i],
            posb_clay[i],
            est_conc[i],
            posb_conc[i]
        ])
        features.append(feature)
    output_layer_provider.addFeatures(features)
    
    # Add layer to the map canvas
    QgsProject.instance().addMapLayer(output_layer)
    
    # iface.setActiveLayer(output_layer)
    # canvas = iface.mapCanvas()
    # canvas.zoomToFullExtent()
    # canvas.refresh() 

    canvas = iface.mapCanvas()
    canvas.setExtent(output_layer.extent())
    canvas.refresh()
    iface.layerTreeView().refreshLayerSymbology(output_layer.id())


    # Return result
    return {self.OUTPUT_LAYER: output_layer}
5

2 Answers 2

4

There is a lot of extraneous code in your question which is not related to your problem. This makes it very difficult to post a tried and tested answer, because I for one do not wish to spend the time to create a test layer, and you have not bothered to provide a download link or a sample of your input layer, even though you seem to be asking for someone to fix your code for you.

Therefore, this answer is not fully tested, and I make no guarantee that it does not contain small bugs e.g. missing imports, typos etc.

I have basically just refactored your code to give you a basic example of how it should be structured. The main changes are:

  1. Using a proper processing destination output parameter (QgsProcessingParameterFeatureSink) and let the processing framework handle loading the output instead of creating a new memory layer and adding it to the project inside the processAlgorithm() method which is not thread-safe*.

  2. Move the code block which creates and sets a new renderer to the output layer to a layer post processor, since this is also not thread-safe and should not be attempted inside the processAlgorithm() method, unless your algorithm returns the NoThreading flag*.

*https://docs.qgis.org/3.28/en/docs/user_manual/processing/scripts.html#flags

from qgis.PyQt.QtCore import QCoreApplication, QVariant

from qgis.PyQt.QtGui import QColor

from qgis.core import (QgsWkbTypes,
                    QgsField,
                    QgsFeature,
                    QgsGeometry,
                    QgsFeatureSink,
                    QgsProcessing,
                    QgsProcessingAlgorithm,
                    QgsProcessingParameterFeatureSource,
                    QgsProcessingParameterField,
                    QgsProcessingParameterFeatureSink,
                    QgsVectorLayer,
                    QgsProcessingLayerPostProcessorInterface,
                    QgsSymbol,
                    QgsRendererCategory,
                    QgsCategorizedSymbolRenderer)

class ExAlgo(QgsProcessingAlgorithm):
    
    INPUT = 'INPUT'
    OUTPUT = 'OUTPUT'
    PIPE_KEY = 'PIPE_KEY'
    LENGTH_KEY = 'LENGTH_KEY'
 
    def __init__(self):
        super().__init__()
 
    def name(self):
        return "exalgo"
     
    def tr(self, text):
        return QCoreApplication.translate("exalgo", text)
         
    def displayName(self):
        return self.tr("Example script")
 
    def group(self):
        return self.tr("Examples")
 
    def groupId(self):
        return "examples"
 
    def shortHelpString(self):
        return self.tr("Example script styling an output layer")
 
    def helpUrl(self):
        return "https://qgis.org"
         
    def createInstance(self):
        return type(self)()
   
    def initAlgorithm(self, config=None):
        self.addParameter(QgsProcessingParameterFeatureSource(
            self.INPUT,
            self.tr("Input layer"),
            [QgsProcessing.TypeVectorAnyGeometry]))
        
        self.addParameter(QgsProcessingParameterField(
            self.PIPE_KEY,
            self.tr("Pipe size field"),
            parentLayerParameterName=self.INPUT,
            type=QgsProcessingParameterField.Numeric))
        
        self.addParameter(QgsProcessingParameterField(
            self.LENGTH_KEY,
            self.tr("Pipe size field"),
            parentLayerParameterName=self.INPUT,
            type=QgsProcessingParameterField.Numeric))
    
        self.addParameter(QgsProcessingParameterFeatureSink(
            self.OUTPUT,
            self.tr("Output layer"),
            QgsProcessing.TypeVectorPoint))
 
    def processAlgorithm(self, parameters, context, feedback):
        # retrieve input parameter source
        rawz_layer = self.parameterAsSource(parameters, self.INPUT, context)
        
        rawz_pipe_field = self.parameterAsString(parameters, self.PIPE_KEY, context)
        
        rawz_length_field = self.parameterAsString(parameters, self.LENGTH_KEY, context)
        
        #define new fields
        out_fields = QgsFields()
        #append fields
        out_fields.append(QgsField('N_SIZES', QVariant.Int))
        out_fields.append(QgsField('FREQUENCY', QVariant.Int))
        out_fields.append(QgsField('TOTAL_FEET', QVariant.Double))
        out_fields.append(QgsField('SING_EST', QVariant.Double))
        out_fields.append(QgsField('SING_POSB', QVariant.Double))
        out_fields.append(QgsField('SMOT_EST', QVariant.Double))
        out_fields.append(QgsField('SMOT_POSB', QVariant.Double))
        out_fields.append(QgsField('CLAY_EST', QVariant.Double))
        out_fields.append(QgsField('CLAY_POSB', QVariant.Double))
        out_fields.append(QgsField('CONC_EST', QVariant.Double))
        out_fields.append(QgsField('CONC_POSB', QVariant.Double))
        
        # To load your output properly, don't create a new memory layer and
        # try to add it to the project, use a proper processing output parameter...
        (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, out_fields, QgsWkbTypes.Point, rawz_layer.sourceCrs())
        
        rawz_fields = rawz_layer.fields()
        
        # Get indices of P_SIZES and LENGTH fields
        p_size_index = rawz_fields.indexFromName(rawz_pipe_field)
        length_index = rawz_fields.indexFromName(rawz_length_field)
        
        # Pipe Sizes
        size_pipe = [4, 5, 6, 8, 10, 12, 15, 18, 21, 24, 30, 36, 42]
        # Pipe Types with Prices
        single_wall_price = [2, 3, 4, 5, 8, 10, 20, 30, 40, 50, 80, 100, 150]
        smooth_wall_price = [3, 4, 5, 7, 10, 13, 20, 30, 45, 60, 90, 150, 250]
        clay_wall_price = [12, 15, 18, 25, 30, 40, 55, 80, 110, 140, 250, 450, 600]
        concrete_wall_price = [15, 20, 25, 40, 50, 70, 100, 150, 200, 250, 400, 600, 800]

        # Initialize lists for output attributes
        n_sizes = []
        n_freq = []
        total_length = []
        est_sing = []
        posb_sing = []
        est_smt = []
        posb_smt = []
        est_clay = []
        posb_clay = []
        est_conc = []
        posb_conc = []
        
        # Loop through features and group by pipe size
        groups = {}
        for feature in rawz_layer.getFeatures():
            p_size = feature.attributes()[p_size_index]
            length = feature.attributes()[length_index]

            # Add feature to corresponding pipe size group
            if p_size not in groups:
                groups[p_size] = {'freq': 0, 'length': 0}
            groups[p_size]['freq'] += 1
            groups[p_size]['length'] += length

        # Loop through pipe size groups and calculate output attributes
        for size in size_pipe:
            if size in groups:
                n_sizes.append(size)
                n_freq.append(groups[size]['freq'])
                total_length.append(groups[size]['length'])
                est_sing.append(groups[size]['length'] * single_wall_price[size_pipe.index(size)])
                posb_sing.append(est_sing[-1] * 0.9)
                est_smt.append(groups[size]['length'] * smooth_wall_price[size_pipe.index(size)])
                posb_smt.append(est_smt[-1] * 0.9)
                est_clay.append(groups[size]['length'] * clay_wall_price[size_pipe.index(size)])
                posb_clay.append(est_clay[-1] * 0.9)
                est_conc.append(groups[size]['length'] * concrete_wall_price[size_pipe.index(size)])
                posb_conc.append(est_conc[-1] * 0.9)

        # Add features to output layer with new fields of attribute values
        for i, n_size in enumerate(n_sizes):
            feature = QgsFeature()
            feature.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(0, i)))
            feature.setAttributes([
                n_sizes[i],
                n_freq[i],
                total_length[i],
                est_sing[i],
                posb_sing[i],
                est_smt[i],
                posb_smt[i],
                est_clay[i],
                posb_clay[i],
                est_conc[i],
                posb_conc[i]
            ])
            sink.addFeature(feature, QgsFeatureSink.FastInsert)
        
        if context.willLoadLayerOnCompletion(dest_id):
            context.layerToLoadOnCompletionDetails(dest_id).setPostProcessor(MyLayerPostProcessor.create())
 
        return {self.OUTPUT: dest_id}
        
class MyLayerPostProcessor(QgsProcessingLayerPostProcessorInterface):

    instance = None

    def postProcessLayer(self, layer, context, feedback):
        if not isinstance(layer, QgsVectorLayer):
            return
        # Here you can do any non thread-safe post processing to your layer- set style or whatever!
        # Use random colors to create categorized symbol for each group/category
        color_list = ['#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF', '#800000', '#008000', '#000080', '#808000', '#008080', '#800080', '#C0C0C0']
        categories = []
        i = 0
        for n_size in n_sizes:
            symbol = QgsSymbol.defaultSymbol(layer.geometryType())
            symbol.setColor(QColor(color_list[i % len(color_list)]))
            category = QgsRendererCategory(n_size, symbol, str(n_size))
            categories.append(category)
            i += 1
        renderer = QgsCategorizedSymbolRenderer('n_sizes', categories)
        layer.setRenderer(renderer)

    @staticmethod
    def create() -> 'MyLayerPostProcessor':
        MyLayerPostProcessor.instance = MyLayerPostProcessor()
        return MyLayerPostProcessor.instance
0
world = []

for layer_num in range(60):
    sum_rarities = 0
    types_inlayer = []
    layer = []
    for type in block_types:
        if type.in_layer(layer_num):
            types_in_layer.append(type)
            sum_rarities +=type.rarity
        for type in types_in_layer:
            num_in_layer = type.rarity/sum_rarities * screen_with/block_side_length
            for i in range(math.floor(blocks_remaining)):
                layer.append(Block(type))
            blocks_remaining = (screen_width/block_side_length) -len(layer)
1
  • 1
    As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Sep 4, 2023 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.