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

nens / ThreeDiToolbox / #2709

10 Jun 2026 10:34AM UTC coverage: 36.476% (-0.1%) from 36.578%
#2709

push

coveralls-python

web-flow
Merge 5578a72a9 into 21d44a4fc

7 of 58 new or added lines in 5 files covered. (12.07%)

1 existing line in 1 file now uncovered.

5320 of 14585 relevant lines covered (36.48%)

0.36 hits per line

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

22.95
/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:
×
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
        # SchemaStructureError will only be called if MigrationMissingError is not
NEW
136
        except errors.SchemaStructureError as e:
×
NEW
137
            msg = str(e)[0].lower() + str(e)[1:]
×
NEW
138
            feedback.pushWarning(f"The schematisation was already migrated to the latest version but the {msg}")
×
NEW
139
            return {self.OUTPUT: None}
×
140
        success = True
×
141
        return {self.OUTPUT: success}
×
142

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

153
    def displayName(self):
1✔
154
        """
155
        Returns the translated algorithm name, which should be used for any
156
        user-visible display of the algorithm name.
157
        """
158
        return self.tr("Migrate schematisation database")
×
159

160
    def group(self):
1✔
161
        """
162
        Returns the name of the group this algorithm belongs to. This string
163
        should be localised.
164
        """
165
        return self.tr(self.groupId())
×
166

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

177
    def tr(self, string):
1✔
178
        return QCoreApplication.translate("Processing", string)
×
179

180
    def createInstance(self):
1✔
181
        return MigrateAlgorithm()
×
182

183

184
class CheckSchematisationAlgorithm(QgsProcessingAlgorithm):
1✔
185
    """
186
    Run the schematisation checker
187
    """
188

189
    INPUT = "INPUT"
1✔
190
    OUTPUT = "OUTPUT"
1✔
191
    ADD_TO_PROJECT = "ADD_TO_PROJECT"
1✔
192

193
    def initAlgorithm(self, config):
1✔
194
        self.addParameter(
×
195
            QgsProcessingParameterFile(
196
                self.INPUT, self.tr("Rana schematisation geopackage"), fileFilter="GeoPackage (*.gpkg)"
197
            )
198
        )
199

200
        self.addParameter(
×
201
            QgsProcessingParameterFileDestination(
202
                self.OUTPUT, self.tr("Output"), fileFilter="csv"
203
            )
204
        )
205

206
        self.addParameter(
×
207
            QgsProcessingParameterBoolean(
208
                self.ADD_TO_PROJECT, self.tr("Add result to project"), defaultValue=True
209
            )
210
        )
211

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

256
        # Create an output GeoPackage
257
        feedback.pushInfo("Writing checker results to geopackage...")
×
258
        gdal.UseExceptions()
×
259
        driver = ogr.GetDriverByName("GPKG")
×
260
        data_source = driver.CreateDataSource(self.output_file_path)
×
261
        if data_source is None:
×
262
            feedback.pushWarning(
×
263
                f"Unable to create the GeoPackage '{self.output_file_path}', check the directory permissions."
264
            )
265
            return {self.OUTPUT: None}
×
266

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

299
            # Add fields
300
            layer.CreateField(ogr.FieldDefn("level", ogr.OFTString))
×
301
            layer.CreateField(ogr.FieldDefn("error_code", ogr.OFTString))
×
302
            layer.CreateField(ogr.FieldDefn("id", ogr.OFTString))
×
303
            layer.CreateField(ogr.FieldDefn("table", ogr.OFTString))
×
304
            layer.CreateField(ogr.FieldDefn("column", ogr.OFTString))
×
305
            layer.CreateField(ogr.FieldDefn("value", ogr.OFTString))
×
306
            layer.CreateField(ogr.FieldDefn("description", ogr.OFTString))
×
307

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

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

369
    def name(self):
1✔
370
        """
371
        Returns the algorithm name, used for identifying the algorithm. This
372
        string should be fixed for the algorithm, and must not be localised.
373
        The name should be unique within each provider. Names should contain
374
        lowercase alphanumeric characters only and no spaces or other
375
        formatting characters.
376
        """
377
        return "check_schematisation"
×
378

379
    def displayName(self):
1✔
380
        """
381
        Returns the translated algorithm name, which should be used for any
382
        user-visible display of the algorithm name.
383
        """
384
        return self.tr("Check Schematisation")
×
385

386
    def group(self):
