hydamo-validation 1.3.0b1__tar.gz → 1.3.0b3__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.

Potentially problematic release.


This version of hydamo-validation might be problematic. Click here for more details.

Files changed (49) hide show
  1. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/PKG-INFO +1 -1
  2. hydamo_validation-1.3.0b3/hydamo_validation/__init__.py +22 -0
  3. hydamo_validation-1.3.0b3/hydamo_validation/functions/__init__.py +0 -0
  4. hydamo_validation-1.3.0b3/hydamo_validation/functions/general.py +319 -0
  5. hydamo_validation-1.3.0b3/hydamo_validation/functions/logic.py +338 -0
  6. hydamo_validation-1.3.0b3/hydamo_validation/functions/topologic.py +665 -0
  7. hydamo_validation-1.3.0b3/hydamo_validation/schemas/hydamo/HyDAMO_2.2.json +5946 -0
  8. hydamo_validation-1.3.0b3/hydamo_validation/schemas/hydamo/HyDAMO_2.3.json +6602 -0
  9. hydamo_validation-1.3.0b3/hydamo_validation/schemas/rules/rules_1.0.json +1134 -0
  10. hydamo_validation-1.3.0b3/hydamo_validation/schemas/rules/rules_1.1.json +1134 -0
  11. hydamo_validation-1.3.0b3/hydamo_validation/schemas/rules/rules_1.2.json +1128 -0
  12. hydamo_validation-1.3.0b3/hydamo_validation/schemas/rules/rules_1.3.json +1128 -0
  13. hydamo_validation-1.3.0b3/hydamo_validation/styles/hydroobject.qml +497 -0
  14. hydamo_validation-1.3.0b3/hydamo_validation/styles/hydroobject.sld +15 -0
  15. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation.egg-info/PKG-INFO +1 -1
  16. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation.egg-info/SOURCES.txt +12 -0
  17. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/pyproject.toml +4 -1
  18. hydamo_validation-1.3.0b1/hydamo_validation/__init__.py +0 -13
  19. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/LICENSE +0 -0
  20. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/README.md +0 -0
  21. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/datamodel.py +0 -0
  22. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/datasets.py +0 -0
  23. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/geometry.py +0 -0
  24. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/logical_validation.py +0 -0
  25. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/styles.py +0 -0
  26. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/summaries.py +0 -0
  27. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/syntax_validation.py +0 -0
  28. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/utils.py +0 -0
  29. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation/validator.py +0 -0
  30. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation.egg-info/dependency_links.txt +0 -0
  31. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation.egg-info/requires.txt +0 -0
  32. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation.egg-info/top_level.txt +0 -0
  33. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/hydamo_validation.egg-info/zip-safe +0 -0
  34. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/setup.cfg +0 -0
  35. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/setup.py +0 -0
  36. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_datasets.py +0 -0
  37. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_dommelerwaard.py +0 -0
  38. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_general_functions.py +0 -0
  39. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_hydamo_2_2.py +0 -0
  40. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_init.py +0 -0
  41. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_logic_functions.py +0 -0
  42. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_not_overlapping.py +0 -0
  43. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_productie.py +0 -0
  44. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_structures_at_interersections.py +0 -0
  45. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_summaries.py +0 -0
  46. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_topologic_functions.py +0 -0
  47. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_validationrules.py +0 -0
  48. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_wrij.py +0 -0
  49. {hydamo_validation-1.3.0b1 → hydamo_validation-1.3.0b3}/tests/test_wrij_profielen.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hydamo_validation
3
- Version: 1.3.0b1
3
+ Version: 1.3.0b3
4
4
  Summary: Validation module for HyDAMO data
5
5
  Author-email: Daniel Tollenaar <daniel@d2hydro.nl>
6
6
  License: MIT
