• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nens / ThreeDiToolbox / #2525

04 Jun 2025 07:10AM UTC coverage: 34.875% (-0.1%) from 35.003%
#2525

push

coveralls-python

web-flow
Merge pull request #1101 from nens/margriet_1049_error_vector_layers

Add model checker errors as vector layers

7 of 83 new or added lines in 1 file covered. (8.43%)

5 existing lines in 1 file now uncovered.

4744 of 13603 relevant lines covered (34.87%)

0.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

24.82
/processing/schematisation_algorithms.py
1
# -*- coding: utf-8 -*-
2

3
"""
1✔
4
***************************************************************************
5
*                                                                         *
6
*   This program is free software; you can redistribute it and/or modify  *
7
*   it under the terms of the GNU General Public License as published by  *
8
*   the Free Software Foundation; either version 2 of the License, or     *
9
*   (at your option) any later version.                                   *
10
*                                                                         *
11
***************************************************************************
12
"""
13

14
import os
1✔
15
import shutil
1✔
16
import warnings
1✔
17

18
from pathlib import Path
1✔
19

20
from hydxlib.scripts import run_import_export
1✔
21
from hydxlib.scripts import write_logging_to_file
1✔
22

23
from osgeo import ogr, osr
1✔
24
from osgeo import gdal
1✔
25
from qgis.core import Qgis
1✔
26
from qgis.core import QgsProcessingAlgorithm
1✔
27
from qgis.core import QgsProcessingException
1✔
28
from qgis.core import QgsProcessingParameterBoolean
1✔
29
from qgis.core import QgsProcessingParameterFile
1✔
30
from qgis.core import QgsProcessingParameterFileDestination
1✔
31
from qgis.core import QgsProcessingParameterFolderDestination
1✔
32
from qgis.core import QgsProcessingParameterString
1✔
33
from qgis.core import QgsProject
1✔
34
from qgis.core import QgsVectorLayer
1✔
35
from qgis.PyQt.QtCore import QCoreApplication
1✔
36
from shapely import wkb  # Import Shapely wkb for geometry handling
1✔
37
from sqlalchemy.exc import DatabaseError
1✔
38
from sqlalchemy.exc import OperationalError
1✔
39

40
from threedi_modelchecker import ThreediModelChecker
1✔
41
from threedi_modelchecker.exporters import export_with_geom
1✔
42
from threedi_results_analysis.processing.download_hydx import download_hydx
1✔
43
from threedi_results_analysis.utils.utils import backup_sqlite
1✔
44
from threedi_schema import errors
1✔
45
from threedi_schema import ThreediDatabase
1✔
46

47
STYLE_DIR = Path(__file__).parent / "styles"
1✔
48

49

50
def get_threedi_database(filename, feedback):
1✔
51
    try:
×
52
        threedi_db = ThreediDatabase(filename)
×
53
        threedi_db.check_connection()
×
54
        return threedi_db
×
55
    except (OperationalError, DatabaseError):
×
56
        feedback.pushWarning("Invalid schematisation file")
×
57
        return None
×
58

59

60
def feedback_callback_factory(feedback):
1✔
61
    """Callback function to track schematisation migration progress."""
62

63
    def feedback_callback(progres_value, message):
×
64
        feedback.setProgress(progres_value)
×
65
        feedback.setProgressText(message)
×
66

67
    return feedback_callback
×
68

69

70
class MigrateAlgorithm(QgsProcessingAlgorithm):
1✔
71
    """
72
    Migrate 3Di model schema to the current version
73
    """
74

75
    INPUT = "INPUT"
1✔
76
    OUTPUT = "OUTPUT"
1✔
77

78
    def initAlgorithm(self, config):
1✔
79
        self.addParameter(
×
80
            QgsProcessingParameterFile(
81
                self.INPUT,
82
                "3Di schematisation database",
83
                fileFilter="3Di schematisation database (*.gpkg *.sqlite)"
84
            )
85
        )
86

87
    def processAlgorithm(self, parameters, context, feedback):
1✔
88
        filename = self.parameterAsFile(parameters, self.INPUT, context)
×
89
        threedi_db = get_threedi_database(filename=filename, feedback=feedback)
×
90
        if not threedi_db:
×
91
            return {self.OUTPUT: None}
×
92
        schema = threedi_db.schema
×
93

94
        # Check whether is it not an intermediate legacy geopackage created by
95
        # the schematisation editor
