3

I have a map layer and I want to find out how many of one type of feature there are, and sum an associated feature. I want to do this as a plugin script as there will be a number of other operations too, this is just the starting point.

In the example below, I want to know how many instances of Virgin there are and the total KM associated with them.

I can do this as pure Python script and I can do it in Excel, but I don't know how to do it with PyQGIS.

 Fx   Status    KM
 Fx   Accept    32.408
 Fx   Accept    32.41
 Fx   Reject    0
 Fx   Virgin    32.413
 Fx   Virgin    24
 Fx   Accept    32.417

I have a working plugin and I have a filtered ComboBox to select the layer I want to interrogate, but how do I do the Count and Sum? I have a basic feature loop to get the COUNT (there must be a better way), but am stuck on the SUM.

The script I need help with it the def CalcCDuration() part, I have only included the CalCTime in case it is relevant.

'Fx' is another feature I am using so the ComboBox only selects a certain type of layer and is of no relevance to this question beyond it being in the ComboBox script.

"""Code to Calculate time to complete workscope."""
    def CalCTime(self):
        """Run method that performs all the real work."""
        if self.first_start is True:
            # self.first_start = False
            from .ocr_time_dialog import OCR_TIMEDialog
            self.dlg = OCR_TIMEDialog()

        # show the dialog
        self.dlg.show()
        if self.dlg.isVisible():
            self.map_layers = QgsProject.instance().mapLayers().values()
            self.allow_list = [
                lyr.id() for lyr in self.map_layers if lyr.type() ==
                QgsMapLayerType.VectorLayer
                and lyr.geometryType() == QgsWkbTypes.LineGeometry
                and 'Fx' in [f.name() for f in lyr.fields()]
            ]
        self.except_list = [y for y in self.map_layers if y.id()
                            not in self.allow_list]

        self.dlg.mMapLayerComboBoxT.setExceptedLayerList(self.except_list)

        self.dlg.mMapLayerComboBoxT.setFilters(QgsMapLayerProxyModel.LineLayer)

        self.dlg.pushButton.clicked.connect(self.CalCDuration)

    def CalCDuration(self):
        """Calculate the duration of the particular survey type"""
        # TurnT = float(self.dlg.TurnBox.text())
        # Kn = float(self.dlg.KnBox.text())
        # Ms = float(self.dlg.MsBox.text())

        
        #Get line Km and Number from chosen layer
        global TLayer
        TLayer = self.dlg.mMapLayerComboBoxT.currentLayer()
        exp = QgsExpression('Status ILIKE \'%Virgin%\'')
        request = QgsFeatureRequest(exp)
        NoV = 0
        NoK = 0
        for feature in TLayer.getFeatures(request):
            NoV = NoV+1             
        
        self.dlg.SolBox.setText(str(NoV))
1
  • 1
    summation usually requires adding the relevant attribute of each feature in the loop to a variable.
    – Ian Turton
    Nov 1, 2022 at 16:23

1 Answer 1

1

Further investigation and borrowing from a number of answers on this site has given me a working plugin. The relevant section I needed to resolve is now working. The key was to use the f.attributes in conjunction with the layer.dataProvider

"""Get line Km and Number from chosen layer."""
TLayer = self.dlg.mMapLayerComboBoxT.currentLayer()
Kmidx=TLayer.dataProvider().fieldNameIndex('Designed km')
expV = QgsExpression('Status ILIKE \'%Virgin%\'')
expT = QgsExpression('Status ILIKE \'%TBC%\'')
expR = QgsExpression('Status ILIKE \'%Reject%\' AND ''Format ILIKE \'%Rerun%\'')
expI = QgsExpression('Status ILIKE \'%Virgin%\' AND ''Format ILIKE \'%Infill%\'')
requestV = QgsFeatureRequest(expV)
requestT = QgsFeatureRequest(expT)
requestR = QgsFeatureRequest(expR)
requestI = QgsFeatureRequest(expI)
NoV=0
NoT=0
NoI=0
NoR=0
KoV=0
KoT=0
KoI=0
KoR=0
for f in TLayer.getFeatures(requestV):
    KoV+=f.attributes()[Kmidx]
    KoV=round(KoV,3)
    NoV=NoV+1
for f in TLayer.getFeatures(requestT):
    KoT+=f.attributes()[Kmidx]
    KoT=round(KoT,3)
    NoT=NoT+1
for f in TLayer.getFeatures(requestI):
    KoI+=f.attributes()[Kmidx]
    KoI=round(KoI,3)
    NoI=NoI+1    
for f in TLayer.getFeatures(requestR):
    KoR+=f.attributes()[Kmidx]
    KoR=round(KoR,3)
    NoR=NoR+1

Improved version :

t_layer = self.dlg.mMapLayerComboBoxT.currentLayer()
field_km_idx = t_layer.fields().indexFromName("Designed km")
requests_expressions = {
    "V": "Status ILIKE '%Virgin%'",
    "T": "Status ILIKE '%TBC%'",
    "R": "Status ILIKE '%Reject%' AND Format ILIKE '%Rerun%'",
    "I": "Status ILIKE '%Virgin%' AND Format ILIKE '%Infill%'",
}
results = {}

for request_name, expression in requests_expressions.items():
    request = QgsFeatureRequest(QgsExpression(expression))
    data = [f[field_km_idx] for f in t_layer.getFeatures(request)]
    results[request_name] = {"Ko": round(sum(data), 3), "No": len(data)}
6
  • 2
    I took the liberty of adding a more Pythonic / PyQGIS version of the code. Nov 2, 2022 at 9:23
  • @J.Monticolo Thanks. How do I connect your script revision to my follow-on equations. Currently I have KoV & NoV variable which I then perform various maths functions on. In your script I only see Ko & No, how does your script provide the 8 variables? Also, I assume (req) should be (request).
    – WillH
    Nov 2, 2022 at 12:43
  • 1
    I corrected the req into request, good catch ! To access you have the dict results : results["V"]["Ko"] and results["V"]["No"] (for the V request). Nov 2, 2022 at 12:54
  • @J.Monticolo, found the dict and can now see what is being created (also explains why Ko and not KoV in the your last line of script, which I didn't initially understand).
    – WillH
    Nov 2, 2022 at 13:16
  • 1
    I have accepted my own answer, as I it is not possible to accept @J.Monticolo 'Improved version'. The final code will be based on a combination of both.
    – WillH
    Nov 3, 2022 at 15:41

Your Answer

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

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