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

nens / ThreeDiToolbox / #2640

19 Dec 2025 03:01PM UTC coverage: 36.526% (-0.02%) from 36.546%
#2640

push

coveralls-python

web-flow
Rebranding the 3Di Results Analysis to Rana Results Analysis (#1151)

13 of 77 new or added lines in 22 files covered. (16.88%)

3 existing lines in 3 files now uncovered.

5262 of 14406 relevant lines covered (36.53%)

0.37 hits per line

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

23.65
/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 database 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
                "Schematisation geopackage",
83
                fileFilter="Rana schematisation geopackage (*.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:
×
NEW
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 Rana 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("Rana schematisation geopackage"), 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✔
208
        feedback.setProgress(0)
×
209
        self.add_to_project = self.parameterAsBoolean(
×
210
            parameters, self.ADD_TO_PROJECT, context
211
        )
212
        feedback.pushInfo("Loading schematisation...")
×
213
        self.output_file_path = None
×
214
        input_filename = self.parameterAsFile(parameters, self.INPUT, context)
×
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 schematisation does not have the latest schema version. Please "
224
                "migrate your schematisation to the latest version."
225
            )
226
            return {self.OUTPUT: None}
×
227
        schema = threedi_db.schema
×
228
        schema.set_spatial_indexes()
×
229
        srid, _ = schema._get_epsg_data()
×
230
        generated_output_file_path = self.parameterAsFileOutput(
×
231
            parameters, self.OUTPUT, context
232
        )
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)
×
237
        progress_per_check = 80.0 / total_checks
×
238
        checks_passed = 0
×
239
        error_list = []
×
240
        feedback.pushInfo("Checking schematisation...")
×
241
        for i, check in enumerate(model_checker.checks(level="info")):
×
242
            model_errors = check.get_invalid(session)
×
243
            error_list += [[check, error_row] for error_row in model_errors]
×
244
            checks_passed += 1
×
245
            feedback.setProgress(int(checks_passed * progress_per_check))
×
246
        error_details = export_with_geom(error_list)
×
247

248
        # Create an output GeoPackage
249
        feedback.pushInfo("Writing checker results to geopackage...")
×
250
        gdal.UseExceptions()
×
251
        driver = ogr.GetDriverByName("GPKG")
×
252
        data_source = driver.CreateDataSource(self.output_file_path)
×
253
        if data_source is None:
×
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
260
        grouped_errors = {'Point': [], 'LineString': [], 'Polygon': [], 'Table': []}
×
261
        for error in error_details:
×
262
            geom = wkb.loads(error.geom.data) if error.geom is not None else None
×
263
            if geom is None or geom.geom_type not in grouped_errors.keys():
×
264
                feature_type = 'Table'
×
265
            else:
266
                feature_type = geom.geom_type
×
267
            grouped_errors[feature_type].append(error)
×
268
        group_name_map = {'LineString': 'Line'}
×
269
        ordered_keys = ["Point", "LineString", "Polygon", "Table"]
×
270
        for i, feature_type in enumerate(ordered_keys):
×
271
            errors_group = grouped_errors[feature_type]
×
272
            group_name = f'{group_name_map.get(feature_type, feature_type)} features'
×
273
            feedback.pushInfo(f"Adding layer '{group_name}' to geopackage...")
×
274
            feedback.setProgress(85+5*i)
×
275
            if feature_type == 'Table':
×
276
                # Create a table for non-geometry errors
277
                layer = data_source.CreateLayer(
×
278
                    group_name, None, ogr.wkbNone, options=["OVERWRITE=YES"]
279
                )
280
            else:
281
                # Create a layer for each type of geometry
282
                spatial_ref = osr.SpatialReference()
×
283
                spatial_ref.ImportFromEPSG(srid)
×
284
                layer = data_source.CreateLayer(
×
285
                    group_name,
286
                    srs=spatial_ref,
287
                    geom_type=getattr(ogr, f"wkb{feature_type}"),
288
                    options=["OVERWRITE=YES"],
289
                    )
290

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

300
            defn = layer.GetLayerDefn()
×
301
            for error in errors_group:
×
302
                if feature_type != 'Table':
×
303
                    try:
×
304
                        geom = wkb.loads(error.geom.data)
×
305
                        geom_wkb = ogr.CreateGeometryFromWkb(geom.wkb)
×
306
                    except (ValueError, TypeError):
×
307
                        # When wkb.loads fails due to malformed WKB data move error to Table errors
308
                        grouped_errors['Table'].append(error)
×
309
                        continue
×
310
                feat = ogr.Feature(defn)