96
        if filename.endswith(".gpkg"):
×
97
            if schema.get_version() < 300:
×
98
                warn_msg = "The selected file is not a valid 3Di schematisation database.\n\nYou may have selected a geopackage that was created by an older version of the 3Di Schematisation Editor (before version 2.0). In that case, there will probably be a Spatialite (*.sqlite) in the same folder. Please use that file instead."
×
99
                feedback.pushWarning(warn_msg)
×
100
                return {self.OUTPUT: None}
×
101

102
        try:
×
103
            schema.validate_schema()
×
104
            schema.set_spatial_indexes()
×
105
        except errors.MigrationMissingError:
×
106
            backup_filepath = backup_sqlite(filename)
×
107

108
            srid, _ = schema._get_epsg_data()
×
109
            if srid is None:
×
110
                try:
×
111
                    srid = schema._get_dem_epsg()
×
112
                except errors.InvalidSRIDException:
×
113
                    srid = None
×
114
            if srid is None:
×
115
                feedback.pushWarning(
×
116
                    "Could not fetch valid EPSG code from database or DEM; aborting database migration."
117
                )
118
                return {self.OUTPUT: None}
×
119

120
            try:
×
121
                feedback_callback = feedback_callback_factory(feedback)
×
122
                with warnings.catch_warnings(record=True) as w:
×
123
                    warnings.simplefilter("always", UserWarning)
×
124
                    schema.upgrade(backup=False, epsg_code_override=srid, progress_func=feedback_callback)
×
125
                if w:
×
126
                    for warning in w:
×
127
                        feedback.pushWarning(f'{warning._category_name}: {warning.message}')
×
128
                schema.set_spatial_indexes()
×
129
                shutil.rmtree(os.path.dirname(backup_filepath))
×
130
            except errors.UpgradeFailedError:
×
131
                feedback.pushWarning(
×
132
                    "The schematisation database cannot be migrated to the current version. Please contact the service desk for assistance."
133
                )
134
                return {self.OUTPUT: None}
×
135
        success = True
×
136
        return {self.OUTPUT: success}
×
137

138
    def name(self):
1✔
139
        """
140
        Returns the algorithm name, used for identifying the algorithm. This
141
        string should be fixed for the algorithm, and must not be localised.
142
        The name should be unique within each provider. Names should contain
143
        lowercase alphanumeric characters only and no spaces or other
144
        formatting characters.
145
        """
146
        return "migrate"
×
147

148
    def displayName(self):
1✔
149
        """
150
        Returns the translated algorithm name, which should be used for any
151
        user-visible display of the algorithm name.
152
        """
153
        return self.tr("Migrate schematisation database")
×
154

155
    def group(self):
1✔
156
        """
157
        Returns the name of the group this algorithm belongs to. This string
158
        should be localised.
159
        """
160
        return self.tr(self.groupId())
×
161

162
    def groupId(self):
1✔
163
        """
164
        Returns the unique ID of the group this algorithm belongs to. This
165
        string should be fixed for the algorithm, and must not be localised.
166
        The group id should be unique within each provider. Group id should
167
        contain lowercase alphanumeric characters only and no spaces or other
168
        formatting characters.
169
        """
170
        return "Schematisation"
×
171

172
    def tr(self, string):
1✔
173
        return QCoreApplication.translate("Processing", string)
×
174

175
    def createInstance(self):
1✔
176
        return MigrateAlgorithm()
×
177

178

179
class CheckSchematisationAlgorithm(QgsProcessingAlgorithm):
1✔
180
    """
181
    Run the schematisation checker
182
    """
183

184
    INPUT = "INPUT"
1✔
185
    OUTPUT = "OUTPUT"
1✔
186
    ADD_TO_PROJECT = "ADD_TO_PROJECT"
1✔
187

188
    def initAlgorithm(self, config):
1✔
189
        self.addParameter(
×
190
            QgsProcessingParameterFile(
191
                self.INPUT, self.tr("3Di Schematisation"), fileFilter="GeoPackage (*.gpkg)"
192
            )
193
        )
194

195
        self.addParameter(
×
196
            QgsProcessingParameterFileDestination(
197
                self.OUTPUT, self.tr("Output"), fileFilter="csv"
198
            )
199
        )
200

201
        self.addParameter(
×
202
            QgsProcessingParameterBoolean(
203
                self.ADD_TO_PROJECT, self.tr("Add result to project"), defaultValue=True
204
            )
205
        )