1✔
387
        """
388
        Returns the name of the group this algorithm belongs to. This string
389
        should be localised.
390
        """
391
        return self.tr(self.groupId())
×
392

393
    def groupId(self):
1✔
394
        """
395
        Returns the unique ID of the group this algorithm belongs to. This
396
        string should be fixed for the algorithm, and must not be localised.
397
        The group id should be unique within each provider. Group id should
398
        contain lowercase alphanumeric characters only and no spaces or other
399
        formatting characters.
400
        """
401
        return "Schematisation"
×
402

403
    def tr(self, string):
1✔
404
        return QCoreApplication.translate("Processing", string)
×
405

406
    def createInstance(self):
1✔
407
        return CheckSchematisationAlgorithm()
×
408

409

410
class ImportHydXAlgorithm(QgsProcessingAlgorithm):
1✔
411
    """
412
    Import data from GWSW HydX to a Rana schematisation
413
    """
414

415
    INPUT_DATASET_NAME = "INPUT_DATASET_NAME"
1✔
416
    HYDX_DOWNLOAD_DIRECTORY = "HYDX_DOWNLOAD_DIRECTORY"
1✔
417
    INPUT_HYDX_DIRECTORY = "INPUT_HYDX_DIRECTORY"
1✔
418
    TARGET_SCHEMATISATION = "TARGET_SCHEMATISATION"
1✔
419

420
    def initAlgorithm(self, config):
1✔
421
        self.addParameter(
×
422
            QgsProcessingParameterFile(
423
                self.TARGET_SCHEMATISATION, "Target Rana schematisation", fileFilter="GeoPackage (*.gpkg)"
424
            )
425
        )
426

427
        self.addParameter(
×
428
            QgsProcessingParameterFile(
429
                self.INPUT_HYDX_DIRECTORY,
430
                "GWSW HydX directory (local)",
431
                behavior=QgsProcessingParameterFile.Behavior.Folder,
432
                optional=True,
433
            )
434
        )
435

436
        self.addParameter(
×
437
            QgsProcessingParameterString(
438
                self.INPUT_DATASET_NAME, "GWSW dataset name (online)", optional=True
439
            )
440
        )
441

442
        self.addParameter(
×
443
            QgsProcessingParameterFolderDestination(
444
                self.HYDX_DOWNLOAD_DIRECTORY,
445
                "Destination directory for GWSW HydX dataset download",
446
                optional=True,
447
            )
448
        )
449

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

515
    def name(self):
1✔
516
        """
517
        Returns the algorithm name, used for identifying the algorithm. This
518
        string should be fixed for the algorithm, and must not be localised.
519
        The name should be unique within each provider. Names should contain
520
        lowercase alphanumeric characters only and no spaces or other
521
        formatting characters.
522
        """
523
        return "import_hydx"
×
524

525
    def displayName(self):
1✔
526
        """
527
        Returns the translated algorithm name, which should be used for any
528
        user-visible display of the algorithm name.
529
        """
530
        return self.tr("Import GWSW HydX")
×
531

532
    def shortHelpString(self):
1✔
533
        return """
×
534
        <h3>Introduction</h3>
535
        <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>
536
        <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>
537
        <h3>Parameters</h3>
538
        <h4>Target Rana schematisation</h4>
539
        <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>
540
        <h4>GWSW HydX directory (local)</h4>
541
        <p>Use this option if you have already downloaded a GWSW HydX dataset to a local directory.</p>
542
        <h4>GWSW dataset name (online)</h4>
543
        <p>Use this option if you want to download a GWSW HydX dataset.</p>
544
        <h4>Destination directory for GWSW HydX dataset download</h4>
545
        <p>If you have chosen to download a GWSW HydX dataset, this is the directory it will be downloaded to.</p>
546
        """
547

548
    def group(self):
1✔
549
        """
550
        Returns the name of the group this algorithm belongs to. This string
551
        should be localised.
552
        """
553
        return self.tr(self.groupId())
×
554

555
    def groupId(self):
1✔
556
        """
557
        Returns the unique ID of the group this algorithm belongs to. This
558
        string should be fixed for the algorithm, and must not be localised.
559
        The group id should be unique within each provider. Group id should
560
        contain lowercase alphanumeric characters only and no spaces or other
561
        formatting characters.
562
        """
563
        return "Schematisation"
×
564

565
    def tr(self, string):
1✔
566
        return QCoreApplication.translate("Processing", string)
×
567

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