@@ -0,0 +1,22 @@
1
+ __author__ = ["Het Waterschapshuis", "D2HYDRO", "HKV", "HydroConsult"]
2
+ __copyright__ = "Copyright 2021, HyDAMO ValidatieTool"
3
+ __credits__ = ["D2HYDRO", "HKV", "HydroConsult"]
4
+ __version__ = "1.3.0b3"
5
+
6
+ __license__ = "MIT"
7
+ __maintainer__ = "Daniel Tollenaar"
8
+ __email__ = "daniel@d2hydro.nl"
9
+
10
+ import fiona
11
+ from hydamo_validation.functions import topologic as topologic_functions
12
+ from hydamo_validation.functions import logic as logic_functions
13
+ from hydamo_validation.functions import general as general_functions
14
+ from hydamo_validation.validator import validator
15
+
16
+ __all__ = [
17
+ "fiona",
18
+ "topologic_functions",
19
+ "logic_functions",
20
+ "general_functions",
21
+ "validator",
22
+ ]
@@ -0,0 +1,319 @@
1
+ """functions to be executed on gdf."""
2
+
3
+ import geopandas as gpd
4
+ from typing import Literal
5
+ import numpy as np
6
+ from pathlib import Path
7
+ from rasterstats import zonal_stats
8
+ import logging
9
+ import pandas as pd
10
+
11
+ try:
12
+ import rasterio
13
+ except ImportError:
14
+ import gdal # noqa to avoid rasterio.version error: https://github.com/conda-forge/rasterio-feedstock/issues/240
15
+ import rasterio
16
+
17
+ COVERAGES = {}
18
+ # DATA_MODEL = None
19
+ # OBJECT_LAYER = None
20
+
21
+ # We get a false-positive settingwithcopywarning in buffer-function that we supress
22
+ pd.options.mode.chained_assignment = None
23
+
24
+
25
+ def _set_coverage(coverage: str, directory: str):
26
+ """Add a coverage for functions."""
27
+ global COVERAGES
28
+ coverage_path = Path(directory)
29
+ if not coverage_path.exists():
30
+ logging.error(
31
+ (
32
+ f"Path to coverage {coverage} does not exist: ",
33
+ f"{coverage_path.absolute().resolve()}",
34
+ ". Functions using this coverage fail without data.",
35
+ )
36
+ )
37
+ raise FileNotFoundError(f"{coverage_path.absolute().resolve()}")
38
+ COVERAGES[coverage] = coverage_path
39
+
40
+
41
+ def _buffer_row(row, column):
42
+ radius = max(row[column], 0.5)
43
+ return row.geometry.buffer(radius)
44
+
45
+
46
+ def _get_geometric_attribute(gdf, geom_parameter):
47
+ geometry, method = geom_parameter.split(".")
48
+ return getattr(gdf[geometry], method)
49
+
50
+
51
+ def sum(gdf, array: list):
52
+ """Return a sum expression."""
53
+ expression = " + ".join(map(str, array))
54
+ return gdf.eval(expression)
55
+
56
+
57
+ def difference(gdf, left, right, absolute=False):
58
+ """
59
+ Difference between 'left' and 'right'
60
+
61
+ Parameters
62
+ ----------
63
+ gdf : GeoDataFrame
64
+ Input GeoDataFrame
65
+ left : str, numeric
66
+ Left column or value in expression
67
+ right : TYPE
68
+ Right column or value in expression
69
+ absolute : bool, optional
70
+ Absolute (True) or relative difference (False) to left.
71
+ The default is False.
72
+
73
+ Returns
74
+ -------
75
+ result : Series
76
+ Float series
77
+
78
+ """
79
+
80
+ if left in gdf.columns:
81
+ left = gdf[left]
82
+ if right in gdf.columns:
83
+ right = gdf[right]
84
+ if absolute:
85
+ result = (left - right).abs()
86
+ else:
87
+ result = left - right
88
+
89
+ return result
90
+
91
+
92
+ def divide(gdf, left, right):
93
+ """
94
+ Division of 'left' by 'right'
95
+
96
+ Parameters
97
+ ----------
98
+ gdf : GeoDataFrame
99
+ Input GeoDataFrame
100
+ left : str, numeric
101
+ Left column or value in expression
102
+ right : TYPE
103
+ Right column or value in expression
104
+
105
+ Returns
106
+ -------
107
+ result : Series
108
+ Float series
109
+
110
+ """
111
+ expression = " / ".join(map(str, [left, right]))
112
+ return gdf.eval(expression)
113
+
114
+
115
+ def multiply(gdf, left, right):
116
+ """
117
+ Multiply 'left' with 'right'
118
+
119
+ Parameters
120
+ ----------
121
+ gdf : GeoDataFrame
122
+ Input GeoDataFrame
123
+ left : str, numeric
124
+ Left column or value in expression
125
+ right : str, numeric
126
+ Right column or value in expression
127
+
128
+ Returns
129
+ -------
130
+ result : Series
131
+ Float series
132
+
133
+ """
134
+ expression = " * ".join(map(str, [left, right]))
135
+ return gdf.eval(expression)
136
+
137
+
138
+ def buffer(gdf, radius, percentile, coverage="ahn", fill_value: float = None):
139
+ """
140
+ Percentile of coverage-value of an area defined by a radius around the
141
+ object
142
+
143
+ Parameters
144
+ ----------
145
+ gdf : GeoDataFrame
146
+ Input GeoDataFrame
147
+ radius: str, numeric
148
+ Radius around object used to define a cirular area
149
+ percentile : int
150
+ The percentile of the coverage within area around object
151
+ coverage : str, optional
152
+ The coverage to use. The default value is 'ahn'
153
+ fill_value : float, optional
154
+ The fill_value to use when the area is not intersecting the coverage.
155
+ The default is None
156
+
157
+ Returns
158
+ -------
159
+ result : Series
160
+ Float series
161
+
162
+ """
163
+ gdf_out = gdf.copy()
164
+ gdf_out["result"] = np.nan
165
+ xmin, ymin, xmax, ymax = gdf_out.total_bounds
166
+ coverage_path = COVERAGES[coverage]
167
+
168
+ index_gdf = gpd.read_file(coverage_path.joinpath("index.shp"))
169
+
170
+ for idx, row in index_gdf.cx[xmin:xmax, ymin:ymax].iterrows():
171
+ try:
172
+ bathymetrie_raster = coverage_path.joinpath(
173
+ f'{row["bladnr"].upper()}_CM.tif'
174
+ )
175
+
176
+ gdf_select = gdf_out.loc[
177
+ gdf_out["geometry"].centroid.within(row["geometry"])
178
+ ]
179
+ if not gdf_select.empty:
180
+ if isinstance(radius, str):
181
+ gdf_select.loc[:, ("geometry")] = gdf_select.apply(
182
+ _buffer_row, args=(radius,), axis=1
183
+ )
184
+ else:
185
+ radius = max(radius, 0.5)
186
+ gdf_select.loc[:, ("geometry")] = gdf_select["geometry"].buffer(
187
+ radius
188
+ )
189
+
190
+ with rasterio.open(bathymetrie_raster, "r") as src:
191
+ profile = src.profile
192
+ raster_data = src.read(1)
193
+ affine = src.transform
194
+ scale = src.scales[0]
195
+
196
+ raster_stats = zonal_stats(
197
+ gdf_select,
198
+ raster_data,
199
+ affine=affine,
200
+ stats=f"percentile_{percentile}",
201
+ nodata=profile["nodata"],
202
+ raster_out=True,
203
+ )
204
+
205
+ gdf_out.loc[gdf_select.index.to_list(), "result"] = [
206
+ np.nan if item is None else round(item * scale, 2)
207
+ for item in [
208
+ item[f"percentile_{percentile}"] for item in raster_stats
209
+ ]
210
+ ]
211
+ except Exception as e:
212
+ print(
213
+ (
214
+ f"bathymetrie: {bathymetrie_raster}\n"
215
+ f"indices: {gdf_select.index}\n"
216
+ f"geometrien: {gdf_select['geometry']}"
217
+ )
218
+ )
219
+ raise e
220
+
221
+ # fill series if if provided
222
+ if fill_value is not None:
223
+ gdf_out.loc[gdf_out["result"].isna(), "result"] = fill_value
224
+
225
+ return gdf_out["result"]
226
+
227
+
228
+ def join_parameter(
229
+ gdf,
230
+ join_object: str,
231
+ join_gdf: gpd.GeoDataFrame,
232
+ join_parameter: str,
233
+ fill_value=None,
234
+ ):
235
+ """Joins a parameteer of other object to geodataframe."""
236
+ _gdf = gdf.copy()
237
+
238
+ _join_gdf = join_gdf.copy()
239
+ _join_gdf.set_index("globalid", inplace=True)
240
+ series = _join_gdf[join_parameter]
241
+ series.name = "result"
242
+ _gdf = _gdf.merge(series, how="left", left_on=f"{join_object}id", right_index=True)
243
+
244
+ # fill series if if provided
245
+ if fill_value is not None:
246
+ _gdf.loc[_gdf["result"].isna(), "result"] = fill_value
247
+
248
+ return _gdf["result"]
249
+
250
+
251
+ def object_relation(
252
+ gdf,
253
+ related_gdf: gpd.GeoDataFrame,
254
+ code_relation: str,
255
+ statistic: Literal["min", "max", "sum", "count"],
256
+ related_parameter: str = None,
257
+ fill_value=None,
258
+ ):
259
+ """
260
+ Statistic of related object to geodataframe
261
+
262
+ Parameters
263
+ ----------
264
+ gdf : GeoDataFrame
265
+ Input GeoDataFrame
266
+ related_gdf : GeoDataFrame
267
+ GeoDataFrame with related attributes
268
+ code_relation : str
269
+ Column in related_gdf used to relate to gdf. Example 'stuwid'
270
+ statistic : str, options: 'min', 'max', 'sum', 'count'
271
+ Statistic to compute over related values
272
+ related_parameter: str
273
+ Column in related_gdf over which the statistic is to be computed
274
+ fill_value : float, optional
275
+ The fill_value to use when the area is not intersecting the coverage.
276
+ The default is None
277
+
278
+ Returns
279
+ -------
280
+ result : Series
281
+ Float series
282
+
283
+ """
284
+
285
+ gdf_out = gdf.copy()
286
+
287
+ # remove NaN values in from related_gdf[related_parameter]
288
+ if related_parameter:
289
+ if "geometry" in related_parameter:
290
+ related_gdf[related_parameter] = _get_geometric_attribute(
291
+ related_gdf, related_parameter
292
+ )
293
+ related_gdf = related_gdf.loc[related_gdf[related_parameter].notna()]
294
+
295
+ # compute statistic
296
+ if statistic == "count":
297
+ series = related_gdf.groupby(by=[code_relation])[code_relation].count()
298
+ elif statistic == "sum":
299
+ series = related_gdf.groupby(by=[code_relation])[related_parameter].sum()
300
+ elif statistic == "min":
301
+ series = related_gdf.groupby(by=[code_relation])[related_parameter].min()
302
+ elif statistic == "max":
303
+ series = related_gdf.groupby(by=[code_relation])[related_parameter].max()
304
+ elif statistic == "majority":
305
+ series = related_gdf.groupby(by=[code_relation])[related_parameter].agg(
306
+ pd.Series.mode
307
+ )
308
+
309
+ # join series with gdf
310
+ series.name = "result"
311
+ series = pd.DataFrame(series.loc[series.index.isin(gdf["globalid"])]).reset_index()
312
+ gdf_out = gdf_out.merge(
313
+ series, how="left", left_on="globalid", right_on=code_relation
314
+ )
315
+
316
+ # fill series if if provided
317
+ if fill_value is not None:
318
+ gdf_out.loc[gdf_out["result"].isna(), "result"] = fill_value
319
+ return gdf_out["result"]
@@ -0,0 +1,338 @@
1
+ """Logic functions to be used in eval-method."""
2
+
3
+ import pandas as pd
4
+
5
+
6
+ def _overlapping_period(row, df, start_date, end_date):
7
+ _df = df[df.index != row.name]
8
+ return ~(
9
+ (row[start_date] <= _df[end_date]) & (row[end_date] >= _df[end_date])
10
+ ).any()
11
+
12
+
13
+ def _check_attributes(gdf, attributes):
14
+ for i in attributes:
15
+ if type(i) == str:
16
+ if not i in gdf.columns:
17
+ raise KeyError(
18
+ rf"'{i}' not in columns: {gdf.columns.to_list()}. Rule cannot be executed"
19
+ )
20
+
21
+
22
+ def LE(gdf, left, right, dtype=bool):
23
+ """
24
+ Evaluate if left is less or equal to/than right
25
+
26
+ Parameters
27
+ ----------
28
+ gdf : GeoDataFrame
29
+ Input GeoDataFrame
30
+ left : str, numeric
31
+ Left column or value in expression
32
+ right : TYPE
33
+ Right column or value in expression
34
+ dtype : dtype, optional
35
+ dtype assigned to result Series
36
+ The default is bool.
37
+
38
+ Returns
39
+ -------
40
+ result : Series
41
+ Pandas Series (default dtype = bool)
42
+
43
+ """
44
+ _check_attributes(gdf, [left, right])
45
+ expression = f"{left} <= {right}".lower()
46
+ return gdf.eval(expression).astype(dtype)
47
+
48
+
49
+ def LT(gdf, left, right, dtype=bool):
50
+ """
51
+ Evaluate if left is less than right
52
+
53
+ Parameters
54
+ ----------
55
+ gdf : GeoDataFrame
56
+ Input GeoDataFrame
57
+ left : str, numeric
58
+ Left column or value in expression
59
+ right : TYPE
60
+ Right column or value in expression
61
+ dtype : dtype, optional
62
+ dtype assigned to result Series
63
+ The default is bool.
64
+
65
+ Returns
66
+ -------
67
+ result : Series
68
+ Pandas Series (default dtype = bool)
69
+
70
+ """
71
+ _check_attributes(gdf, [left, right])
72
+ expression = f"{left} < {right}".lower()
73
+ return gdf.eval(expression).astype(dtype)
74
+
75
+
76
+ def GT(gdf, left, right, dtype=bool):
77
+ """
78
+ Evaluate if left is greater than right
79
+
80
+ Parameters
81
+ ----------
82
+ gdf : GeoDataFrame
83
+ Input GeoDataFrame
84
+ left : str, numeric
85
+ Left column or value in expression
86
+ right : TYPE
87
+ Right column or value in expression
88
+ dtype : dtype, optional
89
+ dtype assigned to result Series
90
+ The default is bool.
91
+
92
+ Returns
93
+ -------
94
+ result : Series
95
+ Pandas Series (default dtype = bool)
96
+
97
+ """
98
+ _check_attributes(gdf, [left, right])
99
+ expression = f"{left} > {right}".lower()
100
+ return gdf.eval(expression).astype(dtype)
101
+
102
+
103
+ def GE(gdf, left, right, dtype=bool):
104
+ """Evaluate if left is greater or equal to/than right
105
+
106
+ Parameters
107
+ ----------
108
+ gdf : GeoDataFrame
109
+ Input GeoDataFrame
110
+ left : str, numeric
111
+ Left column or value in expression
112
+ right : TYPE
113
+ Right column or value in expression
114
+ dtype : dtype, optional
115
+ dtype assigned to result Series
116
+ The default is bool.
117
+
118
+ Returns
119
+ -------
120
+ result : Series
121
+ Pandas Series (default dtype = bool)
122
+
123
+ """
124
+ _check_attributes(gdf, [left, right])
125
+ expression = f"{left} >= {right}".lower()
126
+ return gdf.eval(expression).astype(dtype)
127
+
128
+
129
+ def EQ(gdf, left, right, dtype=bool):
130
+ """Evalate if left an right expression are equal
131
+
132
+ Parameters
133
+ ----------
134
+ gdf : GeoDataFrame
135
+ Input GeoDataFrame
136
+ left : str, numeric
137
+ Left column or value in expression
138
+ right : TYPE
139
+ Right column or value in expression
140
+ dtype : dtype, optional
141
+ dtype assigned to result Series
142
+ The default is bool.
143
+
144
+ Returns
145
+ -------
146
+ result : Series
147
+ Pandas Series (default dtype = bool)
148
+
149
+ """
150
+ _check_attributes(gdf, [left, right])
151
+ expression = f"{left} == {right}".lower()
152
+ return gdf.eval(expression).astype(dtype)
153
+
154
+
155
+ def BE(gdf, parameter, min, max, inclusive=False):
156
+ """Evaluate if parameter-value is between min/max inclusive (true/false)
157
+
158
+ Parameters
159
+ ----------
160
+ gdf : GeoDataFrame
161
+ Input GeoDataFrame
162
+ parameter: str
163
+ Input column with numeric values
164
+ min : numeric
165
+ Lower limit of function
166
+ max : numeric
167
+ Upper limit of function
168
+ inclusive : bool, optional
169
+ To include min and max
170
+ The default is False.
171
+
172
+ Returns
173
+ -------
174
+ result : Series
175
+ Pandas Series (default dtype = bool)
176
+
177
+ """
178
+ _check_attributes(gdf, [parameter, min, max])
179
+ if inclusive:
180
+ series = GE(gdf, parameter, min, dtype=bool) & LE(
181
+ gdf, parameter, max, dtype=bool
182
+ )
183
+ else:
184
+ series = GT(gdf, parameter, min, dtype=bool) & LT(
185
+ gdf, parameter, max, dtype=bool
186
+ )
187
+ return series
188
+
189
+
190
+ def ISIN(gdf, parameter, array):
191
+ """Evaluate if values in parameter are in array
192
+
193
+ Parameters
194
+ ----------
195
+ gdf : GeoDataFrame
196
+ Input GeoDataFrame
197
+ parameter: str
198
+ Input column with numeric values
199
+ array : list
200
+ list of possible values that return True
201
+
202
+ Returns
203
+ -------
204
+ result : Series
205
+ Pandas Series (default dtype = bool)
206
+
207
+ """
208
+ _check_attributes(gdf, [parameter])
209
+ return gdf[parameter].isin(array)
210
+
211
+
212
+ def NOTIN(gdf, parameter, array):
213
+ """Evaluate if values in parameter are not in array
214
+
215
+ Parameters
216
+ ----------
217
+ gdf : GeoDataFrame
218
+ Input GeoDataFrame
219
+ parameter: str
220
+ Input column with numeric values
221
+ array : list
222
+ list of possible values that return False
223
+
224
+ Returns
225
+ -------
226
+ result : Series
227
+ Pandas Series (default dtype = bool)
228
+
229
+ """
230
+ _check_attributes(gdf, [parameter])
231
+ return ~ISIN(gdf, parameter, array)
232
+
233
+
234
+ def NOTNA(gdf, parameter):
235
+ """Evaluate if values in parameter ar not NaN or None
236
+
237
+ Parameters
238
+ ----------
239
+ gdf : GeoDataFrame
240
+ Input GeoDataFrame
241
+ parameter: str
242
+ Input column with numeric values
243
+
244
+ Returns
245
+ -------
246
+ result : Series
247
+ Pandas Series (default dtype = bool)
248
+
249
+ """
250
+ _check_attributes(gdf, [parameter])
251
+ return gdf[parameter].notna()
252
+
253
+
254
+ def join_object_exists(gdf, join_gdf, join_object):
255
+ """Evaluate if defined related_object id exists in globalid parameter of
256
+ related object-table.
257
+
258
+ Parameters
259
+ ----------
260
+ gdf : GeoDataFrame
261
+ Input GeoDataFrame
262
+ related_gdf : GeoDataFrame
263
+ Input GeoDataFrame with related objects
264
+ object: str
265
+ HyDAMO object name of related object-layer
266
+
267
+ Returns
268
+ -------
269
+ result : Series
270
+ Pandas Series (default dtype = bool)
271
+
272
+ """
273
+ _check_attributes(join_gdf, ["globalid"])
274
+ _check_attributes(gdf, [f"{join_object}id"])
275
+ return gdf[f"{join_object}id"].isin(join_gdf["globalid"])
276
+
277
+
278
+ def consistent_period(
279
+ gdf,
280
+ max_gap=1,
281
+ groupers=["pompid", "regelmiddelid"],
282
+ priority="prioriteit",
283
+ start_date="beginperiode",
284
+ date_format="%d%m",
285
+ end_date="eindperiode",
286
+ ):
287
+ """Check if a periodic-based table is time-consistent
288
+
289
+ Parameters
290
+ ----------
291
+ gdf : GeoDataFrame
292
+ Input GeoDataFrame
293
+ max_gap: int
294
+ max gap in days between too adjacent periods
295
+
296
+ Returns
297
+ -------
298
+ result : Series
299
+ Pandas Series (default dtype = bool)
300
+
301
+ """
302
+
303
+ # create an empty result
304
+ _gdf = gdf.copy()
305
+ result = pd.Series(index=_gdf.index)
306
+
307
+ # convert start_parameter and end_parameter to datetime
308
+ _gdf[start_date] = pd.to_datetime(_gdf[start_date], format=date_format)
309
+ _gdf[end_date] = pd.to_datetime(_gdf[end_date], format=date_format)
310
+
311
+ index_select = _gdf[start_date] > _gdf[end_date]
312
+ _gdf.loc[index_select, end_date] = _gdf[index_select][
313
+ end_date
314
+ ] + pd.offsets.DateOffset(years=1)
315
+
316
+ for group in groupers:
317
+ grouper = _gdf.groupby(by=[group, "prioriteit"])
318
+
319
+ for _, df in _gdf.groupby(by=["pompid", "prioriteit"]):
320
+ df.sort_values(by=start_date, inplace=True)
321
+
322
+ # check for overlap
323
+ bool_series = df.apply(
324
+ (lambda x: _overlapping_period(x, df, start_date, end_date)), axis=1
325
+ )
326
+
327
+ # check for gaps
328
+ gaps_series = df[start_date] - df.shift(1)[end_date]
329
+ gaps_series.iloc[0] = pd.Timedelta(days=0) # due to shift we have NaT here
330
+
331
+ # add to result
332
+ bool_series = (gaps_series <= pd.Timedelta(days=int(max_gap))) & bool_series
333
+ bool_series = bool_series[
334
+ bool_series.index.isin(result[result.isna() | (result == True)].index)
335
+ ]
336
+ result.loc[result.index.isin(bool_series.index)] = bool_series
337
+
338
+ return result