×
311
                feat.SetField("level", error.name)
×
312
                feat.SetField("error_code", error.code)
×
313
                feat.SetField("id", error.id)
×
314
                feat.SetField("table", error.table)
×
315
                feat.SetField("column", error.column)
×
316
                feat.SetField("description", error.description)
×
317
                try:
×
318
                    feat.SetField("value", error.value)
×
319
                except (NotImplementedError, TypeError):
×
320
                    pass
×
321
                except Exception:
×
322
                    feedback.pushWarning(f"Could not set value for check with code {error.code}")
×
323
                if feature_type != 'Table':
×
324
                    feat.SetGeometry(geom_wkb)
×
325
                layer.CreateFeature(feat)
×
326
        feedback.pushInfo("GeoPackage successfully written to file")
×
327
        return {self.OUTPUT: self.output_file_path}
×
328

329
    def postProcessAlgorithm(self, context, feedback):
1✔
330
        if self.add_to_project and self.output_file_path:
×
331
            feedback.pushInfo("Adding results to project...")
×
332
            # Create a group for the GeoPackage layers
333
            group = QgsProject.instance().layerTreeRoot().insertGroup(0, f'Check results: {self.schema_name}')
×
334
            # Add all layers in the geopackage to the group
335
            conn = ogr.Open(self.output_file_path)
×
336
            if conn:
×
337
                for i in range(conn.GetLayerCount()):
×
338
                    layer_name = conn.GetLayerByIndex(i).GetName()
×
339
                    layer_uri = f"{self.output_file_path}|layername={layer_name}"
×
340
                    layer = QgsVectorLayer(layer_uri, layer_name.replace('errors_', '', 1), "ogr")
×
341
                    if layer.isValid():
×
342
                        added_layer = QgsProject.instance().addMapLayer(layer, False)
×
343
                        if added_layer.geometryType() in [
×
344
                            Qgis.GeometryType.Point,
345
                            Qgis.GeometryType.Line,
346
                            Qgis.GeometryType.Polygon,
347
                        ]:
348
                            added_layer.loadNamedStyle(
×
349
                                str(STYLE_DIR / f"checker_{added_layer.geometryType().name.lower()}.qml")
350
                            )
351
                        layer_tree_item = group.addLayer(added_layer)
×
352
                        layer_tree_item.setCustomProperty("showFeatureCount", True)
×
353
                    else:
354
                        feedback.reportError(f"Layer {layer_name} is not valid")
×
355
                conn = None  # Close the connection
×
356
            else:
357
                feedback.reportError(f"Could not open GeoPackage file: {self.output_file_path}")
×
358
            feedback.pushInfo(f"Added results to layer 'Check results: {self.schema_name}'")
×
359
        return {self.OUTPUT: self.output_file_path}
×
360

361
    def name(self):
1✔
362
        """
363
        Returns the algorithm name, used for identifying the algorithm. This
364
        string should be fixed for the algorithm, and must not be localised.
365
        The name should be unique within each provider. Names should contain
366
        lowercase alphanumeric characters only and no spaces or other
367
        formatting characters.
368
        """
369
        return "check_schematisation"
×
370

371
    def displayName(self):
1✔
372
        """
373
        Returns the translated algorithm name, which should be used for any
374
        user-visible display of the algorithm name.
375
        """
376
        return self.tr("Check Schematisation")
×
377

378
    def group(self):
1✔
379
        """
380
        Returns the name of the group this algorithm belongs to. This string
381
        should be localised.
382
        """
383
        return self.tr(self.groupId())
×
384

385
    def groupId(self):
1✔
386
        """
387
        Returns the unique ID of the group this algorithm belongs to. This
388
        string should be fixed for the algorithm, and must not be localised.
389
        The group id should be unique within each provider. Group id should
390
        contain lowercase alphanumeric characters only and no spaces or other
391
        formatting characters.
392
        """
393
        return "Schematisation"
×
394

395
    def tr(self, string):
1✔
396
        return QCoreApplication.translate("Processing", string)
×
397

398
    def createInstance(self):
1✔
399
        return CheckSchematisationAlgorithm()
×
400

401

402
class ImportHydXAlgorithm(QgsProcessingAlgorithm):
1✔
403
    """
404
    Import data from GWSW HydX to a Rana schematisation
405
    """
406

407
    INPUT_DATASET_NAME = "INPUT_DATASET_NAME"
1✔
408
    HYDX_DOWNLOAD_DIRECTORY = "HYDX_DOWNLOAD_DIRECTORY"