206

207
    def processAlgorithm(self, parameters, context, feedback):
1✔
NEW
208
        feedback.setProgress(0)
×
UNCOV
209
        self.add_to_project = self.parameterAsBoolean(
×
210
            parameters, self.ADD_TO_PROJECT, context
211
        )
NEW
212
        feedback.pushInfo("Loading schematisation...")
×
213
        self.output_file_path = None
×
214
        input_filename = self.parameterAsFile(parameters, self.INPUT, context)
×
NEW
215
        self.schema_name = Path(input_filename).stem
×
216
        threedi_db = get_threedi_database(filename=input_filename, feedback=feedback)
×
217
        if not threedi_db:
×
218
            return {self.OUTPUT: None}
×
219
        try:
×
220
            model_checker = ThreediModelChecker(threedi_db)
×
221
        except errors.MigrationMissingError:
×
222
            feedback.pushWarning(
×
223
                "The selected 3Di model does not have the latest migration. Please "
224
                "migrate your model to the latest version."
225
            )
226
            return {self.OUTPUT: None}
×
227
        schema = threedi_db.schema
×
228
        schema.set_spatial_indexes()
×
NEW
229
        srid, _ = schema._get_epsg_data()
×
UNCOV
230
        generated_output_file_path = self.parameterAsFileOutput(
×
231
            parameters, self.OUTPUT, context
232
        )
NEW
233
        self.output_file_path = f"{os.path.splitext(generated_output_file_path)[0]}.gpkg"
×
234
        session = model_checker.db.get_session()
×
235
        session.model_checker_context = model_checker.context
×
236
        total_checks = len(model_checker.config.checks)
×
NEW
237
        progress_per_check = 80.0 / total_checks
×
238
        checks_passed = 0
×
NEW
239
        error_list = []
×
NEW
240
        feedback.pushInfo("Checking schematisation...")
×
NEW
241
        for i, check in enumerate(model_checker.checks(level="info")):
×
NEW
242
            model_errors = check.get_invalid(session)
×
NEW
243
            error_list += [[check, error_row] for error_row in model_errors]
×
NEW
244
            checks_passed += 1
×
NEW
245
            feedback.setProgress(int(checks_passed * progress_per_check))
×
NEW
246
        error_details = export_with_geom(error_list)
×
247

248
        # Create an output GeoPackage
NEW
249
        feedback.pushInfo("Writing checker results to geopackage...")
×
NEW
250
        gdal.UseExceptions()
×
NEW
251
        driver = ogr.GetDriverByName("GPKG")
×
NEW
252
        data_source = driver.CreateDataSource(self.output_file_path)
×
NEW
253
        if data_source is None:
×
UNCOV
254
            feedback.pushWarning(
×
255
                f"Unable to create the GeoPackage '{self.output_file_path}', check the directory permissions."
256
            )
257
            return {self.OUTPUT: None}
×
258

259
        # Loop through the error_details and group by geometry type
NEW
260
        grouped_errors = {'Point': [], 'LineString': [], 'Polygon': [], 'Table': []}
×
NEW
261
        for error in error_details:
×
NEW
262
            geom = wkb.loads(error.geom.data) if error.geom is not None else None
×
NEW
263
            if geom is None or geom.geom_type not in grouped_errors.keys():
×
NEW
264
                feature_type = 'Table'
×
265
            else:
NEW
266
                feature_type = geom.geom_type
×
NEW
267
            grouped_errors[feature_type].append(error)
×
NEW
268
        group_name_map = {'LineString': 'Line'}
×
NEW
269
        for i, (feature_type, errors_group) in enumerate(grouped_errors.items()):
×
NEW
270
            group_name = f'{group_name_map.get(feature_type, feature_type)} features'
×
NEW
271
            feedback.pushInfo(f"Adding layer '{group_name}' to geopackage...")
×
NEW
272
            feedback.setProgress(85+5*i)
×
NEW
273
            if feature_type == 'Table':
×
274
                # Create a table for non-geometry errors
NEW
275
                layer = data_source.CreateLayer(
×
276
                    group_name, None, ogr.wkbNone, options=["OVERWRITE=YES"]
277
                )
278
            else:
279
                # Create a layer for each type of geometry
NEW
280
                spatial_ref = osr.SpatialReference()
×
NEW
281
                spatial_ref.ImportFromEPSG(srid)
