def testOrderBy(self):
        self.renderer.setOrderBy(
            QgsFeatureRequest.OrderBy(
                [QgsFeatureRequest.OrderByClause('Value', False)]))
        self.renderer.setOrderByEnabled(True)

        # Setup rendering check
        renderchecker = QgsMultiRenderChecker()
        renderchecker.setMapSettings(self.mapsettings)
        renderchecker.setControlName('expected_singlesymbol_orderby')
        self.assertTrue(renderchecker.runTest('singlesymbol_orderby'))

        # disable order by and retest
        self.renderer.setOrderByEnabled(False)
        self.assertTrue(renderchecker.runTest('single'))
Example #2
0
    def apply_filter(self, request, qgis_layer, qgis_feature_request, view):

        if request.query_params.get('ordering') is not None:

            ordering_rules = []

            for ordering in request.query_params.get('ordering').split(','):
                ascending = True
                if ordering.startswith('-'):
                    ordering = ordering[1:]
                    ascending = False

                if not self._is_valid_field(qgis_layer, ordering, view):
                    continue

                ordering_rules.append(
                    QgsFeatureRequest.OrderByClause(ordering, ascending))

            if ordering_rules:
                order_by = QgsFeatureRequest.OrderBy(ordering_rules)
                qgis_feature_request.setOrderBy(order_by)
    def testNumeric(self):
        """ Test calculation of aggregates on numeric fields"""

        layer = QgsVectorLayer(
            "Point?field=fldint:integer&field=flddbl:double", "layer",
            "memory")
        pr = layer.dataProvider()

        # must be same length:
        int_values = [4, 2, 3, 2, 5, None, 8]
        dbl_values = [5.5, 3.5, 7.5, 5, 9, None, 7]
        self.assertEqual(len(int_values), len(dbl_values))

        features = []
        for i in range(len(int_values)):
            f = QgsFeature()
            f.setFields(layer.fields())
            f.setAttributes([int_values[i], dbl_values[i]])
            features.append(f)
        assert pr.addFeatures(features)

        tests = [
            [QgsAggregateCalculator.Count, 'fldint', 6],
            [QgsAggregateCalculator.Count, 'flddbl', 6],
            [QgsAggregateCalculator.Sum, 'fldint', 24],
            [QgsAggregateCalculator.Sum, 'flddbl', 37.5],
            [QgsAggregateCalculator.Mean, 'fldint', 4],
            [QgsAggregateCalculator.Mean, 'flddbl', 6.25],
            [QgsAggregateCalculator.StDev, 'fldint', 2.0816],
            [QgsAggregateCalculator.StDev, 'flddbl', 1.7969],
            [QgsAggregateCalculator.StDevSample, 'fldint', 2.2803],
            [QgsAggregateCalculator.StDevSample, 'flddbl', 1.9685],
            [QgsAggregateCalculator.Min, 'fldint', 2],
            [QgsAggregateCalculator.Min, 'flddbl', 3.5],
            [QgsAggregateCalculator.Max, 'fldint', 8],
            [QgsAggregateCalculator.Max, 'flddbl', 9],
            [QgsAggregateCalculator.Range, 'fldint', 6],
            [QgsAggregateCalculator.Range, 'flddbl', 5.5],
            [QgsAggregateCalculator.Median, 'fldint', 3.5],
            [QgsAggregateCalculator.Median, 'flddbl', 6.25],
            [QgsAggregateCalculator.CountDistinct, 'fldint', 5],
            [QgsAggregateCalculator.CountDistinct, 'flddbl', 6],
            [QgsAggregateCalculator.CountMissing, 'fldint', 1],
            [QgsAggregateCalculator.CountMissing, 'flddbl', 1],
            [QgsAggregateCalculator.FirstQuartile, 'fldint', 2],
            [QgsAggregateCalculator.FirstQuartile, 'flddbl', 5.0],
            [QgsAggregateCalculator.ThirdQuartile, 'fldint', 5.0],
            [QgsAggregateCalculator.ThirdQuartile, 'flddbl', 7.5],
            [QgsAggregateCalculator.InterQuartileRange, 'fldint', 3.0],
            [QgsAggregateCalculator.InterQuartileRange, 'flddbl', 2.5],
            [QgsAggregateCalculator.ArrayAggregate, 'fldint', int_values],
            [QgsAggregateCalculator.ArrayAggregate, 'flddbl', dbl_values],
        ]

        agg = QgsAggregateCalculator(layer)
        for t in tests:
            val, ok = agg.calculate(t[0], t[1])
            self.assertTrue(ok)
            if isinstance(t[2], (int, list)):
                self.assertEqual(val, t[2])
            else:
                self.assertAlmostEqual(val, t[2], 3)

        # bad tests - the following stats should not be calculatable for numeric fields
        for t in [
                QgsAggregateCalculator.StringMinimumLength,
                QgsAggregateCalculator.StringMaximumLength
        ]:
            val, ok = agg.calculate(t, 'fldint')
            self.assertFalse(ok)
            val, ok = agg.calculate(t, 'flddbl')
            self.assertFalse(ok)

        # with order by
        agg = QgsAggregateCalculator(layer)
        val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate,
                                'fldint')
        self.assertEqual(val, [4, 2, 3, 2, 5, NULL, 8])
        params = QgsAggregateCalculator.AggregateParameters()
        params.orderBy = QgsFeatureRequest.OrderBy(
            [QgsFeatureRequest.OrderByClause('fldint')])
        agg.setParameters(params)
        val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate,
                                'fldint')
        self.assertEqual(val, [2, 2, 3, 4, 5, 8, NULL])
        params.orderBy = QgsFeatureRequest.OrderBy(
            [QgsFeatureRequest.OrderByClause('flddbl')])
        agg.setParameters(params)
        val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate,
                                'fldint')
        self.assertEqual(val, [2, 2, 4, 8, 3, 5, NULL])
    def testString(self):
        """ Test calculation of aggregates on string fields"""

        layer = QgsVectorLayer("Point?field=fldstring:string", "layer",
                               "memory")
        pr = layer.dataProvider()

        values = [
            'cc', 'aaaa', 'bbbbbbbb', 'aaaa', 'eeee', '', 'eeee', '', 'dddd'
        ]
        features = []
        for v in values:
            f = QgsFeature()
            f.setFields(layer.fields())
            f.setAttributes([v])
            features.append(f)
        assert pr.addFeatures(features)

        tests = [
            [QgsAggregateCalculator.Count, 'fldstring', 9],
            [QgsAggregateCalculator.CountDistinct, 'fldstring', 6],
            [QgsAggregateCalculator.CountMissing, 'fldstring', 2],
            [QgsAggregateCalculator.Min, 'fldstring', 'aaaa'],
            [QgsAggregateCalculator.Max, 'fldstring', 'eeee'],
            [QgsAggregateCalculator.StringMinimumLength, 'fldstring', 0],
            [QgsAggregateCalculator.StringMaximumLength, 'fldstring', 8],
            [QgsAggregateCalculator.ArrayAggregate, 'fldstring', values],
        ]

        agg = QgsAggregateCalculator(layer)
        for t in tests:
            val, ok = agg.calculate(t[0], t[1])
            self.assertTrue(ok)
            self.assertEqual(val, t[2])

        # test string concatenation
        agg.setDelimiter(',')
        self.assertEqual(agg.delimiter(), ',')
        val, ok = agg.calculate(QgsAggregateCalculator.StringConcatenate,
                                'fldstring')
        self.assertTrue(ok)
        self.assertEqual(val, 'cc,aaaa,bbbbbbbb,aaaa,eeee,,eeee,,dddd')
        val, ok = agg.calculate(QgsAggregateCalculator.StringConcatenateUnique,
                                'fldstring')
        self.assertTrue(ok)
        self.assertEqual(val, 'cc,aaaa,bbbbbbbb,eeee,,dddd')

        # bad tests - the following stats should not be calculatable for string fields
        for t in [
                QgsAggregateCalculator.Sum, QgsAggregateCalculator.Mean,
                QgsAggregateCalculator.Median, QgsAggregateCalculator.StDev,
                QgsAggregateCalculator.StDevSample,
                QgsAggregateCalculator.Range, QgsAggregateCalculator.Minority,
                QgsAggregateCalculator.Majority,
                QgsAggregateCalculator.FirstQuartile,
                QgsAggregateCalculator.ThirdQuartile,
                QgsAggregateCalculator.InterQuartileRange
        ]:
            val, ok = agg.calculate(t, 'fldstring')
            self.assertFalse(ok)

        # with order by
        agg = QgsAggregateCalculator(layer)
        val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate,
                                'fldstring')
        self.assertEqual(
            val,
            ['cc', 'aaaa', 'bbbbbbbb', 'aaaa', 'eeee', '', 'eeee', '', 'dddd'])
        params = QgsAggregateCalculator.AggregateParameters()
        params.orderBy = QgsFeatureRequest.OrderBy(
            [QgsFeatureRequest.OrderByClause('fldstring')])
        agg.setParameters(params)
        val, ok = agg.calculate(QgsAggregateCalculator.ArrayAggregate,
                                'fldstring')
        self.assertEqual(
            val,
            ['', '', 'aaaa', 'aaaa', 'bbbbbbbb', 'cc', 'dddd', 'eeee', 'eeee'])
        val, ok = agg.calculate(QgsAggregateCalculator.StringConcatenate,
                                'fldstring')
        self.assertEqual(val, 'aaaaaaaabbbbbbbbccddddeeeeeeee')
