def get_editable_layers() -> Set[str]: """Liste der Tabellen, für die in der Layerliste der Status editable aktiviert ist. Dient dazu, sicherzustellen, dass keine Datenbankoperationen auf editierbare Layer zugreifen.""" elayers = set([]) # Zuerst leere Liste anlegen layers = [ x.layer() for x in iface.layerTreeCanvasBridge().rootGroup().findLayers() ] # logger.debug(u'Layerliste erstellt') if len(layers) > 0: # über Layer iterieren for lay in layers: lyattr = {} # Attributstring für Layer splitten for le in lay.source().split(" "): if "=" in le: key, value = le.split("=", 1) lyattr[key] = value.strip('"').strip("'") # Falls Abschnitte 'table' und 'dbname' existieren, handelt es sich um einen Datenbank-Layer if "table" in lyattr and "dbname" in lyattr: if lay.isEditable(): elayers.add(lyattr["table"]) return elayers
def processAlgorithm(self, parameters, context, feedback): # Set the standard CRS to GDA2020 (EPSG:7844) standardCRS = "EPSG:7844" #Parse the RE numbers res = re.sub( "[^0-9a-zA-Z.-]+", ',', self.parameterAsString(parameters, self.INPUT, context).lower()).split(',') res_message = "Regional ecosystem(s):" + ",".join(res) outputDIR = self.parameterAsFileOutput(parameters, self.OUTPUTDIR, context) PrimaryOnly = self.parameterAsBoolean(parameters, self.LOADPRIMARY, context) Exact = self.parameterAsBoolean(parameters, self.LOADEXACT, context) feedback.setProgress(5) feedback.setProgressText(res_message) # The following lines workaround an apparent QGIS bug where a temporary directory isn't actually made. try: os.mkdir(outputDIR) except FileExistsError as e: pass #Get RE polygon(s) RElayer = self.LoadRELayer(res, outputDIR, standardCRS, context, feedback, PrimaryOnly, Exact) # if RElayer == None or not RElayer.isValid( ) or RElayer.featureCount() < 1: feedback.reportError("Failed to load REs! Invalid REs?", True) return {} # Load layer to canvas project = QgsProject.instance() project.addMapLayer(RElayer, False) layerTree = iface.layerTreeCanvasBridge().rootGroup() layerTree.insertChildNode(0, QgsLayerTreeLayer(RElayer)) feedback.setProgress(99) # #Zoom to extent canvas = iface.mapCanvas() canvasCRS = QgsCoordinateReferenceSystem( canvas.mapSettings().destinationCrs().authid()) reCRS = QgsCoordinateReferenceSystem(RElayer.crs()) xform = QgsCoordinateTransform(reCRS, canvasCRS, project) canvas.setExtent(xform.transform(RElayer.extent())) # # # feedback.setProgress(100) # return {}
# coding: utf-8 from qgis.gui import QgsCustomLayerOrderWidget from qgis.utils import iface layer_tree_canvas_bridge = iface.layerTreeCanvasBridge() custom_layer_order_widget = QgsCustomLayerOrderWidget( layer_tree_canvas_bridge ) custom_layer_order_widget.show()
current_date_layer = create_current_date() # Bar date def create_bar_date(): bar_today_layer = QgsVectorLayer( "Point?" "crs=epsg:3857&" "field=id:integer&" "index=yes", "Bar", "memory") QgsProject.instance().addMapLayer(bar_today_layer, False) bar_today_layer.loadNamedStyle(os.path.join(FOLDER, 'qml', 'bar.qml')) with edit(bar_today_layer): feat = QgsFeature() feat.setAttributes([1]) feat.setGeometry(QgsGeometry.fromPointXY(QgsPointXY(0, 0))) bar_today_layer.addFeature(feat) return bar_today_layer bar_today_layer = create_bar_date() # Insert in the correct order layerTree = iface.layerTreeCanvasBridge().rootGroup() layerTree.insertChildNode(-1, QgsLayerTreeLayer(current_date_layer)) layerTree.insertChildNode(-1, QgsLayerTreeLayer(releases_point_layer)) layerTree.insertChildNode(-1, QgsLayerTreeLayer(axes_layer)) layerTree.insertChildNode(-1, QgsLayerTreeLayer(releases_line_layer)) layerTree.insertChildNode(-1, QgsLayerTreeLayer(bar_today_layer))
# coding: utf-8 from qgis.gui import QgsCustomLayerOrderWidget from qgis.utils import iface layer_tree_canvas_bridge = iface.layerTreeCanvasBridge() custom_layer_order_widget = QgsCustomLayerOrderWidget(layer_tree_canvas_bridge) custom_layer_order_widget.show()
def processAlgorithm(self, parameters, context, feedback): # Set the standard CRS to GDA2020 (EPSG:7844) standardCRS = "EPSG:7844" #Parse the lot plan numbers lots = re.sub( '[^0-9a-zA-Z]+', ',', self.parameterAsString(parameters, self.INPUT, context).upper()).split(',') # lotplans_message = "Lot/Plan(s): " + ", ".join(lots) feedback.setProgressText(lotplans_message) # loadTenure = self.parameterAsBool(parameters, self.LOADTENURE, context) loadSupportingMap = self.parameterAsBool(parameters, self.LOADSUPPORTINGMAP, context) loadPreClearMap = self.parameterAsBool(parameters, self.LOADPRECLEARMAP, context) loadRVM = self.parameterAsBool(parameters, self.LOADRVM, context) loadPMAV = self.parameterAsBool(parameters, self.LOADPMAV, context) loadEssHab = self.parameterAsBool(parameters, self.LOADESSHAB, context) loadPPTM = self.parameterAsBool(parameters, self.LOADPPTM, context) loadWetlands = self.parameterAsBool(parameters, self.LOADWETLANDS, context) loadVMWater8 = self.parameterAsBool(parameters, self.LOADVMWATER8, context) loadVMWater7 = self.parameterAsBool(parameters, self.LOADVMWATER7, context) loadKoalaPA = self.parameterAsBool(parameters, self.LOADKPA, context) loadKoalaHA = self.parameterAsBool(parameters, self.LOADKHA, context) loadKoalaRA = self.parameterAsBool(parameters, self.LOADKRA, context) loadKoalaHAL = self.parameterAsBool(parameters, self.LOADKHAL, context) loadSLATS = self.parameterAsBool(parameters, self.LOADSLATS, context) outputDIR = self.parameterAsFileOutput(parameters, self.OUTPUTDIR, context) feedback.setProgress(1) # make directory try: os.mkdir(outputDIR) except FileExistsError as e: pass layerInfo = dict( lots=lots, layername='Property Boundary', layerstyle='LayerStyles/Property.qml', outputDIR=outputDIR, standardCRS=standardCRS, ) #Get property polygon(s) PropertyVlayer = LoadPropertyLayer(layerInfo, context, feedback) if PropertyVlayer == None or not PropertyVlayer.isValid( ) or PropertyVlayer.featureCount() < 1: feedback.reportError( "Failed to load property boundary! Invalid lot/plan? Network or server problems?", True) return {} # feedback.setProgress(15) feedback.setProgressText("Property polygon(s) loaded") # Load property layer to canvas project = QgsProject.instance() project.addMapLayer(PropertyVlayer, False) layerTree = iface.layerTreeCanvasBridge().rootGroup() layerTree.insertChildNode(0, QgsLayerTreeLayer(PropertyVlayer)) feedback.setProgress(20) # #Zoom to extent canvas = iface.mapCanvas() canvasCRS = QgsCoordinateReferenceSystem( canvas.mapSettings().destinationCrs().authid()) PropertyCRS = QgsCoordinateReferenceSystem(PropertyVlayer.crs()) xform = QgsCoordinateTransform(PropertyCRS, canvasCRS, project) canvas.setExtent(xform.transform(PropertyVlayer.extent())) # # Initialise dictionaries post = dict(serviceType="MapServer/", f='geojson') layerInfo = dict(vlayer=PropertyVlayer, outputDIR=outputDIR, standardCRS=standardCRS, searchType='lotplan') # #Load Natural Resource layers if loadTenure: post.update( dict(service1="PlanningCadastre/", service2="LandParcelPropertyFramework/", serviceNumber=str(13))) layerInfo.update( dict(layername="Tenure", layerstyle="LayerStyles/QldPropertyTenure.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(25) # Update server details post.update(dict(service1="Biota/", service2="VegetationManagement/")) if loadSupportingMap: post.update(dict(serviceNumber=str(134))) layerInfo.update( dict(layername="Regional ecosystem map (regulated)", layerstyle="LayerStyles/SupportingMap.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(30) if loadPreClearMap: post.update(dict(serviceNumber=str(15))) layerInfo.update( dict(layername="Pre-clear RE map (VM edition)", layerstyle="LayerStyles/PreClearMap.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(35) if loadRVM: post.update(dict(serviceNumber=str(109))) layerInfo.update( dict(layername="RVM", layerstyle="LayerStyles/RVM.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(40) if loadPMAV: post.update(dict(serviceNumber=str(146))) layerInfo.update( dict(layername="PMAV", layerstyle="LayerStyles/PMAV.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(50) if loadEssHab: post.update(dict(serviceNumber=str(5))) layerInfo.update( dict(layername="Essential Habitat", layerstyle="LayerStyles/EssentialHabitat.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(60) if loadWetlands: post.update(dict(serviceNumber=str(4))) layerInfo.update( dict(layername="Wetlands", layerstyle="LayerStyles/Wetlands.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(70) if loadVMWater8: post.update(dict(serviceNumber=str(8))) layerInfo.update( dict(layername="VM Watercourses-SEQ", layerstyle="LayerStyles/VMWaterCourse.qml", geomtype="MultiLineString")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(80) if loadVMWater7: post.update(dict(serviceNumber=str(7))) layerInfo.update( dict(layername="VM Watercourses QLD", layerstyle="LayerStyles/VMWaterCourse.qml", geomtype="MultiLineString")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(90) if loadPPTM: post.update(dict(serviceNumber=str(201))) layerInfo.update( dict(layername="Protected Plant Trigger Map", layerstyle="LayerStyles/PPTM.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(94) # Update server details post.update(dict(service1="Environment/", service2="KoalaPlan/")) if loadKoalaPA: post.update(dict(serviceNumber=str(1))) layerInfo.update( dict(layername="Koala Priority Area", layerstyle="LayerStyles/KoalaPriorityArea.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(95) if loadKoalaHA: post.update(dict(serviceNumber=str(3))) layerInfo.update( dict(layername="Koala Habitat Area-core", layerstyle="LayerStyles/KoalaHabitatArea-core.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(96) if loadKoalaRA: post.update(dict(serviceNumber=str(6))) layerInfo.update( dict(layername="Koala Restoration Area", layerstyle="LayerStyles/KoalaHabitatRestorationArea.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(97) if loadKoalaHAL: post.update(dict(serviceNumber=str(5))) layerInfo.update( dict(layername="Koala Habitat Area-locally refined", layerstyle= "LayerStyles/KoalaHabitatArea-LocallyRefined.qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) feedback.setProgress(98) if loadSLATS: # Update server details post.update(dict(service1="VegView/", service2="SLATS/")) for i in range(1, 24): post.update(dict(serviceNumber=str(i))) LayerName = json.loads( GetGEOJSON(queryLayerName(post, context, feedback), context, feedback)) DefaultLayerName = str(i) LayerName = LayerName.get('name', DefaultLayerName) if i % 2 == 0: colourCodeR = int((25 - i) / (30) * 250) colourCodeG = int((i) / (30) * 250) colourCodeB = int((i) / (30) * 250) else: colourCodeR = int((i) / (26) * 250) colourCodeG = int((i) / (26) * 250) colourCodeB = int((25 - i) / (26) * 250) if i % 3 == 0: colourCodeR = int((i) / (30) * 250) colourCodeG = int((25 - i) / (30) * 250) colourCodeB = int((i) / (30) * 250) if i % 4 == 0: colourCodeR = int((25 - i) / (30) * 250) colourCodeG = int((i) / (30) * 250) colourCodeB = int((25 - i) / (30) * 250) if i % 5 == 0: colourCodeR = int((i) / (30) * 250) colourCodeG = int((25 - i) / (30) * 250) colourCodeB = int((25 - i) / (30) * 250) if i % 6 == 0: colourCodeR = int((25 - i) / (30) * 250) colourCodeG = int((25 - i) / (30) * 250) colourCodeB = int((i) / (30) * 250) colourCodeH = int(250) colourCodeRGB = (colourCodeR, colourCodeG, colourCodeB, colourCodeH) #print(colourCodeRGB) layerInfo.update(dict(colour=colourCodeRGB)) LayerName = 'SLATS - ' + LayerName layerInfo.update( dict(layername=LayerName, layerstyle="LayerStyles/SLATS" + str(i) + ".qml", geomtype="MultiPolygon")) self.getNRlayer(post, layerInfo, context, feedback) # # feedback.setProgress(100) # return {}
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{}
def run(self): # show the dialog self.dlg.show() # Run the dialog event loop result = self.dlg.exec_() # See if OK was pressed if result == 1: layers = iface.layerTreeCanvasBridge().rootGroup().layerOrder() if len(layers) != 0: #Check that there is a selected layer layer = iface.mapCanvas().currentLayer() if layer.type() == 0: #check selected layer is a vector layer if layer.geometryType() == 1: #check to see if selected layer is a linestring geometry if len(layer.selectedFeatures()) != 0: #Dir = float(self.dlg.radLeft.isChecked() layers = iface.layerTreeCanvasBridge().rootGroup( ).layerOrder() Lexists = "false" #check to see if the virtual layer already exists for layer in layers: if layer.name() == "Parallel_Offset": Lexists = "true" vl = layer #if it doesn't exist create it if Lexists == "false": vl = QgsVectorLayer( "Linestring?field=offset:integer&field=direction:string(10)&field=method:string(10)", "Parallel_Offset", "memory") #vl = QgsVectorLayer("Linestring", "Parallel_Offset", "memory") pr = vl.dataProvider() vl.startEditing() #pr.addAttributes( [ QgsField("offset", Double), QgsField("direction", String), QgsField("method", String) ] ) else: pr = vl.dataProvider() vl.startEditing() #get the direction Dir = 'right' if self.dlg.radLeft.isChecked(): Dir = 'left' #get the method if self.dlg.radRound.isChecked(): js = 1 if self.dlg.radMitre.isChecked(): js = 2 if self.dlg.radBevel.isChecked(): js = 3 #get the offset #loffset = float(self.dlg.txtDistance.text()) loffset = self.dlg.sbDistance.value() #create the new feature from the selection for feature in layer.selectedFeatures(): geom = feature.geometry() h = geom.asPolyline() line = LineString(h) for i in range(self.dlg.sbNumberLines.value()): nline = line.parallel_offset( loffset * (i + 1), Dir, resolution=16, join_style=js, mitre_limit=10.0) #turn nline back into a polyline and add it to the map as a new layer. fet = QgsFeature() fet.setGeometry( QgsGeometry.fromWkt(str(nline))) fet.setAttributes([loffset, Dir, js]) pr.addFeatures([fet]) vl.commitChanges() QgsProject.instance().addMapLayer(vl) mc = self.iface.mapCanvas() mc.refresh()