threedi-modelchecker 2.6.3.dev1__tar.gz → 2.7.1.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/CHANGES.rst +8 -1
- {threedi-modelchecker-2.6.3.dev1/threedi_modelchecker.egg-info → threedi-modelchecker-2.7.1.dev0}/PKG-INFO +2 -2
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/pyproject.toml +1 -1
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/__init__.py +1 -1
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/geo_query.py +1 -1
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/other.py +65 -34
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/raster.py +34 -11
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/config.py +305 -476
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/interfaces/raster_interface.py +4 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/interfaces/raster_interface_gdal.py +7 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/interfaces/raster_interface_rasterio.py +4 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/model_checks.py +1 -0
- threedi-modelchecker-2.7.1.dev0/threedi_modelchecker/tests/data/empty.gpkg +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/factories.py +31 -34
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_checks_base.py +43 -124
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_checks_factories.py +1 -1
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_checks_other.py +76 -14
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_checks_raster.py +56 -54
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0/threedi_modelchecker.egg-info}/PKG-INFO +2 -2
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker.egg-info/requires.txt +1 -1
- threedi-modelchecker-2.6.3.dev1/threedi_modelchecker/tests/data/empty.gpkg +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/LICENSE +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/MANIFEST.in +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/README.rst +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/setup.cfg +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/__init__.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/base.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/cross_section_definitions.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/factories.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/checks/timeseries.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/exporters.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/interfaces/__init__.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/scripts.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/__init__.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/conftest.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/data/__init__.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_checks_cross_section_definitions.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_checks_timeseries.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_exporters.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker/tests/test_model_checks.py +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker.egg-info/SOURCES.txt +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker.egg-info/dependency_links.txt +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker.egg-info/entry_points.txt +0 -0
- {threedi-modelchecker-2.6.3.dev1 → threedi-modelchecker-2.7.1.dev0}/threedi_modelchecker.egg-info/top_level.txt +0 -0
|
@@ -2,7 +2,14 @@ Changelog of threedi-modelchecker
|
|
|
2
2
|
=================================
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
2.
|
|
5
|
+
2.7.1 (unreleased)
|
|
6
|
+
------------------
|
|
7
|
+
- Add info check 1406 to inform the user if a raster is not compressed.
|
|
8
|
+
- Add check 799 to warn if raster friction pixels are < 1 while Chezy friction is selected
|
|
9
|
+
- Change error message for check 1500
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
2.7.0 (2024-03-12)
|
|
6
13
|
------------------
|
|
7
14
|
- Support geopackage
|
|
8
15
|
- Support changes in threedi-schema (0.220) needed for geopackage support
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: threedi-modelchecker
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.1.dev0
|
|
4
4
|
Summary: Checks validity of a 3Di schematisation
|
|
5
5
|
Author-email: Nelen & Schuurmans <info@nelen-schuurmans.nl>
|
|
6
6
|
License: MIT
|
|
@@ -15,7 +15,7 @@ License-File: LICENSE
|
|
|
15
15
|
Requires-Dist: Click
|
|
16
16
|
Requires-Dist: GeoAlchemy2!=0.11.*,>=0.9
|
|
17
17
|
Requires-Dist: SQLAlchemy>=1.4
|
|
18
|
-
Requires-Dist: threedi-schema==0.
|
|
18
|
+
Requires-Dist: threedi-schema==0.222.dev0
|
|
19
19
|
Provides-Extra: rasterio
|
|
20
20
|
Requires-Dist: rasterio>=1.3; extra == "rasterio"
|
|
21
21
|
Provides-Extra: test
|
|
@@ -8,7 +8,7 @@ DEFAULT_EPSG = 28992
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def epsg_code_query():
|
|
11
|
-
epsg_code = Query(models.
|
|
11
|
+
epsg_code = Query(models.ModelSettings.epsg_code).limit(1).scalar_subquery()
|
|
12
12
|
return func.coalesce(epsg_code, literal(DEFAULT_EPSG)).label("epsg_code")
|
|
13
13
|
|
|
14
14
|
|
|
@@ -10,15 +10,6 @@ from .base import BaseCheck, CheckLevel
|
|
|
10
10
|
from .cross_section_definitions import cross_section_configuration
|
|
11
11
|
from .geo_query import distance, length, transform
|
|
12
12
|
|
|
13
|
-
# Use these to make checks only work on the first global settings entry:
|
|
14
|
-
first_setting = (
|
|
15
|
-
Query(models.GlobalSetting.id)
|
|
16
|
-
.order_by(models.GlobalSetting.id)
|
|
17
|
-
.limit(1)
|
|
18
|
-
.scalar_subquery()
|
|
19
|
-
)
|
|
20
|
-
first_setting_filter = models.GlobalSetting.id == first_setting
|
|
21
|
-
|
|
22
13
|
|
|
23
14
|
class CorrectAggregationSettingsExist(BaseCheck):
|
|
24
15
|
"""Check if aggregation settings are correctly filled with aggregation_method and flow_variable as required"""
|
|
@@ -30,30 +21,24 @@ class CorrectAggregationSettingsExist(BaseCheck):
|
|
|
30
21
|
*args,
|
|
31
22
|
**kwargs,
|
|
32
23
|
):
|
|
33
|
-
super().__init__(column=models.
|
|
24
|
+
super().__init__(column=models.ModelSettings.id, *args, **kwargs)
|
|
34
25
|
self.aggregation_method = aggregation_method.value
|
|
35
26
|
self.flow_variable = flow_variable.value
|
|
36
27
|
|
|
37
28
|
def get_invalid(self, session: Session) -> List[NamedTuple]:
|
|
38
|
-
global_settings = self.to_check(session)
|
|
29
|
+
global_settings = self.to_check(session)
|
|
39
30
|
correctly_defined = session.execute(
|
|
40
|
-
select(models.AggregationSettings)
|
|
41
|
-
.filter(
|
|
31
|
+
select(models.AggregationSettings).filter(
|
|
42
32
|
models.AggregationSettings.aggregation_method
|
|
43
33
|
== self.aggregation_method,
|
|
44
34
|
models.AggregationSettings.flow_variable == self.flow_variable,
|
|
45
35
|
)
|
|
46
|
-
.filter(
|
|
47
|
-
models.AggregationSettings.global_settings_id
|
|
48
|
-
== global_settings.subquery().c.id
|
|
49
|
-
)
|
|
50
36
|
).all()
|
|
51
|
-
|
|
52
37
|
return global_settings.all() if len(correctly_defined) == 0 else []
|
|
53
38
|
|
|
54
39
|
def description(self) -> str:
|
|
55
40
|
return (
|
|
56
|
-
"To use the water balance tool,
|
|
41
|
+
"To use the water balance tool, aggregation_settings should have a row where "
|
|
57
42
|
f"aggregation_method is {self.aggregation_method} and flow_variable is {self.flow_variable}."
|
|
58
43
|
)
|
|
59
44
|
|
|
@@ -244,12 +229,14 @@ class Use0DFlowCheck(BaseCheck):
|
|
|
244
229
|
"""
|
|
245
230
|
|
|
246
231
|
def __init__(self, *args, **kwargs):
|
|
247
|
-
super().__init__(
|
|
232
|
+
super().__init__(
|
|
233
|
+
column=models.SimulationTemplateSettings.use_0d_inflow, *args, **kwargs
|
|
234
|
+
)
|
|
248
235
|
|
|
249
236
|
def to_check(self, session):
|
|
250
237
|
"""Return a Query object on which this check is applied"""
|
|
251
|
-
return session.query(models.
|
|
252
|
-
models.
|
|
238
|
+
return session.query(models.SimulationTemplateSettings).filter(
|
|
239
|
+
models.SimulationTemplateSettings.use_0d_inflow != 0
|
|
253
240
|
)
|
|
254
241
|
|
|
255
242
|
def get_invalid(self, session):
|
|
@@ -301,7 +288,7 @@ class ConnectionNodes(BaseCheck):
|
|
|
301
288
|
class ConnectionNodesLength(BaseCheck):
|
|
302
289
|
"""Check that the distance between `start_node` and `end_node` is at least
|
|
303
290
|
`min_distance`. The coords will be transformed into (the first entry) of
|
|
304
|
-
|
|
291
|
+
ModelSettings.epsg_code.
|
|
305
292
|
"""
|
|
306
293
|
|
|
307
294
|
def __init__(
|
|
@@ -469,7 +456,7 @@ class ChannelManholeLevelCheck(BaseCheck):
|
|
|
469
456
|
|
|
470
457
|
class OpenChannelsWithNestedNewton(BaseCheck):
|
|
471
458
|
"""Checks whether the model has any closed cross-section in use when the
|
|
472
|
-
NumericalSettings.
|
|
459
|
+
NumericalSettings.use_nested_newton is turned off.
|
|
473
460
|
|
|
474
461
|
See https://github.com/nens/threeditoolbox/issues/522
|
|
475
462
|
"""
|
|
@@ -479,7 +466,7 @@ class OpenChannelsWithNestedNewton(BaseCheck):
|
|
|
479
466
|
column=models.CrossSectionDefinition.id,
|
|
480
467
|
level=level,
|
|
481
468
|
filters=Query(models.NumericalSettings)
|
|
482
|
-
.filter(models.NumericalSettings.
|
|
469
|
+
.filter(models.NumericalSettings.use_nested_newton == 0)
|
|
483
470
|
.exists(),
|
|
484
471
|
*args,
|
|
485
472
|
**kwargs,
|
|
@@ -532,8 +519,8 @@ class OpenChannelsWithNestedNewton(BaseCheck):
|
|
|
532
519
|
def description(self) -> str:
|
|
533
520
|
return (
|
|
534
521
|
f"{self.column_name} has a closed cross section definition while "
|
|
535
|
-
f"NumericalSettings.
|
|
536
|
-
f"This gives convergence issues. We recommend setting
|
|
522
|
+
f"NumericalSettings.use_nested_newton is switched off. "
|
|
523
|
+
f"This gives convergence issues. We recommend setting use_nested_newton = 1."
|
|
537
524
|
)
|
|
538
525
|
|
|
539
526
|
|
|
@@ -731,7 +718,8 @@ class PumpStorageTimestepCheck(BaseCheck):
|
|
|
731
718
|
.filter(
|
|
732
719
|
(models.ConnectionNode.storage_area != None)
|
|
733
720
|
& (
|
|
734
|
-
(
|
|
721
|
+
(
|
|
722
|
+
# calculate how many seconds the pumpstation takes to empty its storage: (storage * height)/pump capacity
|
|
735
723
|
(
|
|
736
724
|
# Arithmetic operations on None return None, so without this
|
|
737
725
|
# conditional type cast, no invalid results would be returned
|
|
@@ -744,9 +732,7 @@ class PumpStorageTimestepCheck(BaseCheck):
|
|
|
744
732
|
)
|
|
745
733
|
)
|
|
746
734
|
/ (models.Pumpstation.capacity / 1000)
|
|
747
|
-
< Query(models.
|
|
748
|
-
.filter(first_setting_filter)
|
|
749
|
-
.scalar_subquery()
|
|
735
|
+
< Query(models.TimeStepSettings.time_step).scalar_subquery()
|
|
750
736
|
)
|
|
751
737
|
)
|
|
752
738
|
.all()
|
|
@@ -819,7 +805,7 @@ class InflowNoFeaturesCheck(BaseCheck):
|
|
|
819
805
|
"""Check that the surface table in the global use_0d_inflow setting contains at least 1 feature."""
|
|
820
806
|
|
|
821
807
|
def __init__(self, *args, surface_table, condition=True, **kwargs):
|
|
822
|
-
super().__init__(*args, column=models.
|
|
808
|
+
super().__init__(*args, column=models.ModelSettings.id, **kwargs)
|
|
823
809
|
self.surface_table = surface_table
|
|
824
810
|
self.condition = condition
|
|
825
811
|
|
|
@@ -828,13 +814,13 @@ class InflowNoFeaturesCheck(BaseCheck):
|
|
|
828
814
|
select(func.count(self.surface_table.id))
|
|
829
815
|
).scalar()
|
|
830
816
|
return (
|
|
831
|
-
session.query(models.
|
|
817
|
+
session.query(models.ModelSettings)
|
|
832
818
|
.filter(self.condition, surface_table_length == 0)
|
|
833
819
|
.all()
|
|
834
820
|
)
|
|
835
821
|
|
|
836
822
|
def description(self) -> str:
|
|
837
|
-
return f"
|
|
823
|
+
return f"model_settings.use_0d_inflow is set to use {self.surface_table.__tablename__}, but {self.surface_table.__tablename__} does not contain any features."
|
|
838
824
|
|
|
839
825
|
|
|
840
826
|
class NodeSurfaceConnectionsCheck(BaseCheck):
|
|
@@ -1082,3 +1068,48 @@ class AllPresentVariableVegetationParameters(AllPresent):
|
|
|
1082
1068
|
)
|
|
1083
1069
|
)
|
|
1084
1070
|
)
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
class UsedSettingsPresentCheck(BaseCheck):
|
|
1074
|
+
def __init__(
|
|
1075
|
+
self,
|
|
1076
|
+
column,
|
|
1077
|
+
settings_table,
|
|
1078
|
+
filters=None,
|
|
1079
|
+
level=CheckLevel.ERROR,
|
|
1080
|
+
error_code=0,
|
|
1081
|
+
):
|
|
1082
|
+
super().__init__(column, filters, level, error_code)
|
|
1083
|
+
self.settings_table = settings_table
|
|
1084
|
+
|
|
1085
|
+
def get_invalid(self, session: Session) -> List[NamedTuple]:
|
|
1086
|
+
# more than 1 row should be caught by another check
|
|
1087
|
+
all_results = self.to_check(session).filter(self.column == True).all()
|
|
1088
|
+
use_cols = len(all_results) > 0
|
|
1089
|
+
if use_cols and session.query(self.settings_table).count() == 0:
|
|
1090
|
+
return all_results
|
|
1091
|
+
return []
|
|
1092
|
+
|
|
1093
|
+
def description(self) -> str:
|
|
1094
|
+
return f"{self.column_name} in {self.table.name} is set to True but {self.settings_table.__tablename__} is empty"
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
class MaxOneRecordCheck(BaseCheck):
|
|
1098
|
+
def __init__(self, column, filters=None, level=CheckLevel.ERROR, error_code=0):
|
|
1099
|
+
super().__init__(column, filters, level, error_code)
|
|
1100
|
+
self.observed_length = 0
|
|
1101
|
+
|
|
1102
|
+
def get_invalid(self, session: Session) -> List[NamedTuple]:
|
|
1103
|
+
# return mock list in case the table is empty when it shouldn't be
|
|
1104
|
+
all_results = self.to_check(session).all()
|
|
1105
|
+
self.observed_length = len(all_results)
|
|
1106
|
+
if self.observed_length > 1:
|
|
1107
|
+
return all_results if self.observed_length > 0 else ["foo"]
|
|
1108
|
+
else:
|
|
1109
|
+
return []
|
|
1110
|
+
|
|
1111
|
+
def description(self) -> str:
|
|
1112
|
+
return (
|
|
1113
|
+
f"{self.table.name} has {self.observed_length} rows, "
|
|
1114
|
+
f"but should have at most 1 row."
|
|
1115
|
+
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from math import isclose
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Dict, Optional, Type
|
|
5
5
|
|
|
6
6
|
from threedi_schema import models
|
|
7
7
|
|
|
@@ -9,6 +9,12 @@ from threedi_modelchecker.interfaces import GDALRasterInterface, RasterInterface
|
|
|
9
9
|
|
|
10
10
|
from .base import BaseCheck
|
|
11
11
|
|
|
12
|
+
# interface_cls.is_valid_geotiff returns False if the dataset is empty
|
|
13
|
+
# (such as when the column doesn't reference a file) or the dataset is not a valid geotiff.
|
|
14
|
+
# It's used on every raster check which reads the raster file in any way,
|
|
15
|
+
# to prevent any other code in the check from executing on a bad file.
|
|
16
|
+
# On such datasets, RasterExistsCheck and/or RasterIsValidCheck will be raised.
|
|
17
|
+
|
|
12
18
|
|
|
13
19
|
class Context:
|
|
14
20
|
pass
|
|
@@ -16,7 +22,7 @@ class Context:
|
|
|
16
22
|
|
|
17
23
|
@dataclass
|
|
18
24
|
class ServerContext(Context):
|
|
19
|
-
available_rasters:
|
|
25
|
+
available_rasters: Dict[str, str]
|
|
20
26
|
raster_interface: Type[RasterInterface] = GDALRasterInterface
|
|
21
27
|
|
|
22
28
|
|
|
@@ -62,7 +68,9 @@ class BaseRasterCheck(BaseCheck):
|
|
|
62
68
|
]
|
|
63
69
|
|
|
64
70
|
def get_path_local(self, record, context: LocalContext) -> Optional[str]:
|
|
65
|
-
abs_path = context.base_path
|
|
71
|
+
abs_path = context.base_path.joinpath(
|
|
72
|
+
"rasters", getattr(record, self.column.name)
|
|
73
|
+
)
|
|
66
74
|
if abs_path.exists():
|
|
67
75
|
return str(abs_path)
|
|
68
76
|
|
|
@@ -153,7 +161,7 @@ class RasterHasMatchingEPSGCheck(BaseRasterCheck):
|
|
|
153
161
|
"""Check whether a raster's EPSG code matches the EPSG code in the global settings for the SQLite."""
|
|
154
162
|
|
|
155
163
|
def get_invalid(self, session):
|
|
156
|
-
epsg_code_query = session.query(models.
|
|
164
|
+
epsg_code_query = session.query(models.ModelSettings.epsg_code).first()
|
|
157
165
|
if epsg_code_query is not None:
|
|
158
166
|
self.epsg_code = epsg_code_query[0]
|
|
159
167
|
else:
|
|
@@ -169,7 +177,7 @@ class RasterHasMatchingEPSGCheck(BaseRasterCheck):
|
|
|
169
177
|
return raster.epsg_code == self.epsg_code
|
|
170
178
|
|
|
171
179
|
def description(self):
|
|
172
|
-
return f"The file in {self.column_name} has no EPSG code or the EPSG code does not match does not match
|
|
180
|
+
return f"The file in {self.column_name} has no EPSG code or the EPSG code does not match does not match model_settings.epsg_code"
|
|
173
181
|
|
|
174
182
|
|
|
175
183
|
class RasterSquareCellsCheck(BaseRasterCheck):
|
|
@@ -196,9 +204,11 @@ class RasterGridSizeCheck(BaseRasterCheck):
|
|
|
196
204
|
"""Check whether the global settings' grid size is an even multiple of a raster's cell size (at least 2x)."""
|
|
197
205
|
|
|
198
206
|
def get_invalid(self, session):
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
minimum_cell_size_query = session.query(
|
|
208
|
+
models.ModelSettings.minimum_cell_size
|
|
209
|
+
).first()
|
|
210
|
+
if minimum_cell_size_query is not None:
|
|
211
|
+
self.minimum_cell_size = minimum_cell_size_query[0]
|
|
202
212
|
else:
|
|
203
213
|
return []
|
|
204
214
|
return super().get_invalid(session)
|
|
@@ -211,17 +221,17 @@ class RasterGridSizeCheck(BaseRasterCheck):
|
|
|
211
221
|
try:
|
|
212
222
|
return (
|
|
213
223
|
isclose(
|
|
214
|
-
a=((self.
|
|
224
|
+
a=((self.minimum_cell_size / raster.pixel_size[0]) % 2),
|
|
215
225
|
b=0,
|
|
216
226
|
rel_tol=1e-09,
|
|
217
227
|
)
|
|
218
|
-
) and (self.
|
|
228
|
+
) and (self.minimum_cell_size >= (2 * raster.pixel_size[0]))
|
|
219
229
|
# if one of the fields is a NoneType it will be caught elsewhere
|
|
220
230
|
except TypeError:
|
|
221
231
|
return True
|
|
222
232
|
|
|
223
233
|
def description(self):
|
|
224
|
-
return "
|
|
234
|
+
return "model_settings.minimum_cell_size is not a positive even multiple of the raster cell size."
|
|
225
235
|
|
|
226
236
|
|
|
227
237
|
class RasterPixelCountCheck(BaseRasterCheck):
|
|
@@ -300,6 +310,19 @@ class RasterRangeCheck(BaseRasterCheck):
|
|
|
300
310
|
return f"{self.column_name} has values {' and/or '.join(parts)} or is empty"
|
|
301
311
|
|
|
302
312
|
|
|
313
|
+
class RasterCompressionUsedCheck(BaseRasterCheck):
|
|
314
|
+
"""Checks whether compression was used for the raster"""
|
|
315
|
+
|
|
316
|
+
def is_valid(self, path: str, interface_cls: Type[RasterInterface]):
|
|
317
|
+
with interface_cls(path) as raster:
|
|
318
|
+
if not raster.is_valid_geotiff:
|
|
319
|
+
return True
|
|
320
|
+
return raster.compression != "NONE"
|
|
321
|
+
|
|
322
|
+
def description(self):
|
|
323
|
+
return f"Raster {self.column_name} is not compressed. It is recommended to use DEFLATE compression. This speeds up uploading and downloading and reduces storage space."
|
|
324
|
+
|
|
325
|
+
|
|
303
326
|
@dataclass
|
|
304
327
|
class GDALUnavailable:
|
|
305
328
|
id: int
|