×
NEW
282
                layer = data_source.CreateLayer(
×
283
                    group_name,
284
                    srs=spatial_ref,
285
                    geom_type=getattr(ogr, f"wkb{feature_type}"),
286
                    options=["OVERWRITE=YES"],
287
                    )
288

289
            # Add fields
NEW
290
            layer.CreateField(ogr.FieldDefn("level", ogr.OFTString))
×
NEW
291
            layer.CreateField(ogr.FieldDefn("error_code", ogr.OFTString))
×
NEW
292
            layer.CreateField(ogr.FieldDefn("id", ogr.OFTString))
×
NEW
293
            layer.CreateField(ogr.FieldDefn("table", ogr.OFTString))
×
NEW
294
            layer.CreateField(ogr.FieldDefn("column", ogr.OFTString))
×
NEW
295
            layer.CreateField(ogr.FieldDefn("value", ogr.OFTString))
×
NEW
296
            layer.CreateField(ogr.FieldDefn("description", ogr.OFTString))
×
297

NEW
298
            defn = layer.GetLayerDefn()
×
NEW
299
            for error in errors_group:
×
NEW
300
                feat = ogr.Feature(defn)
×
NEW
301
                feat.SetField("level", error.name)
×
NEW
302
                feat.SetField("error_code", error.code)
×
NEW
303
                feat.SetField("id", error.id)
×
NEW
304
                feat.SetField("table", error.table)
×
NEW
305
                feat.SetField("column", error.column)
×
NEW
306
                feat.SetField("value", error.value)
×
NEW
307
                feat.SetField("description", error.description)
×
NEW
308
                if feature_type != 'Table':
×
NEW
309
                    geom = wkb.loads(error.geom.data)  # Convert WKB to a Shapely geometry object
×
NEW
310
                    feat.SetGeometry(ogr.CreateGeometryFromWkb(geom.wkb))  # Convert back to OGR-compatible WKB
×
NEW
311
                layer.CreateFeature(feat)
×
NEW
312
        feedback.pushInfo("GeoPackage successfully written to file")
×
UNCOV
313
        return {self.OUTPUT: self.output_file_path}
×
314

315
    def postProcessAlgorithm(self, context, feedback):
1✔
NEW
316
        if self.add_to_project and self.output_file_path:
×
NEW
317
            feedback.pushInfo("Adding results to project...")
×
318
            # Create a group for the GeoPackage layers
NEW
319
            group = QgsProject.instance().layerTreeRoot().insertGroup(0, f'Check results: {self.schema_name}')
×
320
            # Add all layers in the geopackage to the group
NEW
321
            conn = ogr.Open(self.output_file_path)
×
NEW
322
            if conn:
×
NEW
323
                for i in range(conn.GetLayerCount()):
×
NEW
324
                    layer_name = conn.GetLayerByIndex(i).GetName()
×
NEW
325
                    layer_uri = f"{self.output_file_path}|layername={layer_name}"
×
NEW
326
                    layer = QgsVectorLayer(layer_uri, layer_name.replace('errors_', '', 1), "ogr")
×
NEW
327
                    if layer.isValid():
×
NEW
328
                        added_layer = QgsProject.instance().addMapLayer(layer, False)
×
NEW
329
                        if added_layer.geometryType() in [
×
330
                            Qgis.GeometryType.Point,
331
                            Qgis.GeometryType.Line,
332
                            Qgis.GeometryType.Polygon,
333
                        ]:
NEW
334
                            added_layer.loadNamedStyle(
×
335
                                str(STYLE_DIR / f"checker_{added_layer.geometryType().name.lower()}.qml")
336
                            )
NEW
337
                        group.addLayer(added_layer)
×
338
                    else:
NEW
339
                        feedback.reportError(f"Layer {layer_name} is not valid")
×
NEW
340
                conn = None  # Close the connection
×
341
            else:
NEW
342
                feedback.reportError(f"Could not open GeoPackage file: {self.output_file_path}")
×
NEW
343
            feedback.pushInfo(f"Added results to layer 'Check results: {self.schema_name}'")
×
UNCOV
344
        return {self.OUTPUT: self.output_file_path}
×
345

346
    def name(self):
1✔
347
        """
348
        Returns the algorithm name, used for identifying the algorithm. This
349
        string should be fixed for the algorithm, and must not be localised.
350
        The name should be unique within each provider. Names should contain
351
        lowercase alphanumeric characters only and no spaces or other
352
        formatting characters.
353
        """