1✔
409
    INPUT_HYDX_DIRECTORY = "INPUT_HYDX_DIRECTORY"
1✔
410
    TARGET_SCHEMATISATION = "TARGET_SCHEMATISATION"
1✔
411

412
    def initAlgorithm(self, config):
1✔
413
        self.addParameter(
×
414
            QgsProcessingParameterFile(
415
                self.TARGET_SCHEMATISATION, "Target Rana schematisation", fileFilter="GeoPackage (*.gpkg)"
416
            )
417
        )
418

419
        self.addParameter(
×
420
            QgsProcessingParameterFile(
421
                self.INPUT_HYDX_DIRECTORY,
422
                "GWSW HydX directory (local)",
423
                behavior=QgsProcessingParameterFile.Behavior.Folder,
424
                optional=True,
425
            )
426
        )
427

428
        self.addParameter(
×
429
            QgsProcessingParameterString(
430
                self.INPUT_DATASET_NAME, "GWSW dataset name (online)", optional=True
431
            )
432
        )
433

434
        self.addParameter(
×
435
            QgsProcessingParameterFolderDestination(
436
                self.HYDX_DOWNLOAD_DIRECTORY,
437
                "Destination directory for GWSW HydX dataset download",
438
                optional=True,
439
            )
440
        )
441

442
    def processAlgorithm(self, parameters, context, feedback):
1✔
443
        hydx_dataset_name = self.parameterAsString(
×
444
            parameters, self.INPUT_DATASET_NAME, context
445
        )
446
        hydx_download_dir = self.parameterAsString(
×
447
            parameters, self.HYDX_DOWNLOAD_DIRECTORY, context
448
        )
449
        hydx_path = self.parameterAsString(
×
450
            parameters, self.INPUT_HYDX_DIRECTORY, context
451
        )
452
        out_path = self.parameterAsFile(parameters, self.TARGET_SCHEMATISATION, context)
×
453
        threedi_db = get_threedi_database(filename=out_path, feedback=feedback)
×
454
        if not threedi_db:
×
455
            raise QgsProcessingException(
×
456
                f"Unable to connect to Rana schematisation geopackage '{out_path}'"
457
            )
458
        try:
×
459
            schema = threedi_db.schema
×
460
            schema.validate_schema()
×
461

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

506
    def name(self):
1✔
507
        """
508
        Returns the algorithm name, used for identifying the algorithm. This
509
        string should be fixed for the algorithm, and must not be localised.
510
        The name should be unique within each provider. Names should contain
511
        lowercase alphanumeric characters only and no spaces or other
512
        formatting characters.
513
        """
514
        return "import_hydx"
×
515

516
    def displayName(self):
1✔
517
        """
518
        Returns the translated algorithm name, which should be used for any
519
        user-visible display of the algorithm name.
520
        """
521
        return self.tr("Import GWSW HydX")
×
522

523
    def shortHelpString(self):
1✔
524
        return """
×
525
        <h3>Introduction</h3>
526
        <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>
527
        <p>A log file will be created in the same directory as the Target Rana schematisation. Please check this log file after the import has completed.&nbsp;&nbsp;</p>
528
        <h3>Parameters</h3>
529
        <h4>Target Rana schematisation</h4>
530
        <p>GeoPackage (.gpkg) file that contains the vector layers and tables of a Rana schematisation. Imported data will be added to any data already contained in the schematisation.</p>
531
        <h4>GWSW HydX directory (local)</h4>
532
        <p>Use this option if you have already downloaded a GWSW HydX dataset to a local directory.</p>
533
        <h4>GWSW dataset name (online)</h4>
534
        <p>Use this option if you want to download a GWSW HydX dataset.</p>
535
        <h4>Destination directory for GWSW HydX dataset download</h4>
536
        <p>If you have chosen to download a GWSW HydX dataset, this is the directory it will be downloaded to.</p>
537
        """
538

539
    def group(self):
1✔
540
        """
541
        Returns the name of the group this algorithm belongs to. This string
542
        should be localised.
543
        """
544
        return self.tr(self.groupId())
×
545

546
    def groupId(self):
1✔
547
        """
548
        Returns the unique ID of the group this algorithm belongs to. This
549
        string should be fixed for the algorithm, and must not be localised.
550
        The group id should be unique within each provider. Group id should
551
        contain lowercase alphanumeric characters only and no spaces or other
552
        formatting characters.
553
        """
554
        return "Schematisation"
×
555

556
    def tr(self, string):
1✔
557
        return QCoreApplication.translate("Processing", string)
×
558

559
    def createInstance(self):
1✔
560
        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