2

I write myself plugin for QGIS with Python3 API. For my plugin logic I need write my self geometry checker because of difficult logic and different actions when I find some of geometry errors. In documentation I find some classes, wicth can help me, but I don't understend how use it.

For example I want individual geometry check with class QgsSingleGeometryCheck.

I do somethong like this:

import qgis.analysis as qgs_ana
import qgis.core as qgs_core

layer_in = qgs_core.QgsProject().instance().mapLayer(layer_id)

for feat in layer_in.getFeatures():
    check = qgs_ana.QgsSingleGeometryCheck()
    geom = feat.geometry()
    print(check.processGeometry(geom))

After run this code I get TypeError like this: enter image description here

I understend that this error is because QgsSingleGeometryCheck is a abstract class. I don't understend how and what I need override, because no documentation and no example cases. In opensource code of QGIS it looks like in should work like this, but I write only on python and don't understand c++ and what I should do.

Maybe there is another way to do individual Checks for geometry. I learn in documentation another classes whitch can help me, for example QgsGeometryValidator, QgsGeometryEngine in methods of this classes I find some helpful functional for me, but i beleve QGIS have more best way to check geometrys. I want find correct way to doing different geometry checks.

If you do something like this please tell me how can I check geometry for example on duplicate nodes.

PS: QGIS self algorithms like "processing algorithms" is not for me because it work with layer but not with geometry as such.

UPD:

I try using example from Taras link's of overriding abstract classes. Problem with TypeError is decied! BUT...

import qgis.analysis as qgs_ana
import qgis.core as qgs_core
from typing import List

class QgsSingleGeometryCheck(qgs_ana.QgsSingleGeometryCheck):
    def processGeometry(self, geometry: qgs_core.QgsGeometry) -> List[qgs_ana.QgsSingleGeometryCheckError]:
        pass

layer_in = qgs_core.QgsProject().instance().mapLayer(layer_id)

for feat in layer_in.getFeatures():
    check = qgs_ana.QgsSingleGeometryCheck()
    geom = feat.geometry()
    print(check.processGeometry(geom))
# output: None

Metod processGeometry should return empty list if geometry is valid and list of errors if not valid. In my "new" code it return None in all case. I think method didn't working correctly in my code and I don't know why. Because in example link from Taras, method was override without logic, just pass code inside method.

Actually, I need repeat geometry check logic from standart plugin "Geometry Checker" with my additions. And when I learn documentation QGIS, I found QgsGeometryValidator and experimentally use it, but result is not like in "Geometry Checker" plugin. It is looks like: Setup Result is: Result

Code with differents validator engine give me:

import qgis.analysis as qgs_ana
import qgis.core as qgs_core

validator_meth = qgs_core.Qgis.GeometryValidationEngine(0)
#ValidatorQgisInternal(0)
#validator_meth = qgs_core.Qgis.GeometryValidationEngine(1)
#ValidatorGeos(1)

layer_in = self.qgis_project.mapLayer(layer_id)

for feat in layer_in.getFeatures():
    poly_geom = feat.geometry()

    poly_eng = qgs_core.QgsGeometry().createGeometryEngine(poly_geom.constGet())
    poly_eng.prepareGeometry()
    poly_eng.setLogErrors(enabled=True)

    validator = qgs_core.QgsGeometryValidator(geometry=poly_geom)

    if not poly_eng.isValid():

        errors = [[error.what(), error.where()] for error in validator.validateGeometry(geometry=poly_geom, method=validator_meth)]

        for e in errors:
            print(f"Feature id {feat.id()} Error {e}")
#Output with ValidatorQgisInternal is:
#Feature id 490 Error ['segments 3 and 142 of line 0 intersect at 1.2131e+06, 418062', <QgsPointXY: POINT(1213096.22999999998137355 418062.03000000002793968)>]
#Feature id 490 Error ['segments 4 and 142 of line 0 intersect at 1.2131e+06, 418062', <QgsPointXY: POINT(1213096.22999999998137355 418062.03000000002793968)>]

# If I change validation engine on ValidatorGeos:
# Output with ValidatorGeos is:
#Feature id 1035 Error ['Self-intersection', <QgsPointXY: POINT(1215741.44999999995343387 416008.21000000002095476)>]
#Feature id 1043 Error ['Self-intersection', <QgsPointXY: POINT(1215590.76000000000931323 415891.94000000000232831)>]
#Feature id 1154 Error ['Self-intersection', <QgsPointXY: POINT(1217266.03000000002793968 415655.30999999999767169)>]
#Feature id 1250 Error ['Self-intersection', <QgsPointXY: POINT(1219298.23999999999068677 420871.39000000001396984)>]

Why It is so different result? In first case is ok, 1 error was found but others is not. In second case 0 errors like in "Geometry Checker" plugin was found.

UPD_2:

Ok, I find "mistake" with @CodeBard help. If I code like him I find new one mistake with duplicate node like in "Geometry Checker" plugin.

import qgis.analysis as qgs_ana
import qgis.core as qgs_core

layer_in = self.qgis_project.mapLayer(layer_id)

for feat in layer_in.getFeatures():

    poly_geom = feat.geometry()
    validator = qgs_core.QgsGeometryValidator(geometry=poly_geom)
    errors = [[error.what(), error.where()] for error in validator.validateGeometry(geometry=poly_geom)]

        for e in errors:
            print(f"Feature id {feat.id()} Error {e}")
#Output with this code:
#Feature id 461 Error ['line 1 contains 2 duplicate nodes starting at vertex 19', <QgsPointXY: POINT(1212358.13999999989755452 418338.15000000002328306)>]
#Feature id 490 Error ['segments 3 and 142 of line 0 intersect at 1.2131e+06, 418062', <QgsPointXY: POINT(1213096.22999999998137355 418062.03000000002793968)>]
#Feature id 490 Error ['segments 4 and 142 of line 0 intersect at 1.2131e+06, 418062', <QgsPointXY: POINT(1213096.22999999998137355 418062.03000000002793968)>]

I still don't understand where is feature with 699 id like in plugin. ¯_(ツ)_/¯

I really want understand how it work and how I can do it correctly! I will be glad for any help.

2

1 Answer 1

4

First of all, I strongly believe that the subclasses of QgsGeometryCheck (e.g QgsSingleGeometryCheck) are not what you are looking for.

These classes will help you define custom geometry checks which can be used from within the geometry validation panel. You can't actually apply those classes such as QgsGeometryDuplicateNodesCheck, QgsGeometryMultipartCheck, etc. from within pyqgis to check individual geometries, you can only subclass QgsGeometryCheck to add more custom geometry checks which will then be available in the dropdown menu of the geometry validation panel.

But if that is what you are after, I will try to explain the process. It's not easy:

  1. You will need to subclass QgsGeometryCheck to add your custom validation algorithm by overriding collectErrors function (all other abstract methods will also need to be implemented)
  2. Subclass QgsGeometryCheckFactory which is a factory class managing the instantiation of the QgsGeometryCheck subclass from step 1 (also need to implement all abstract methods here!)
  3. Lastly register your new geometry checker so that QGIS knows about it: QgsAnalysis.geometryCheckRegistry().registerGeometryCheck(MyCustomCheckFactory())

However, to just check individual geometries it will be way simpler using QgsGeometryValidator. I couldn't find a list of all the things QgsGeometryValidator validates, but it definitely checks common problems such as duplicate nodes, unclosed rings, etc.

layer = iface.activeLayer()

for feat in layer.getFeatures():
    geom = feat.geometry()
    validator = QgsGeometryValidator(geom)
    error = validator.validateGeometry(geom)
    print(error)
    # output example: [<QgsGeometry.Error: segment 3 of ring 0 of polygon 0 intersects segment 1 of ring 0 of polygon 9 at 386605]
    # or [<QgsGeometry.Error: line 1 contains 2 duplicate nodes starting at vertex 2>]
1
  • Thank you for you answer. I think I start partial understanding information in this theme. But I have some additional questions. I make update paragraph in my post. Commented Jun 16, 2022 at 5:47

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.