Example #5
0
def assign_highest_value(exposure, hazard):
    """Assign the highest hazard value to an indivisible feature.

    For indivisible polygon exposure layers such as buildings, we need to
    assigned the greatest hazard that each polygon touches and use that as the
    effective hazard class.

    Issue https://github.com/inasafe/inasafe/issues/3192

    We follow the concept here that any part of the exposure dataset that
    touches the hazard is affected, and the greatest hazard is the effective
    hazard.

    :param exposure: The building vector layer.
    :type exposure: QgsVectorLayer

    :param hazard: The vector layer to use for hazard.
    :type hazard: QgsVectorLayer

    :return: The new impact layer.
    :rtype: QgsVectorLayer

    .. versionadded:: 4.0
    """
    output_layer_name = assign_highest_value_steps['output_layer_name']

    hazard_inasafe_fields = hazard.keywords['inasafe_fields']
    haz_id_field = hazard_inasafe_fields[hazard_id_field['key']]
    try:
        aggr_id_field = hazard_inasafe_fields[aggregation_id_field['key']]
    except AttributeError:
        aggr_id_field = None

    if not hazard.keywords.get('classification'):
        raise InvalidKeywordsForProcessingAlgorithm
    if not hazard_inasafe_fields.get(hazard_class_field['key']):
        raise InvalidKeywordsForProcessingAlgorithm

    indices = []
    exposure.startEditing()
    for field in hazard.fields():
        exposure.addAttribute(field)
        indices.append(exposure.fields().lookupField(field.name()))
    exposure.commitChanges()
    provider = exposure.dataProvider()

    spatial_index = create_spatial_index(exposure)

    # cache features from exposure layer for faster retrieval
    exposure_features = {}
    for f in exposure.getFeatures():
        exposure_features[f.id()] = f

    # Todo callback
    # total = 100.0 / len(selectionA)

    hazard_field = hazard_inasafe_fields[hazard_class_field['key']]

    layer_classification = None
    for classification in hazard_classification['types']:
        if classification['key'] == hazard.keywords['classification']:
            layer_classification = classification
            break

    # Get a ordered list of classes like ['high', 'medium', 'low']
    levels = [key['key'] for key in layer_classification['classes']]
    levels.append(not_exposed_class['key'])

    def _hazard_sort_key(feature):
        """Custom feature sort function.

        The function were intended to sort hazard features, in order
        for maintaining consistencies between subsequent runs.

        With controlled order, we will have the same output if one
        hazard spans over multiple aggregation boundaries.
        """
        if aggr_id_field:
            return (feature[haz_id_field]
                    or feature.id(), feature[aggr_id_field])
        return feature[haz_id_field] or feature.id()

    # Let's loop over the hazard layer, from high to low hazard zone.
    for hazard_value in levels:
        expression = '"%s" = \'%s\'' % (hazard_field, hazard_value)
        order_by_hazard_field = QgsFeatureRequest.OrderByClause(haz_id_field)
        order_by_aggregation_field = QgsFeatureRequest.OrderByClause(
            aggr_id_field)
        order_by = QgsFeatureRequest.OrderBy(
            [order_by_hazard_field, order_by_aggregation_field])
        hazard_request = QgsFeatureRequest()\
            .setOrderBy(order_by).setFilterExpression(expression)
        update_map = {}
        areas = sorted(hazard.getFeatures(hazard_request),
                       key=_hazard_sort_key)
        for area in areas:
            geometry = area.geometry().constGet()
            intersects = spatial_index.intersects(geometry.boundingBox())

            # to force consistencies between subsequent runs, sort the index.
            # ideally each exposure feature needs to have prioritization
            # value/score to determine which hazard/aggregation it belongs to,
            # in case one feature were intersected with one or more high
            # hazard geometry. sorting the ids works by ignoring this
            # tendencies but still maintains consistencies for subsequent run.
            intersects.sort()

            # use prepared geometry: makes multiple intersection tests faster
            geometry_prepared = QgsGeometry.createGeometryEngine(geometry)
            geometry_prepared.prepareGeometry()

            # We need to loop over each intersections exposure / hazard.
            for i in intersects:
                building = exposure_features[i]
                building_geometry = building.geometry()

                if geometry_prepared.intersects(building_geometry.constGet()):
                    update_map[building.id()] = {}
                    for index, value in zip(indices, area.attributes()):
                        update_map[building.id()][index] = value

                    # We don't want this building again, let's remove it from
                    # the index.
                    spatial_index.deleteFeature(building)

        provider.changeAttributeValues(update_map)

    exposure.updateExtents()
    exposure.updateFields()

    exposure.keywords['inasafe_fields'].update(
        hazard.keywords['inasafe_fields'])
    exposure.keywords['layer_purpose'] = layer_purpose_exposure_summary['key']

    exposure.keywords['exposure_keywords'] = exposure.keywords.copy()
    exposure.keywords['aggregation_keywords'] = (
        hazard.keywords['aggregation_keywords'].copy())
    exposure.keywords['hazard_keywords'] = (
        hazard.keywords['hazard_keywords'].copy())

    exposure.keywords['title'] = output_layer_name

    check_layer(exposure)
    return exposure
Example #6
0
def __get_qgis_features(qgis_layer,
                        qgis_feature_request=None,
                        bbox_filter=None,
                        attribute_filters=None,
                        search_filter=None,
                        with_geometry=True,
                        page=None,
                        page_size=None,
                        ordering=None,
                        exclude_fields=None,
                        extra_expression=None,
                        extra_subset_string=None):
    """Private implementation for count and get"""

    if qgis_feature_request is None:
        qgis_feature_request = QgsFeatureRequest()

    if exclude_fields is not None:
        if exclude_fields == '__all__':
            qgis_feature_request.setNoAttributes()
        else:
            qgis_feature_request.setSubsetOfAttributes([
                name for name in qgis_layer.fields().names()
                if name not in exclude_fields
            ], qgis_layer.fields())

    expression_parts = []

    if extra_expression is not None:
        expression_parts.append(extra_expression)

    if not with_geometry:
        qgis_feature_request.setFlags(QgsFeatureRequest.NoGeometry)

    if bbox_filter is not None:
        assert isinstance(bbox_filter, QgsRectangle)
        qgis_feature_request.setFilterRect(bbox_filter)

    # Ordering
    if ordering is not None:
        ascending = True
        if ordering.startswith('-'):
            ordering = ordering[1:]
            ascending = False
        order_by = QgsFeatureRequest.OrderBy(
            [QgsFeatureRequest.OrderByClause('"%s"' % ordering, ascending)])
        qgis_feature_request.setOrderBy(order_by)

    # Search
    if search_filter is not None:
        exp_template = '"{field_name}" ILIKE \'%' + search_filter.replace(
            '\'', '\\\'') + '%\''
        exp_parts = []
        for f in qgis_layer.fields():
            exp_parts.append(
                exp_template.format(field_name=f.name().replace('"', '\\"')))
        expression_parts.append(' OR '.join(exp_parts))

    # Attribute filters
    if attribute_filters is not None:
        exp_parts = []
        for field_name, field_value in attribute_filters.items():
            exp_parts.append('"{field_name}" ILIKE \'%{field_value}%\''.format(
                field_name=field_name.replace('"', '\\"'),
                field_value=str(field_value).replace('\'', '\\\'')))
        expression_parts.append(' AND '.join(exp_parts))

    offset = 0
    feature_count = qgis_layer.featureCount()

    if page is not None and page_size is not None:
        page_size = int(page_size)
        page = int(page)
        offset = page_size * (page - 1)
        feature_count = page_size * page
        # Set to max, without taking filters into account
        qgis_feature_request.setLimit(feature_count)
    else:
        page_size = None  # make sure it's none

    # Fetch features
    if expression_parts:
        qgis_feature_request.combineFilterExpression(
            '(' + ') AND ('.join(expression_parts) + ')')

    logger.debug(
        'Fetching features from layer {layer_name} - filter expression: {filter} - BBOX: {bbox}'
        .format(layer_name=qgis_layer.name(),
                filter=qgis_feature_request.filterExpression(),
                bbox=qgis_feature_request.filterRect()))

    features = []

    original_subset_string = qgis_layer.subsetString()
    if extra_subset_string is not None:
        subset_string = original_subset_string
        if subset_string:
            qgis_layer.setSubsetString(
                "({original_subset_string}) AND ({extra_subset_string})".
                format(original_subset_string=original_subset_string,
                       extra_subset_string=extra_subset_string))
        else:
            qgis_layer.setSubsetString(extra_subset_string)

    iterator = qgis_layer.getFeatures(qgis_feature_request)

    try:
        for _ in range(offset):
            next(iterator)
        if page_size is not None:
            for __ in range(page_size):
                features.append(next(iterator))
        else:
            while True:
                features.append(next(iterator))
    except StopIteration:
        pass

    if extra_subset_string is not None:
        qgis_layer.setSubsetString(original_subset_string)

    return features
Example #7
0
    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(5, model_feedback)
        results = {}
        outputs = {}

        MNT = self.parameterAsRasterLayer(parameters, self.INPUT, context)
        emprise = self.parameterAsVectorLayer(parameters, self.EMPRISE,
                                              context)
        dZ = self.parameterAsDouble(parameters, self.DZ, context)
        fichier_html = self.parameterAsFileOutput(parameters, self.OUTPUT,
                                                  context)
        fichier_txt = "{}.txt".format(os.path.splitext(fichier_html)[0])

        if parameters[self.MAXZ] == None:
            maxZ = 99999
        else:
            maxZ = self.parameterAsDouble(parameters, self.MAXZ, context)

        # Découper un raster selon une couche de masquage
        alg_params = {
            "ALPHA_BAND": False,
            "CROP_TO_CUTLINE": True,
            "DATA_TYPE": 0,
            "EXTRA": "",
            "INPUT": MNT.source(),
            "KEEP_RESOLUTION": True,
            "MASK": emprise,
            "MULTITHREADING": False,
            "NODATA": None,
            "OPTIONS": "",
            "SET_RESOLUTION": False,
            "SOURCE_CRS": None,
            "TARGET_CRS": "ProjectCrs",
            "X_RESOLUTION": None,
            "Y_RESOLUTION": None,
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["Clip"] = processing.run(
            "gdal:cliprasterbymasklayer",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        feedback.setCurrentStep(1)
        if feedback.isCanceled():
            return {}

        # Polygones Courbes de niveau
        alg_params = {
            "BAND": 1,
            "CREATE_3D": False,
            "EXTRA": "",
            "FIELD_NAME_MAX": "ELEV_MAX",
            "FIELD_NAME_MIN": "ELEV_MIN",
            "IGNORE_NODATA": False,
            "INPUT": outputs["Clip"]["OUTPUT"],
            "INTERVAL": dZ,
            "NODATA": None,
            "OFFSET": 0,
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["PolygonesCourbesDeNiveau"] = processing.run(
            "gdal:contour_polygon",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        feedback.setCurrentStep(2)
        if feedback.isCanceled():
            return {}

        # Collecter les géométries
        alg_params = {
            "FIELD": ["ELEV_MIN"],
            "INPUT": outputs["PolygonesCourbesDeNiveau"]["OUTPUT"],
            "OUTPUT": QgsProcessing.TEMPORARY_OUTPUT,
        }
        outputs["CollectGeom"] = processing.run("native:collect",
                                                alg_params,
                                                context=context,
                                                feedback=feedback,
                                                is_child_algorithm=True)

        feedback.setCurrentStep(3)
        if feedback.isCanceled():
            return {}

        # Calcul "Surface"
        layer = QgsProcessingUtils.generateTempFilename("layer.shp")
        alg_params = {
            "FIELD_LENGTH": 12,
            "FIELD_NAME": "Surface",
            "FIELD_PRECISION": 2,
            "FIELD_TYPE": 0,
            "FORMULA": "$area",
            "INPUT": outputs["CollectGeom"]["OUTPUT"],
            "OUTPUT": layer,
        }
        outputs["CalculSurface"] = processing.run(
            "native:fieldcalculator",
            alg_params,
            context=context,
            feedback=feedback,
            is_child_algorithm=True,
        )

        feedback.setCurrentStep(4)
        if feedback.isCanceled():
            return {}

        fields = QgsFields()
        fields.append(QgsField("Z", QVariant.Double))
        fields.append(QgsField("Surface", QVariant.Double))
        fields.append(QgsField("Volume", QVariant.Double))
        (couche, dest_id) = self.parameterAsSink(
            parameters,
            self.OUTPUT2,
            context,
            fields,
            QgsWkbTypes.NoGeometry,
            QgsProject.instance().crs(),
        )

        with open(fichier_html, "w") as f_html, open(fichier_txt,
                                                     "w") as f_txt:
            f_html.write("""
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Courbe HSV</title>
    <style>
    html {
      font-family: sans-serif;
    }

    table {
      border-collapse: collapse;
      border: 2px solid rgb(200,200,200);
      letter-spacing: 1px;
      font-size: 0.8rem;
    }
    
    td, th {
      border: 1px solid rgb(190,190,190);
      padding: 10px 20px;
    }

    td {
      text-align: center;
    }

    caption {
      padding: 10px;
    }
    </style>
  </head>
  <body>
    <h1>Courbe HSV</h1>

    <table>
        <tr>
            <th>Z</th>
            <th>Surface (m&sup2;)</th>
            <th>Volume (m&sup3;)</th>
        </tr>
""")
            f_txt.write("Z\tSurface\tVolume\n")

            z = []
            surface = []
            volume = []

            vLayer = QgsVectorLayer(layer, "temp")

            request = QgsFeatureRequest()

            # Ordonner par ELEV_MIN ascendant
            clause = QgsFeatureRequest.OrderByClause("ELEV_MIN",
                                                     ascending=True)
            orderby = QgsFeatureRequest.OrderBy([clause])
            request.setOrderBy(orderby)

            for current, feat in enumerate(vLayer.getFeatures(request)):
                if feedback.isCanceled():
                    return {}
                if feat["ELEV_MAX"] > maxZ:
                    break
                if current == 0:
                    z.append(round(feat["ELEV_MAX"], 2))
                    surface.append(round(feat["Surface"], 2))
                    volume.append(round(feat["Surface"] * dZ / 2, 2))
                else:
                    z.append(round(feat["ELEV_MAX"], 2))
                    surface.append(round(surface[-1] + feat["Surface"], 2))
                    volume.append(
                        round(
                            feat["Surface"] * dZ / 2 + surface[-2] * dZ +
                            volume[-1], 2))

                self.writeHTMLTableLine(f_html, z[-1], surface[-1], volume[-1])
                f_txt.write("{}\t{}\t{}\n".format(z[-1], surface[-1],
                                                  volume[-1]))

                if couche is not None:
                    fet = QgsFeature()
                    tabAttr = [z[-1], surface[-1], volume[-1]]
                    fet.setAttributes(tabAttr)
                    couche.addFeature(fet)

            f_html.write("""
    </table>

  </body>
</html>
""")

        return {self.OUTPUT: fichier_html, self.OUTPUT2: dest_id}
Example #8
0
    def route(self):
        try:

            import urllib.request
            import json
            origin_dest = []
            featurelist = []
            if self.dlg.inputpoint.isChecked():
                vp_layer = self.dlg.point.currentLayer()
                countfeat = vp_layer.featureCount()

            else:
                vp_layer = iface.activeLayer()
                vp_layer.commitChanges()
                countfeat = vp_layer.featureCount()

            result = processing.run(
                "native:addautoincrementalfield", {
                    'INPUT': vp_layer,
                    'FIELD_NAME': 'id',
                    'START': 1,
                    'GROUP_FIELDS': [],
                    'SORT_EXPRESSION': '\"id\"',
                    'SORT_ASCENDING': True,
                    'SORT_NULLS_FIRST': False,
                    'OUTPUT': 'memory:{0}'.format(self.dlg.route_id.text())
                })
            QgsProject.instance().removeMapLayer(vp_layer)
            vp_layer = result['OUTPUT']
            QgsProject.instance().addMapLayer(vp_layer)
            features = vp_layer.getFeatures()
            points = []
            pointdist = []
            waypoints = []
            # if vp_layer.featureCount() == 2:
            for feature in vp_layer.getFeatures():
                point = feature.geometry().asPoint()
                xpoint = point.x()
                ypoint = point.y()
                Qpoint = QgsPointXY(xpoint, ypoint)
                points.append(Qpoint)
            distcheck = 0

            if self.dlg.direction.currentText() == 'Start->End':
                # for i in points:
                # distance = QgsDistanceArea()
                # Qpoint1 = i
                # for j in points:
                #     Qpoint2 = j
                #     dist = distance.measureLine(Qpoint1, Qpoint2)
                #     pointdist.append(dist)
                #     if dist > distcheck:
                #         distcheck = dist

                self.origin = points[0]
                self.destination = points[countfeat - 1]
                # print('End->Start', self.origin, self.destination)
            elif self.dlg.direction.currentText() == 'End->Start':
                # for i in points:
                #     distance = QgsDistanceArea()
                #     Qpoint1 = i
                #     for j in points:
                #         Qpoint2 = j
                #         dist = distance.measureLine(Qpoint1, Qpoint2)
                #         pointdist.append(dist)
                #         if dist > distcheck:
                #             distcheck = dist
                self.origin = points[countfeat - 1]
                self.destination = points[0]
                # print('Start->End', self.origin, self.destination)
            # print(vp_layer.featureCount())
            if vp_layer.featureCount() > 3:
                for i in range(countfeat - 1):
                    if i != 0 and i != countfeat - 1:
                        if self.dlg.direction.currentText() == 'Start->End':
                            if len(waypoints) < 1:
                                waypoints.append('optimize:true|via:' +
                                                 str(points[i].y()) + ',' +
                                                 str(points[i].x()))
                            else:
                                waypoints.append('via:' + str(points[i].y()) +
                                                 ',' + str(points[i].x()))
                        elif self.dlg.direction.currentText() == 'End->Start':
                            if len(waypoints) < 1:
                                waypoints.append(
                                    'optimize:true|via:' +
                                    str(points[countfeat - i].y()) + ',' +
                                    str(points[countfeat - i].x()))
                            else:
                                waypoints.append(
                                    'via:' + str(points[countfeat - i].y()) +
                                    ',' + str(points[countfeat - i].x()))
                print('|'.join(waypoints))
            elif vp_layer.featureCount() == 3:
                for i in points:
                    if i != self.origin and i != self.destination:
                        waypoints.append('optimize:true|via:' + str(i.y()) +
                                         ',' + str(i.x()))
            else:
                pass
            # print(waypoints[0], 'waypoints')

            # print(distcheck, 'dist')
            # print(pointdist, 'pointdist')
            # print(origin.x(),origin.y(), destination.x(),destination.y(), 'origin_dest')

            # vp_layer.select(origin)
            # vp_layer.select(destination)

            for feature in vp_layer.getFeatures():
                geometry = feature.geometry()
                origin_dest.append(
                    {geometry.asPoint().y(),
                     geometry.asPoint().x()})
            # print(origin_dest)
            endpoint = 'https://maps.googleapis.com/maps/api/directions/json?'
            APIkey = self.dlg.api.text()
            # mode = 'driving'
            origin_str = self.origin.y(), self.origin.x()
            destination_str = self.destination.y(), self.destination.x()
            # departure_time = (self.dlg.timeEdit.time().hour()*3600 + self.dlg.timeEdit.time().minute()*60+ self.dlg.timeEdit.time().second())
            # print(departure_time)
            import time
            import datetime
            # departure = self.totimestamp(self.dlg.timeEdit.dateTime())
            if self.dlg.nowtime.isChecked():
                departure = 'now'
            else:
                departure = self.dlg.timeEdit.dateTime().toSecsSinceEpoch()
            print(departure)
            if vp_layer.featureCount() > 3:

                if self.dlg.avoid.currentText() == 'None':
                    nav_request = 'origin={0},{1}&destination={2},{3}&waypoints={4}&departure_time={5}&mode={6}&model={7}&key={8}'.format(
                        self.origin.y(), self.origin.x(), self.destination.y(),
                        self.destination.x(), '|'.join(waypoints), departure,
                        self.dlg.mode.currentText(),
                        self.dlg.model.currentText(), api_key)
                else:
                    nav_request = 'origin={0},{1}&destination={2},{3}&waypoints={4}&departure_time={5}&avoid={6}&mode={7}&model={8}&key={9}'.format(
                        self.origin.y(), self.origin.x(), self.destination.y(),
                        self.destination.x(), '|'.join(waypoints), departure,
                        self.dlg.avoid.currentText(),
                        self.dlg.mode.currentText(),
                        self.dlg.model.currentText(), api_key)
            elif vp_layer.featureCount() == 3:
                if self.dlg.avoid.currentText() == 'None':
                    nav_request = 'origin={0},{1}&destination={2},{3}&waypoints={4}&departure_time={5}&mode={6}&model={7}&key={8}'.format(
                        self.origin.y(), self.origin.x(), self.destination.y(),
                        self.destination.x(), waypoints[0], departure,
                        self.dlg.mode.currentText(),
                        self.dlg.model.currentText(), api_key)
                else:
                    nav_request = 'origin={0},{1}&destination={2},{3}&waypoints={4}&departure_time={5}&avoid={6}&mode={7}&model={8}&key={9}'.format(
                        self.origin.y(), self.origin.x(), self.destination.y(),
                        self.destination.x(), waypoints[0], departure,
                        self.dlg.avoid.currentText(),
                        self.dlg.mode.currentText(),
                        self.dlg.model.currentText(), api_key)

            else:
                if self.dlg.avoid.currentText() == 'None':
                    nav_request = 'origin={0},{1}&destination={2},{3}&departure_time={4}&mode={5}&model={6}&key={7}'.format(
                        self.origin.y(), self.origin.x(), self.destination.y(),
                        self.destination.x(), departure,
                        self.dlg.mode.currentText(),
                        self.dlg.model.currentText(), api_key)
                    # print(nav_request)
                else:
                    nav_request = 'origin={0},{1}&destination={2},{3}&departure_time{4}&avoid={5}&mode={6}&model={7}&key={8}'.format(
                        self.origin.y(), self.origin.x(), self.destination.y(),
                        self.destination.x(), departure,
                        self.dlg.avoid.currentText(),
                        self.dlg.mode.currentText(),
                        self.dlg.model.currentText(), api_key)
            request = endpoint + nav_request
            print(request)
            response = urllib.request.urlopen(request).read()
            directions = json.loads(response)
            keys = directions.keys()
            # print(keys)
            # print(directions['error_message'], directions['routes'])
            routes = directions['routes']
            legs = routes[0]['legs']
            line = routes[0]['overview_polyline']
            # print(routes)
            points = polyline.decode(line['points'])
            self.route_layer = QgsVectorLayer(
                "Point?crs=EPSG:4326&field=route_id:String(100)&field=distance:String(100)&field=time:String(100)&field=ascending/descending:String(100)&field=departure_time:String(100)&field=roads_to_avoid:String(100)&field=traffic_model:String(100)",
                "route_points", "memory")
            provider = self.route_layer.dataProvider()
            # QgsProject.instance().addMapLayer(self.route_layer)
            if len(legs[0]['duration']['text'].split(' ')) == 2:
                duration1 = legs[0]['duration']['text'].split(' ')
                print(duration1, 'dur')
                duration = duration1[0]
            else:
                duration1 = legs[0]['duration']['text'].split(' ')
                print(duration1, 'dur1')
                duration = str((int(duration1[0]) * 60) + int(duration1[2]))

            self.route_layer.startEditing()

            route_attrib = [
                self.dlg.route_id.text(),
                legs[0]['distance']['text'].split(' ')[0], duration,
                self.dlg.direction.currentText(),
                (self.dlg.timeEdit.dateTime()),
                self.dlg.avoid.currentText(),
                self.dlg.model.currentText()
            ]
            # print((self.dlg.timeEdit.time()))
            # print(route_attrib)
            for i in points:
                outelem = QgsFeature(self.route_layer.fields())
                outelem.setGeometry(
                    QgsGeometry.fromPointXY(QgsPointXY(i[1], i[0])))
                outelem.setFields(self.route_layer.fields())
                outelem.setAttributes(route_attrib)
                featurelist.append(outelem)
                # print(outelem)
            self.route_layer.dataProvider().addFeatures(featurelist)
            self.route_layer.commitChanges()

            result = processing.run(
                "qgis:pointstopath", {
                    'INPUT': self.route_layer,
                    'ORDER_FIELD': 'route_id',
                    'GROUP_FIELD': None,
                    'DATE_FORMAT': '',
                    'OUTPUT': 'memory:'
                })

            if not self.dlg.checkBox.isChecked():
                route = QgsVectorLayer(
                    "Linestring?crs=EPSG:4326&field=route_id:String(100)&field=distance(km):String(100)&field=time(min):String(100)&field=ascending/descending:String(100)&field=departure_time:String(100)&field=duration_in_traffic(min):String(100)&field=roads_to_avoid:String(100)&field=traffic_model:String(100)&field=no_of_nodes:String(100)",
                    "route", "memory")
            else:
                # print(self.dlg.route_id.text(), route_attrib2)
                route = self.dlg.layer.currentLayer()
            fields = route.dataProvider().fields()
            field_name = [field.name() for field in fields]

            # line_layer = QgsVectorLayer(result['OUTPUT'], 'route')
            featurelist2 = []

            # if self.dlg.mode.currentText() not in ['walking', 'bicycling', 'transit']:
            if 'duration_in_traffic' in legs[0].keys():
                if len(legs[0]['duration']['text'].split(' ')) == 2:
                    duration1 = legs[0]['duration_in_traffic']['text'].split(
                        ' ')
                    print(duration1, 'dur12')
                    duration_in_traffic = duration1[0]
                else:
                    duration1 = legs[0]['duration_in_traffic']['text'].split(
                        ' ')
                    print(duration1, 'dur13')
                    duration_in_traffic = str((int(duration1[0]) * 60) +
                                              int(duration1[2]))
                if 'fid' in field_name:
                    route_attrib2 = [
                        route.featureCount(),
                        self.dlg.route_id.text(),
                        legs[0]['distance']['text'].split(' ')[0], duration,
                        self.dlg.direction.currentText(),
                        (self.dlg.timeEdit.dateTime()), duration_in_traffic,
                        self.dlg.avoid.currentText(),
                        self.dlg.model.currentText(),
                        str(self.route_layer.featureCount())
                    ]
                else:
                    route_attrib2 = [
                        self.dlg.route_id.text(),
                        legs[0]['distance']['text'].split(' ')[0], duration,
                        self.dlg.direction.currentText(),
                        (self.dlg.timeEdit.dateTime()), duration_in_traffic,
                        self.dlg.avoid.currentText(),
                        self.dlg.model.currentText(),
                        str(self.route_layer.featureCount())
                    ]
            else:
                if 'fid' in field_name:
                    route_attrib2 = [
                        route.featureCount(),
                        self.dlg.route_id.text(),
                        legs[0]['distance']['text'].split(' ')[0], duration,
                        self.dlg.direction.currentText(),
                        (self.dlg.timeEdit.dateTime()), 'None',
                        self.dlg.avoid.currentText(),
                        self.dlg.model.currentText(),
                        str(self.route_layer.featureCount())
                    ]
                else:
                    route_attrib2 = [
                        self.dlg.route_id.text(),
                        legs[0]['distance']['text'].split(' ')[0], duration,
                        self.dlg.direction.currentText(),
                        (self.dlg.timeEdit.dateTime()), duration_in_traffic,
                        self.dlg.avoid.currentText(),
                        self.dlg.model.currentText(),
                        str(self.route_layer.featureCount())
                    ]

                # print(field_name)
                # if 'fid' in field_name:
                #     # route.startEditing()
                #     # print(route.dataProvider().fieldNameIndex('fid'))
                #     # route.dataProvider().deleteAttributes([route.dataProvider().fieldNameIndex('fid')])
                #     # route.updateFields()
                #     # field_name = [field.name() for field in fields]
                #     # print(field_name)
                #
                #     route_attrib2 = [route.featureCount(), self.dlg.route_id.text(), legs[0]['distance']['text'].split(' ')[0], duration, self.dlg.direction.currentText(),(self.dlg.timeEdit.dateTime()),'None',self.dlg.avoid.currentText(), self.dlg.model.currentText(), str(self.route_layer.featureCount())]
                # else:
                #      pass
            provider = route.dataProvider()
            route.startEditing()

            request = QgsFeatureRequest()
            # set order by field
            clause = QgsFeatureRequest.OrderByClause('route_id',
                                                     ascending=False)
            orderby = QgsFeatureRequest.OrderBy([clause])
            request.setOrderBy(orderby)
            fields = route.dataProvider().fields()
            field_name = [field.name() for field in fields]
            for feature in result['OUTPUT'].getFeatures():
                outelem = QgsFeature(route.fields())
                outelem.setGeometry(feature.geometry())
                outelem.setFields(route.fields())
                if 'fid' not in field_name:
                    outelem.setAttributes(route_attrib2)
                else:
                    for index, field in enumerate(field_name):
                        if field != 'fid':
                            print(field, route_attrib2[index])
                            outelem[field] = route_attrib2[index]
                featurelist2.append(outelem)

            route.dataProvider().addFeatures(featurelist2)

            # route.updateFeature(feature)
            route.commitChanges()
            # QgsProject.instance().addMapLayer(route)
            if route.featureCount() == 1:
                file_path = os.path.abspath(
                    os.path.join(os.path.dirname(__file__), "route_style.qml"))
                route.loadNamedStyle(file_path)
            else:

                # result_del = processing.run("qgis:deletecolumn", {
                #     'INPUT': route,
                #     'COLUMN': ['gid'], 'OUTPUT': 'memory:'})
                # QgsProject.instance().addMapLayer(result_del['OUTPUT'])

                # result = processing.run("native:addautoincrementalfield",
                #                         {'INPUT': result_del['OUTPUT'],
                #                          'FIELD_NAME': 'gid', 'START': 1, 'GROUP_FIELDS': [],
                #                          'SORT_EXPRESSION': '',
                #                          'SORT_ASCENDING': False, 'SORT_NULLS_FIRST': False, 'OUTPUT': 'memory:route'})
                #
                # QgsProject.instance().removeMapLayer(route)
                # route = result['OUTPUT']
                QgsProject.instance().addMapLayer(route)
                # provide file name index and field's unique values
                # fni = route.dataProvider().fieldNameIndex('route_id')
                # unique_values = route.uniqueValues(fni)
                # fni2 = route.dataProvider().fieldNameIndex('route_id')
                # unique_values2 = route.uniqueValues(fni2)
                # unique_values2 = sorted(unique_values2)
                unique_values2 = []
                unique_values = []
                request = QgsFeatureRequest()
                # set order by field
                clause = QgsFeatureRequest.OrderByClause('route_id',
                                                         ascending=False)
                orderby = QgsFeatureRequest.OrderBy([clause])
                request.setOrderBy(orderby)
                for feature in route.getFeatures():
                    attrib = feature.attributes()
                    unique_values2.append(attrib[
                        route.dataProvider().fieldNameIndex('route_id')])
                    unique_values.append(attrib[
                        route.dataProvider().fieldNameIndex('route_id')])
                from random import randrange
                # fill categories
                categories = []
                # print(unique_values)
                # unique_values = sorted(unique_values)
                print(unique_values, unique_values2)
                for index, unique_value in enumerate(unique_values):
                    # initialize the default symbol for this geometry type
                    # symbol = QgsSymbol.defaultSymbol(route.geometryType())
                    # symbol = QgsSymbol.Symbol().setShape(QgsSimpleMarkerSymbolLayerBase.Star
                    # symbol.appendSymbolLayer(symbol_layer)
                    # configure a symbol layer
                    # sym = route.renderer().symbol()
                    # double headed
                    symbol = QgsSymbol.defaultSymbol(route.geometryType())
                    # double headed
                    sym_layer = QgsArrowSymbolLayer.create({
                        "arrow_width":
                        "1",
                        "arrow_start_width":
                        "1",
                        "head_length":
                        "1.5",
                        "head_thickness":
                        "1.5",
                        "head_type":
                        "0",
                        "arrow_type":
                        "0",
                        "is_curved":
                        "0",
                    })
                    fill_sym = QgsFillSymbol.createSimple({
                        "color":
                        '%d, %d, %d' % (randrange(0, 256), randrange(
                            0, 256), randrange(0, 256))
                    })
                    sym_layer.setSubSymbol(fill_sym)
                    symbol.changeSymbolLayer(0, sym_layer)
                    # layer_style = {}
                    # layer_style['color'] = '%d, %d, %d' % (randrange(0, 256), randrange(0, 256), randrange(0, 256))
                    # layer_style['outline'] = '#FF0000'
                    # # layer_style['width'] = '7.6'
                    # symbol_layer = QgsSimpleFillSymbolLayer.create(layer_style)

                    # replace default symbol layer with the configured one
                    # if symbol_layer is not None:
                    #     symbol.changeSymbolLayer(0, symbol_layer)
                    #     symbol.setWidth(0.66)

                    # create renderer object
                    category = QgsRendererCategory(unique_value, symbol,
                                                   str(unique_values2[index]))
                    # entry for the list of category items
                    categories.append(category)

                # create renderer object
                renderer = QgsCategorizedSymbolRenderer('route_id', categories)

                # assign the created renderer to the layer
                if renderer is not None:
                    route.setRenderer(renderer)
                route.triggerRepaint()

                ltl = QgsProject.instance().layerTreeRoot().findLayer(
                    route.id())
                ltm = iface.layerTreeView()
                # ltm.sortItems(0, Qt.AscendingOrder)
                # view = iface.layerTreeView()
                # ltm.model().AllowNodeReorder()
                index_newfeat = ltm.model().index(0, 0)
                node = ltm.model().index2node(index_newfeat)
                nodes = ltm.model().layerLegendNodes(node)
                legendNodes = ltm.model().layerLegendNodes(ltl)
                legend_dict = {}
                legend_dict[node.name()] = legendNodes
                # print(legend_dict)
                ltm.setSortingEnabled(True)
                ltm.sortByColumn(0, Qt.DescendingOrder)

                for index, ln in enumerate(legendNodes):
                    if index + 1 != route.featureCount():
                        ln.setData(Qt.Unchecked, Qt.CheckStateRole)
                # index_newfeat = ltm.model().index(route.featureCount()-1, 0)
                # print(index_newfeat)
                # node = ltm.model().index2node(index_newfeat)
                # print(node)
                # nodes = ltm.model().layerLegendNodes(node)
                # # layer_and_nodes[n.name()] = nodes
                # # print(layer_and_nodes)
                # # legend_get = ltm.model().index2legendNode(nodes)
                # print(nodes)

                # print(index, ln)
                # print(index, ltm.model().legendRootIndex(ln), ltm.model().legendNode2index(ln), ln, index_newfeat)
                # if index+1 != int(self.dlg.route_id.text()):

                # ln.setData(Qt.Checked, Qt.CheckStateRole)

            if not self.dlg.checkBox.isChecked():
                if self.dlg.output.text() != '':
                    path = self.dlg.output.text()
                    QgsVectorFileWriter.writeAsVectorFormat(
                        route, path, 'UTF-8', route.crs(), 'ESRI Shapefile')
                    # layer = QgsProject.instance().layerTreeRoot().findLayer(route.id())
                    # print(layer.name())
                    output = self.dlg.output.text().split('/')
                    route_path = QgsVectorLayer(
                        path, output[len(output) - 1].split('.')[0])
                    QgsProject.instance().addMapLayer(route_path)
                else:
                    QgsProject.instance().addMapLayer(route)
            listselect = []
            for index, feature in enumerate(route.getFeatures()):
                if index + 1 == route.featureCount():
                    listselect.append(feature.id())
            route.select(listselect)
            iface.actionZoomToSelected().trigger()

        except Exception as e:
            alert = QMessageBox()
            alert.setWindowTitle('Alert')

            if self.dlg.mode.currentText() in [
                    'walking', 'bicycling', 'transit'
            ]:
                alert.setText(
                    str(e) + '\nRoute not available for selected mode.')
            else:
                alert.setText(str(e))
            result = alert.exec_()
            print(e)
    def processAlgorithm(self, parameters, context, feedback):
        #retrieve the layer inputs
        source1 = self.parameterAsSource(
            parameters,
            self.INPUT1,
            context
        )
        source2 = self.parameterAsSource(
            parameters,
            self.INPUT2,
            context
        )
        source3 = self.parameterAsSource(
            parameters,
            self.INPUT3,
            context
        )
        source4 = self.parameterAsSource(
            parameters,
            self.INPUT4,
            context
        )
        source5 = self.parameterAsSource(
            parameters,
            self.INPUT5,
            context
        )
        source6 = self.parameterAsSource(
            parameters,
            self.INPUT6,
            context
        )
        source7 = self.parameterAsSource(
            parameters,
            self.INPUT7,
            context
        )
        source8 = self.parameterAsSource(
            parameters,
            self.INPUT8,
            context
        )

        #if a layer was not found, throw an exception to indicate that the algorithm encountered a fatal error
        if source1 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT1))
        if source2 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT2))
        if source3 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT3))
        if source4 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT4))
        if source5 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT5))
        if source6 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT6))
        if source7 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT7))
        if source8 is None:
            raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT8))
        
        feedback.pushInfo('--------------------------------------------------------------------------')
        feedback.pushInfo("1/5 - Checking CRS...")
        feedback.pushInfo('--------------------------------------------------------------------------')
        
        #get crs of each layer
        crs1 = source1.sourceCrs().authid()
        crs2 = source2.sourceCrs().authid()
        crs3 = source3.sourceCrs().authid()
        crs4 = source4.sourceCrs().authid()
        crs5 = source5.sourceCrs().authid()
        crs6 = source6.sourceCrs().authid()
        crs7 = source7.sourceCrs().authid()
        crs8 = source8.sourceCrs().authid()
        
        #check crs of each layer and stop script if not all matching
        if crs1 == crs2 and crs1 == crs3 and crs1 == crs4 and crs1 == crs5 and crs1 == crs6 and crs1 == crs7 and crs1 == crs8:
            feedback.pushInfo('CRS is ' + (crs1))            
            feedback.pushInfo('CRS is matching for all layers')
        else:   
            feedback.pushInfo('Please ensure matching CRS for all layers')
            feedback.pushInfo('CRS for INPUT1 is ' + (crs1))
            feedback.pushInfo('CRS for INPUT2 is ' + (crs2))
            feedback.pushInfo('CRS for INPUT3 is ' + (crs3))
            feedback.pushInfo('CRS for INPUT4 is ' + (crs4))
            feedback.pushInfo('CRS for INPUT5 is ' + (crs5))
            feedback.pushInfo('CRS for INPUT6 is ' + (crs6))
            feedback.pushInfo('CRS for INPUT7 is ' + (crs7))
            feedback.pushInfo('CRS for INPUT8 is ' + (crs8))
            return{}
         
        #check if script has been cancelled before next stage
        if feedback.isCanceled():
            return{}   
        
        #output current stage of script
        feedback.pushInfo('--------------------------------------------------------------------------') 
        feedback.pushInfo("2/5 - Preparing Layer Data...")
        feedback.pushInfo('--------------------------------------------------------------------------')

        #get the layer data from the inputs
        lgaLayer = parameters['INPUT1']
        parcelsLayer = parameters['INPUT2']
        addressLayer = parameters['INPUT3']
        zonesLayer = parameters['INPUT4']
        overlaysLayer = parameters['INPUT5']
        floodLayer = parameters['INPUT6']
        coastLayer = parameters['INPUT7']
        watercourseLayer = parameters['INPUT8']
        
        #clip flood area to LGA
        result = processing.run('native:clip', { 'INPUT': floodLayer, 'OUTPUT': 'memory:', 'OVERLAY': lgaLayer }, context=context, feedback=feedback)
        floodLayer = result["OUTPUT"]
        
        #create a flood check layer (coast and waterways)
        floodCheckList = [coastLayer,watercourseLayer]
        result = processing.run('native:mergevectorlayers', {"LAYERS": floodCheckList, "OUTPUT": 'memory:' }, context=context, feedback=feedback)
        floodCheckLayer = result["OUTPUT"]
        
        #clean up flood layer (keep only areas that intersect with flood check layer)
        result = processing.run('native:extractbylocation', { 'INPUT': floodLayer, 'INTERSECT': floodCheckLayer, 'METHOD': 0, 'OUTPUT': 'memory:', 'PREDICATE': [0] }, context=context, feedback=feedback)
        floodCleanedLayer = result["OUTPUT"]
        
        #get total area of LGA
        result = processing.run('qgis:exportaddgeometrycolumns', { 'CALC_METHOD': 0, 'INPUT': lgaLayer, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        lgaAreaLayer = result["OUTPUT"]
        
        #get zone areas that intersect with flood layer
        result = processing.run('native:extractbylocation', { 'INPUT': zonesLayer, 'INTERSECT': floodCleanedLayer, 'METHOD': 0, 'OUTPUT': 'memory:', 'PREDICATE': [0] }, context=context, feedback=feedback)
        zonesLayer = result["OUTPUT"]
        
        #add "ZONE_CLASS" field to zones layer
        zonesLayer.startEditing()
        zonesLayer.dataProvider().addAttributes([QgsField("ZONE_CLASS", QVariant.String)])
        zonesLayer.updateFields()
        zonesLayer.commitChanges()
        
        #add "ZONE_CLASS" attribute for each feature ("ZONE_CODE" without numeric digit)
        zFeatures = zonesLayer.getFeatures()
        zonesLayer.startEditing()
        for feature in zFeatures:
            ini_string = feature["ZONE_CODE"]
            res = ''.join([i for i in ini_string if not i.isdigit()]) 
            feature["ZONE_CLASS"] = res
            zonesLayer.updateFeature(feature)
        zonesLayer.commitChanges()
        
        #check if script has been cancelled before next stage
        if feedback.isCanceled():
            return{}      
        
        #output current stage of script
        feedback.pushInfo('--------------------------------------------------------------------------')
        feedback.pushInfo("3/5 - Calculating Flooded Area...")
        feedback.pushInfo('--------------------------------------------------------------------------')
        
        #clip zones layer to the flooded area
        #NOTE: must turn off invalid features filtering in QGIS - otherwise this process won't work as some geometry is invalid
        result = processing.run('native:clip', { 'INPUT': zonesLayer, 'OUTPUT': 'memory:', 'OVERLAY': floodCleanedLayer }, context=context, feedback=feedback)
        floodedZonesLayer = result["OUTPUT"]
        
        #group by "ZONE_CLASS"
        result = processing.run('native:dissolve', { 'FIELD': ['ZONE_CLASS'], 'INPUT': floodedZonesLayer, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        floodedZonesLayerDissolved = result["OUTPUT"]
        
        #add area for each "ZONE_CLASS"
        result = processing.run('qgis:exportaddgeometrycolumns', { 'CALC_METHOD': 0, 'INPUT': floodedZonesLayerDissolved, 'OUTPUT': 'memory:Flooded Area' }, context=context, feedback=feedback)
        floodedZonesAreaLayer = result["OUTPUT"]
        
        #add the "Flooded Area" layer to the layers panel
        #first add the layer without showing it
        QgsProject.instance().addMapLayer(floodedZonesAreaLayer, False)
        #obtain the layer tree of the top-level group in the project
        layerTree = iface.layerTreeCanvasBridge().rootGroup()
        #insert the layer - the position is a number starting from 0, with -1 an alias for the end
        layerTree.insertChildNode(-1, QgsLayerTreeLayer(floodedZonesAreaLayer))
        
        #customise the symbology for the "Flooded Area" layer
        layer = QgsProject.instance().mapLayersByName("Flooded Area")[0]
        single_symbol_renderer = layer.renderer()
        symbol = single_symbol_renderer.symbol()
        symbol.setColor(QColor.fromRgb(150, 206, 250))
        symbol.symbolLayer(0).setStrokeColor(QColor.fromRgb(70, 130, 180))
        symbol.setOpacity(0.3)
        layer.triggerRepaint()
        qgis.utils.iface.layerTreeView().refreshLayerSymbology(layer.id())
        
        #check if script has been cancelled before next stage
        if feedback.isCanceled():
            return{}
        
        #output current stage of script
        feedback.pushInfo('--------------------------------------------------------------------------')
        feedback.pushInfo("4/5 - Calculating Flood-Affected Private Parcels...")
        feedback.pushInfo('--------------------------------------------------------------------------')
        
        #get all flood-affected parcels
        result = processing.run('native:extractbylocation', { 'INPUT': parcelsLayer, 'INTERSECT': floodCleanedLayer, 'METHOD': 0, 'OUTPUT': 'memory:', 'PREDICATE': [0] }, context=context, feedback=feedback)
        floodAffectedParcelsOriginal = result["OUTPUT"]
        
        #create inside buffer on zones layer (to avoid zones touching other parcels when intersecting)
        result = processing.run('native:buffer', { 'DISSOLVE': False, 'DISTANCE': -1, 'END_CAP_STYLE': 0, 'INPUT': zonesLayer, 'JOIN_STYLE': 0, 'MITER_LIMIT': 2, 'OUTPUT' : 'memory:', 'SEGMENTS': 200 }, context=context, feedback=feedback)
        zonesLayerClean = result["OUTPUT"]
        
        #exclude irrelevant zones (public zones, PZ and UFZ)
        request = QgsFeatureRequest().setFilterExpression("\"ZONE_CLASS\" = \'PCRZ\' OR \"ZONE_CLASS\" = \'PPRZ\' OR \"ZONE_CLASS\" = \'PUZ\' OR \"ZONE_CLASS\" = \'RDZ\' OR \"ZONE_CLASS\" = \'CA\' OR \"ZONE_CLASS\" = \'PZ\' OR \"ZONE_CLASS\" = \'UFZ\'")
        ids = [f.id() for f in zonesLayerClean.getFeatures(request)]
        zonesLayerClean.startEditing()
        for fid in ids:
            zonesLayerClean.deleteFeature(fid)
        zonesLayerClean.commitChanges()
        
        #add zone data to parcels
        result = processing.run('qgis:joinattributesbylocation', { 'DISCARD_NONMATCHING': True, 'INPUT': floodAffectedParcelsOriginal, 'JOIN': zonesLayerClean, 'METHOD': 1, 'OUTPUT': 'memory:', 'PREDICATE': [0] }, context=context, feedback=feedback)
        floodAffectedParcelsAll = result["OUTPUT"]
          
        #delete parcels with duplicate geometries - a work-around because 'qgis:deleteduplicategeometries' was causing crashes on return{}
        result = processing.run('qgis:exportaddgeometrycolumns', { 'CALC_METHOD': 0, 'INPUT': floodAffectedParcelsAll, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        floodAffectedParcelsArea = result["OUTPUT"]
        result = processing.run('native:removeduplicatesbyattribute', { 'FIELDS': ['area','perimeter'], 'INPUT': floodAffectedParcelsArea, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        floodAffectedParcels = result["OUTPUT"]

        #delete irrelevant parcels by attribute
        #"PC_LOTNO" LIKE 'CM%' = driveways, carparking and building surrounds
        #"PC_LOTNO" LIKE 'R%' = park reserves
        #"PC_STAT" = 'P' = proposed parcels
        #"PC_CRSTAT" = 'C' = crown parcels
        #"PC_CRSTAT" = 'G' = road reserves
        #"PC_SPIC" = '200' = shared driveways
        request = QgsFeatureRequest().setFilterExpression("\"PC_LOTNO\" LIKE \'CM%\' OR \"PC_LOTNO\" LIKE \'R%\' OR \"PC_STAT\" = \'P\' OR \"PC_CRSTAT\" = \'C\' OR \"PC_CRSTAT\" = \'G\' OR \"PC_SPIC\" = \'200\'")
        ids = [f.id() for f in floodAffectedParcels.getFeatures(request)]
        floodAffectedParcels.startEditing()
        for fid in ids:
            floodAffectedParcels.deleteFeature(fid)
        floodAffectedParcels.commitChanges()
        
        #remove address points without a specified address number
        result = processing.run('native:extractbyexpression', {'EXPRESSION': '(\"BUNIT_ID1\" != \'0\' OR \"BUNIT_ID2\" != \'0\' OR \"FLOOR_NO_1\" != \'0\' OR \"FLOOR_NO_2\" != \'0\' OR \"HSE_NUM1\" != \'0\' OR \"HSE_NUM2\" != \'0\' OR \"DISP_NUM1\" != \'0\' OR \"DISP_NUM2\" != \'0\')', 'INPUT': addressLayer, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        addressLayerClean = result["OUTPUT"]

        #get parcels with a valid address point
        result = processing.run('native:extractbylocation', { 'INPUT': floodAffectedParcels, 'INTERSECT': addressLayerClean, 'METHOD': 0, 'OUTPUT': 'memory:', 'PREDICATE': [0] }, context=context, feedback=feedback)
        floodAffectedPrivateParcels = result["OUTPUT"]
        
        #remove parcels under 40sqm (indicates it is not a regular private land parcel)
        result = processing.run('native:extractbyexpression', { 'EXPRESSION': '(\"area\" > \'40\')', 'INPUT': floodAffectedPrivateParcels, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        finalParcelsAreaClean = result["OUTPUT"]
        
        #delete parcels with inner rings (indicates it is not a regular private land parcel)
        #add "POLY_RING" field to parcels layer
        finalParcelsAreaClean.startEditing()
        finalParcelsAreaClean.dataProvider().addAttributes([QgsField("POLY_RING", QVariant.Double)])
        finalParcelsAreaClean.updateFields()
        finalParcelsAreaClean.commitChanges()
        
        #add "POLY_RING" attribute for each feature (count rings for each polygon within feature geometry)
        pFeatures = finalParcelsAreaClean.getFeatures()
        finalParcelsAreaClean.startEditing()
        for feature in pFeatures:
            geometry = feature.geometry()
            polyCount = 0
            ringCount = 0
            if geometry.isMultipart():
                polygons = geometry.asMultiPolygon()
            else:
                polygons = geometry.asPolygon()
            for polygon in polygons:
                polyCount = polyCount + 1
                for ring in polygon:
                    ringCount = ringCount + 1
                count = (ringCount/polyCount)
                feature["POLY_RING"] = count
                finalParcelsAreaClean.updateFeature(feature) 
        finalParcelsAreaClean.commitChanges()
        
        #delete features with more rings than polygons (indicates it has an inner ring)
        request = QgsFeatureRequest().setFilterExpression("\"POLY_RING\" > \'1\'")
        ids = [f.id() for f in finalParcelsAreaClean.getFeatures(request)]
        finalParcelsAreaClean.startEditing()
        for fid in ids:
            finalParcelsAreaClean.deleteFeature(fid)
        finalParcelsAreaClean.commitChanges()
        
        #get relevant flood control overlays
        result = processing.run('native:extractbyexpression', { 'EXPRESSION': '(\"ZONE_CODE\" = \'SBO\' OR \"ZONE_CODE\" = \'LSIO\' OR \"ZONE_CODE\" = \'FO\')', 'INPUT': overlaysLayer, 'OUTPUT': 'memory:' }, context=context, feedback=feedback)
        relevantOverlays = result["OUTPUT"]
        
        #assign parcels with relevant flood control overlays (if intersection)
        result = processing.run('qgis:joinattributesbylocation', { 'DISCARD_NONMATCHING': False, 'INPUT': finalParcelsAreaClean, 'JOIN': relevantOverlays, 'METHOD': 1, 'OUTPUT': 'memory:Flood-Affected Private Parcels', 'PREDICATE': [0] }, context=context, feedback=feedback)
        finalParcels = result["OUTPUT"]
        
        #add the "Flood-Affected Private Parcels" layer to the layers panel
        #first add the layer without showing it
        QgsProject.instance().addMapLayer(finalParcels, False)
        #obtain the layer tree of the top-level group in the project
        layerTree = iface.layerTreeCanvasBridge().rootGroup()
        #insert the layer - the position is a number starting from 0, with -1 an alias for the end
        layerTree.insertChildNode(-1, QgsLayerTreeLayer(finalParcels))
        
        #customise the symbology for the "Flood-Affected Private Parcels" layer
        layer = QgsProject.instance().mapLayersByName("Flood-Affected Private Parcels")[0]
        single_symbol_renderer = layer.renderer()
        symbol = single_symbol_renderer.symbol()
        symbol.setColor(QColor.fromRgb(225, 225, 225))
        symbol.symbolLayer(0).setStrokeColor(QColor.fromRgb(115, 115, 115))
        layer.triggerRepaint()
        qgis.utils.iface.layerTreeView().refreshLayerSymbology(layer.id())
        
        #check if script has been cancelled before next stage
        if feedback.isCanceled():
            return{}
        
        #output current stage of script
        feedback.pushInfo('--------------------------------------------------------------------------')
        feedback.pushInfo("5/5 - Calculating statistics...")
        feedback.pushInfo('--------------------------------------------------------------------------')
        feedback.pushInfo("FLOODED AREA (sqm):")
        feedback.pushInfo('--------------------------------------------------------------------------')

        #get lga area
        lgaFeatures = lgaAreaLayer.getFeatures()
        for feature in lgaFeatures:
            lgaArea = feature["area"]
        
        #get flooded lga area
        lgaFloodArea = 0
        zoneFeatures = floodedZonesAreaLayer.getFeatures()
        for feature in zoneFeatures:
            zoneArea = feature["area"]
            lgaFloodArea = lgaFloodArea + zoneArea
        
        #get flooded area percentage
        floodPercent = ((lgaFloodArea/lgaArea)*100)
        
        #ouput flooded lga area and percentage
        feedback.pushInfo('Flooded Area: ' + (str(round(lgaFloodArea, 2))))
        feedback.pushInfo((str(round(floodPercent, 2))) + '% of LGA Area')
        feedback.pushInfo('BY ZONE:')
        feedback.pushInfo('---------------------------------')
        
        #get and output flooded area for each "ZONE_CLASS"
        request = QgsFeatureRequest()
        clause = QgsFeatureRequest.OrderByClause('area', ascending=False)
        orderby = QgsFeatureRequest.OrderBy([clause])
        request.setOrderBy(orderby)
        zoneFeatures = floodedZonesAreaLayer.getFeatures(request)
        for feature in zoneFeatures:
            zoneName = feature["ZONE_CLASS"]
            zoneArea = feature["area"]
            feedback.pushInfo((zoneName) + ': ' + (str(round(zoneArea, 2))))
            lgaFloodArea = lgaFloodArea + zoneArea
        
        #output current stage of script
        feedback.pushInfo('--------------------------------------------------------------------------')
        feedback.pushInfo("FLOOD-AFFECTED PRIVATE PARCELS:")
        feedback.pushInfo('--------------------------------------------------------------------------')
        
        #get counts for ALL flood-affected private parcels and their flood overlays
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"area\" > \'0\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        allCount = finalParcels.selectedFeatureCount()
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        allCountSBO = finalParcels.selectedFeatureCount()
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        allCountLSIO = finalParcels.selectedFeatureCount()
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        allCountFO = finalParcels.selectedFeatureCount()
        
        #output total parcel counts and by flood overlay
        feedback.pushInfo('Flood-Affected Private Parcels: ' + (str(allCount)))
        feedback.pushInfo('With SBO Overlay: ' + (str(allCountSBO)))
        feedback.pushInfo('With LSIO Overlay: ' + (str(allCountLSIO)))
        feedback.pushInfo('With FO Overlay: ' + (str(allCountFO)))
        feedback.pushInfo('BY ZONE:')
        feedback.pushInfo('---------------------------------')
        
        #get counts for GRZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'GRZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        grzCount = finalParcels.selectedFeatureCount()
        if grzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'GRZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            grzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'GRZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            grzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'GRZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            grzCountFO = finalParcels.selectedFeatureCount()
        
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total General Residential (GRZ): ' + (str(grzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(grzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(grzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(grzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for RZ flood-affected private parcels and thier flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        rzCount = finalParcels.selectedFeatureCount()
        if rzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            rzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            rzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            rzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Residential (RZ): ' + (str(rzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(rzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(rzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(rzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for RGZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RGZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        rgzCount = finalParcels.selectedFeatureCount()
        if rgzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RGZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            rgzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RGZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            rgzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'RGZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            rgzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Residential Growth (RGZ): ' + (str(rgzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(rgzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(rgzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(rgzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for MUZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'MUZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        muzCount = finalParcels.selectedFeatureCount()
        if muzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'MUZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            muzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'MUZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            muzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'MUZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            muzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Mixed Use (MUZ): ' + (str(muzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(muzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(muzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(muzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for CZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        czCount = finalParcels.selectedFeatureCount()
        if czCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            czCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            czCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            czCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Commercial (CZ): ' + (str(czCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(czCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(czCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(czCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for BZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'BZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        bzCount = finalParcels.selectedFeatureCount()
        if bzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'BZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            bzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'BZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            bzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'BZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            bzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Commercial (BZ): ' + (str(bzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(bzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(bzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(bzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for INZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'INZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        inzCount = finalParcels.selectedFeatureCount()
        if inzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'INZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            inzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'INZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            inzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'INZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            inzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Industrial (INZ): ' + (str(inzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(inzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(inzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(inzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for SUZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'SUZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        suzCount = finalParcels.selectedFeatureCount()
        if suzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'SUZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            suzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'SUZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            suzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'SUZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            suzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Special Use (SUZ): ' + (str(suzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(suzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(suzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(suzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #get counts for CDZ flood-affected private parcels and their flood overlays (if applicable)
        processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CDZ\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
        cdzCount = finalParcels.selectedFeatureCount()
        if cdzCount > 0:
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CDZ\' AND \"ZONE_CODE_2\" = \'SBO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            cdzCountSBO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CDZ\' AND \"ZONE_CODE_2\" = \'LSIO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            cdzCountLSIO = finalParcels.selectedFeatureCount()
            processing.run('qgis:selectbyexpression', { 'EXPRESSION': '(\"ZONE_CLASS\" = \'CDZ\' AND \"ZONE_CODE_2\" = \'FO\')', 'INPUT': finalParcels, 'METHOD': 0 }, context=context)
            cdzCountFO = finalParcels.selectedFeatureCount()
            
            #output total parcel counts and by flood overlay
            feedback.pushInfo('Total Comprehensive Development (CDZ): ' + (str(cdzCount)))
            feedback.pushInfo('With SBO Overlay: ' + (str(cdzCountSBO)))
            feedback.pushInfo('With LSIO Overlay: ' + (str(cdzCountLSIO)))
            feedback.pushInfo('With FO Overlay: ' + (str(cdzCountFO)))
            feedback.pushInfo('---------------------------------')
        
        #end the script
        return{}