354
        return "check_schematisation"
×
355

356
    def displayName(self):
1✔
357
        """
358
        Returns the translated algorithm name, which should be used for any
359
        user-visible display of the algorithm name.
360
        """
361
        return self.tr("Check Schematisation")
×
362

363
    def group(self):
1✔
364
        """
365
        Returns the name of the group this algorithm belongs to. This string
366
        should be localised.
367
        """
368
        return self.tr(self.groupId())
×
369

370
    def groupId(self):
1✔
371
        """
372
        Returns the unique ID of the group this algorithm belongs to. This
373
        string should be fixed for the algorithm, and must not be localised.
374
        The group id should be unique within each provider. Group id should
375
        contain lowercase alphanumeric characters only and no spaces or other
376
        formatting characters.
377
        """
378
        return "Schematisation"
×
379

380
    def tr(self, string):
1✔
381
        return QCoreApplication.translate("Processing", string)
×
382

383
    def createInstance(self):
1✔
384
        return CheckSchematisationAlgorithm()
×
385

386

387
class ImportHydXAlgorithm(QgsProcessingAlgorithm):
1✔
388
    """
389
    Import data from GWSW HydX to a 3Di Schematisation
390
    """
391

392
    INPUT_DATASET_NAME = "INPUT_DATASET_NAME"
1✔
393
    HYDX_DOWNLOAD_DIRECTORY = "HYDX_DOWNLOAD_DIRECTORY"
1✔
394
    INPUT_HYDX_DIRECTORY = "INPUT_HYDX_DIRECTORY"
1✔
395
    TARGET_SCHEMATISATION = "TARGET_SCHEMATISATION"
1✔
396

397
    def initAlgorithm(self, config):
1✔
398
        self.addParameter(
×
399
            QgsProcessingParameterFile(
400
                self.TARGET_SCHEMATISATION, "Target 3Di Schematisation", fileFilter="GeoPackage (*.gpkg)"
401
            )
402
        )
403

404
        self.addParameter(
×
405
            QgsProcessingParameterFile(
406
                self.INPUT_HYDX_DIRECTORY,
407
                "GWSW HydX directory (local)",
408
                behavior=QgsProcessingParameterFile.Folder,
409
                optional=True,
410
            )
411
        )
412

413
        self.addParameter(
×
414
            QgsProcessingParameterString(
415
                self.INPUT_DATASET_NAME, "GWSW dataset name (online)", optional=True
416
            )
417
        )
418

419
        self.addParameter(
×
420
            QgsProcessingParameterFolderDestination(
421
                self.HYDX_DOWNLOAD_DIRECTORY,
422
                "Destination directory for GWSW HydX dataset download",
423
                optional=True,
424
            )
425
        )
426

427
    def processAlgorithm(self, parameters, context, feedback):
1✔
428
        hydx_dataset_name = self.parameterAsString(
×
429
            parameters, self.INPUT_DATASET_NAME, context
430
        )
431
        hydx_download_dir = self.parameterAsString(
×
432
            parameters, self.HYDX_DOWNLOAD_DIRECTORY, context
433
        )
434
        hydx_path = self.parameterAsString(
×
435
            parameters, self.INPUT_HYDX_DIRECTORY, context
436
        )
437
        out_path = self.parameterAsFile(parameters, self.TARGET_SCHEMATISATION, context)
×
438
        threedi_db = get_threedi_database(filename=out_path, feedback=feedback)
×
439
        if not threedi_db:
×
440
            raise QgsProcessingException(
×
441
                f"Unable to connect to 3Di schematisation '{out_path}'"
442
            )
443
        try:
×
444
            schema = threedi_db.schema
×
445
            schema.validate_schema()
×
446

447
        except errors.MigrationMissingError:
×
448
            raise QgsProcessingException(
×
449
                "The selected 3Di schematisation does not have the latest database schema version. Please migrate this "
450
                "schematisation and try again: Processing > Toolbox > 3Di > Schematisation > Migrate schematisation database"
451
            )
452
        if not (hydx_dataset_name or hydx_path):
×
453
            raise QgsProcessingException(
×
454
                "Either 'GWSW HydX directory (local)' or 'GWSW dataset name (online)' must be filled in!"
455
            )
456
        if hydx_dataset_name and hydx_path:
