def test_FeatureRequestSortByVirtualField(self): layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer", "addfeat", "memory") pr = layer.dataProvider() f1 = QgsFeature() f1.setAttributes(["test", 123]) f2 = QgsFeature() f2.setAttributes(["test", 124]) self.assertTrue(pr.addFeatures([f1, f2])) idx = layer.addExpressionField('if("fldint"=123,3,2)', QgsField('exp1', QVariant.LongLong)) # NOQA QgsProject.instance().addMapLayers([layer]) request = QgsFeatureRequest() request.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause('exp1', True)])) ids = [] for feat in layer.getFeatures(request): ids.append(feat.id()) self.assertEqual(ids, [2, 1]) request.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause('exp1', False)])) ids = [] for feat in layer.getFeatures(request): ids.append(feat.id()) self.assertEqual(ids, [1, 2]) QgsProject.instance().removeMapLayers([layer.id()])
def test_ZFeatureRequestSortByAuxiliaryField(self): s = QgsAuxiliaryStorage() self.assertTrue(s.isValid()) layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer", "addfeat", "memory") pr = layer.dataProvider() f1 = QgsFeature() f1.setAttributes(["test", 123]) f2 = QgsFeature() f2.setAttributes(["test", 124]) self.assertTrue(pr.addFeatures([f1, f2])) # Create a new auxiliary layer with 'pk' as key pkf = layer.fields().field(layer.fields().indexOf('fldint')) al = s.createAuxiliaryLayer(pkf, layer) self.assertTrue(al.isValid()) layer.setAuxiliaryLayer(al) prop = QgsPropertyDefinition() prop.setComment('test_field') prop.setDataType(QgsPropertyDefinition.DataTypeNumeric) prop.setOrigin('user') prop.setName('custom') self.assertTrue(al.addAuxiliaryField(prop)) layer.startEditing() i = 2 for feat in layer.getFeatures(): feat.setAttribute(2, i) layer.updateFeature(feat) i -= 1 layer.commitChanges() request = QgsFeatureRequest() request.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause(layer.fields()[2].name(), True)])) ids = [] for feat in layer.getFeatures(request): ids.append(feat.id()) self.assertEqual(ids, [2, 1]) request.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause(layer.fields()[2].name(), False)])) ids = [] for feat in layer.getFeatures(request): ids.append(feat.id()) self.assertEqual(ids, [1, 2]) QgsProject.instance().removeMapLayers([layer.id()])
def test_FeatureRequestSortByJoinField(self): """ test sorting requested features using a joined columns """ joinLayer = QgsVectorLayer( "Point?field=x:string&field=y:integer&field=z:integer", "joinlayer", "memory") pr = joinLayer.dataProvider() f1 = QgsFeature() f1.setAttributes(["foo", 123, 321]) f2 = QgsFeature() f2.setAttributes(["bar", 124, 654]) self.assertTrue(pr.addFeatures([f1, f2])) layer = QgsVectorLayer("Point?field=fldtxt:string&field=fldint:integer", "addfeat", "memory") pr = layer.dataProvider() f1 = QgsFeature() f1.setAttributes(["test", 123]) f2 = QgsFeature() f2.setAttributes(["test", 124]) self.assertTrue(pr.addFeatures([f1, f2])) QgsProject.instance().addMapLayers([layer, joinLayer]) join = QgsVectorLayerJoinInfo() join.setTargetFieldName("fldint") join.setJoinLayer(joinLayer) join.setJoinFieldName("y") join.setUsingMemoryCache(True) layer.addJoin(join) request = QgsFeatureRequest() request.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause('joinlayer_z', True)])) ids = [] for feat in layer.getFeatures(request): ids.append(feat.id()) self.assertEqual(ids, [1, 2]) request.setOrderBy(QgsFeatureRequest.OrderBy([QgsFeatureRequest.OrderByClause('joinlayer_z', False)])) ids = [] for feat in layer.getFeatures(request): ids.append(feat.id()) self.assertEqual(ids, [2, 1]) QgsProject.instance().removeMapLayers([layer.id(), joinLayer.id()])
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
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²)</th> <th>Volume (m³)</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}
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{}