×
457
            feedback.pushWarning(
×
458
                "Both 'GWSW dataset name (online)' and 'GWSW HydX directory (local)' are filled in. "
459
                "'GWSW dataset name (online)' will be ignored. This dataset will not be downloaded."
460
            )
461
        elif hydx_dataset_name:
×
462
            try:
×
463
                hydx_download_path = Path(hydx_download_dir)
×
464
                hydx_download_dir_is_valid = hydx_download_path.is_dir()
×
465
            except TypeError:
×
466
                hydx_download_dir_is_valid = False
×
467
            if parameters[self.HYDX_DOWNLOAD_DIRECTORY] == "TEMPORARY_OUTPUT":
×
468
                hydx_download_dir_is_valid = True
×
469
            if not hydx_download_dir_is_valid:
×
470
                raise QgsProcessingException(
×
471
                    f"'Destination directory for HydX dataset download' ({hydx_download_path}) is not a valid directory"
472
                )
473
            hydx_path = download_hydx(
×
474
                dataset_name=hydx_dataset_name,
475
                target_directory=hydx_download_path,
476
                wait_times=[0.1, 1, 2, 3, 4, 5, 10],
477
                feedback=feedback,
478
            )
479
            # hydx_path will be None if user has canceled the process during download
480
            if feedback.isCanceled():
×
481
                raise QgsProcessingException("Process canceled")
×
482
            if hydx_path is None:
×
483
                raise QgsProcessingException("Error in retrieving dataset (note case-sensitivity)")
×
484
        feedback.pushInfo(f"Starting import of {hydx_path} to {out_path}")
×
485
        log_path = Path(out_path).parent / "import_hydx.log"
×
486
        write_logging_to_file(log_path)
×
487
        feedback.pushInfo(f"Logging will be written to {log_path}")
×
488
        run_import_export(hydx_path=hydx_path, out_path=out_path)
×
489
        return {}
×
490

491
    def name(self):
1✔
492
        """
493
        Returns the algorithm name, used for identifying the algorithm. This
494
        string should be fixed for the algorithm, and must not be localised.
495
        The name should be unique within each provider. Names should contain
496
        lowercase alphanumeric characters only and no spaces or other
497
        formatting characters.
498
        """
499
        return "import_hydx"
×
500

501
    def displayName(self):
1✔
502
        """
503
        Returns the translated algorithm name, which should be used for any
504
        user-visible display of the algorithm name.
505
        """
506
        return self.tr("Import GWSW HydX")
×
507

508
    def shortHelpString(self):
1✔
509
        return """
×
510
        <h3>Introduction</h3>
511
        <p>Use this processing algorithm to import data in the format of the Dutch "Gegevenswoordenboek Stedelijk Water (GWSW)". Either select a previously downloaded local dataset, or download a dataset directly from the server.</p>
512
        <p>A log file will be created in the same directory as the Target 3Di schematisation. Please check this log file after the import has completed.&nbsp;&nbsp;</p>
513
        <h3>Parameters</h3>
514
        <h4>Target 3Di Schematisation</h4>
515
        <p>GeoPackage (.gpkg) file that contains the layers required by 3Di. Imported data will be added to any data already contained in the 3Di schematisation.</p>
516
        <h4>GWSW HydX directory (local)</h4>
517
        <p>Use this option if you have already downloaded a GWSW HydX dataset to a local directory.</p>
518
        <h4>GWSW dataset name (online)</h4>
519
        <p>Use this option if you want to download a GWSW HydX dataset.</p>
520
        <h4>Destination directory for GWSW HydX dataset download</h4>
521
        <p>If you have chosen to download a GWSW HydX dataset, this is the directory it will be downloaded to.</p>
522
        """
523

524
    def group(self):
1✔
525
        """
526
        Returns the name of the group this algorithm belongs to. This string
527
        should be localised.
528
        """
529
        return self.tr(self.groupId())
×
530

531
    def groupId(self):
1✔
532
        """
533
        Returns the unique ID of the group this algorithm belongs to. This
534
        string should be fixed for the algorithm, and must not be localised.
535
        The group id should be unique within each provider. Group id should
536
        contain lowercase alphanumeric characters only and no spaces or other
537
        formatting characters.
538
        """
539
        return "Schematisation"
×
540

541
    def tr(self, string):
1✔
542
        return QCoreApplication.translate("Processing", string)
×
543

544
    def createInstance(self):
1✔
545
        return ImportHydXAlgorithm()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc