hydamo-validation 1.3.0b1__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.
- hydamo_validation-1.3.0b1/LICENSE +21 -0
- hydamo_validation-1.3.0b1/PKG-INFO +67 -0
- hydamo_validation-1.3.0b1/README.md +48 -0
- hydamo_validation-1.3.0b1/hydamo_validation/__init__.py +13 -0
- hydamo_validation-1.3.0b1/hydamo_validation/datamodel.py +500 -0
- hydamo_validation-1.3.0b1/hydamo_validation/datasets.py +109 -0
- hydamo_validation-1.3.0b1/hydamo_validation/geometry.py +144 -0
- hydamo_validation-1.3.0b1/hydamo_validation/logical_validation.py +376 -0
- hydamo_validation-1.3.0b1/hydamo_validation/styles.py +124 -0
- hydamo_validation-1.3.0b1/hydamo_validation/summaries.py +268 -0
- hydamo_validation-1.3.0b1/hydamo_validation/syntax_validation.py +269 -0
- hydamo_validation-1.3.0b1/hydamo_validation/utils.py +75 -0
- hydamo_validation-1.3.0b1/hydamo_validation/validator.py +355 -0
- hydamo_validation-1.3.0b1/hydamo_validation.egg-info/PKG-INFO +67 -0
- hydamo_validation-1.3.0b1/hydamo_validation.egg-info/SOURCES.txt +34 -0
- hydamo_validation-1.3.0b1/hydamo_validation.egg-info/dependency_links.txt +1 -0
- hydamo_validation-1.3.0b1/hydamo_validation.egg-info/requires.txt +9 -0
- hydamo_validation-1.3.0b1/hydamo_validation.egg-info/top_level.txt +1 -0
- hydamo_validation-1.3.0b1/hydamo_validation.egg-info/zip-safe +1 -0
- hydamo_validation-1.3.0b1/pyproject.toml +43 -0
- hydamo_validation-1.3.0b1/setup.cfg +4 -0
- hydamo_validation-1.3.0b1/setup.py +3 -0
- hydamo_validation-1.3.0b1/tests/test_datasets.py +46 -0
- hydamo_validation-1.3.0b1/tests/test_dommelerwaard.py +31 -0
- hydamo_validation-1.3.0b1/tests/test_general_functions.py +154 -0
- hydamo_validation-1.3.0b1/tests/test_hydamo_2_2.py +135 -0
- hydamo_validation-1.3.0b1/tests/test_init.py +45 -0
- hydamo_validation-1.3.0b1/tests/test_logic_functions.py +84 -0
- hydamo_validation-1.3.0b1/tests/test_not_overlapping.py +113 -0
- hydamo_validation-1.3.0b1/tests/test_productie.py +103 -0
- hydamo_validation-1.3.0b1/tests/test_structures_at_interersections.py +35 -0
- hydamo_validation-1.3.0b1/tests/test_summaries.py +110 -0
- hydamo_validation-1.3.0b1/tests/test_topologic_functions.py +157 -0
- hydamo_validation-1.3.0b1/tests/test_validationrules.py +60 -0
- hydamo_validation-1.3.0b1/tests/test_wrij.py +183 -0
- hydamo_validation-1.3.0b1/tests/test_wrij_profielen.py +25 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 HyDAMO ValidatieTool
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: hydamo_validation
|
|
3
|
+
Version: 1.3.0b1
|
|
4
|
+
Summary: Validation module for HyDAMO data
|
|
5
|
+
Author-email: Daniel Tollenaar <daniel@d2hydro.nl>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Source, https://github.com/HetWaterschapshuis/HyDAMOValidatieModule
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: geopandas
|
|
12
|
+
Requires-Dist: pandas>=2
|
|
13
|
+
Requires-Dist: pyogrio
|
|
14
|
+
Requires-Dist: rasterio
|
|
15
|
+
Requires-Dist: shapely>=2
|
|
16
|
+
Requires-Dist: rasterstats
|
|
17
|
+
Provides-Extra: tests
|
|
18
|
+
Requires-Dist: pytest; extra == "tests"
|
|
19
|
+
|
|
20
|
+
# The HyDAMO Validation Module: hydamo_validation
|
|
21
|
+
|
|
22
|
+
Validation Module for HyDAMO data.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Python installation
|
|
27
|
+
Make sure you have an Miniconda or Anaconda installation. You can download these here:
|
|
28
|
+
- https://www.anaconda.com/products/individual
|
|
29
|
+
- https://docs.conda.io/en/latest/miniconda.html
|
|
30
|
+
|
|
31
|
+
During installation, tick the box "Add Anaconda to PATH", ignore the red remarks
|
|
32
|
+
|
|
33
|
+
### Create the `validatietool` environment
|
|
34
|
+
Use the `env/environment.yml` in the repository to create the conda environment: `validatietool`
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
conda env create -f environment.yml
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
After installation you can activate your environment in command prompt
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
conda activate validatietool
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Install hydamo_validation
|
|
47
|
+
Simply install the module in the activated environment:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
pip install hydamo_validation
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Develop-install hydamo_validation
|
|
54
|
+
Download or clone the repository. Now simply install the module in the activated environment:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
pip install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Run an example
|
|
61
|
+
A working example with data can be found in `notebooks/test_wrij.ipynb`. In the activated environment launch jupyter notebook by:
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
jupyter notebook
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Select `test_wrij.ipynb` read and run it.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# The HyDAMO Validation Module: hydamo_validation
|
|
2
|
+
|
|
3
|
+
Validation Module for HyDAMO data.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Python installation
|
|
8
|
+
Make sure you have an Miniconda or Anaconda installation. You can download these here:
|
|
9
|
+
- https://www.anaconda.com/products/individual
|
|
10
|
+
- https://docs.conda.io/en/latest/miniconda.html
|
|
11
|
+
|
|
12
|
+
During installation, tick the box "Add Anaconda to PATH", ignore the red remarks
|
|
13
|
+
|
|
14
|
+
### Create the `validatietool` environment
|
|
15
|
+
Use the `env/environment.yml` in the repository to create the conda environment: `validatietool`
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
conda env create -f environment.yml
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After installation you can activate your environment in command prompt
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
conda activate validatietool
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Install hydamo_validation
|
|
28
|
+
Simply install the module in the activated environment:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
pip install hydamo_validation
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Develop-install hydamo_validation
|
|
35
|
+
Download or clone the repository. Now simply install the module in the activated environment:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
pip install .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Run an example
|
|
42
|
+
A working example with data can be found in `notebooks/test_wrij.ipynb`. In the activated environment launch jupyter notebook by:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
jupyter notebook
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Select `test_wrij.ipynb` read and run it.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
__author__ = ["Het Waterschapshuis", "D2HYDRO", "HKV", "HydroConsult"]
|
|
2
|
+
__copyright__ = "Copyright 2021, HyDAMO ValidatieTool"
|
|
3
|
+
__credits__ = ["D2HYDRO", "HKV", "HydroConsult"]
|
|
4
|
+
__version__ = "1.3.0b1"
|
|
5
|
+
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
__maintainer__ = "Daniel Tollenaar"
|
|
8
|
+
__email__ = "daniel@d2hydro.nl"
|
|
9
|
+
|
|
10
|
+
from .functions import topologic as topologic_functions
|
|
11
|
+
from .functions import logic as logic_functions
|
|
12
|
+
from .functions import general as general_functions
|
|
13
|
+
from .validator import validator
|
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"""HyDAMO datamodel for ValidatieTool."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
import warnings
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
import fiona
|
|
11
|
+
import geopandas as gpd
|
|
12
|
+
from geopandas import GeoSeries
|
|
13
|
+
import numpy as np
|
|
14
|
+
from shapely.geometry import LineString, MultiLineString, MultiPolygon, Point, Polygon
|
|
15
|
+
|
|
16
|
+
from hydamo_validation import geometry
|
|
17
|
+
from hydamo_validation.styles import add_styles_to_geopackage
|
|
18
|
+
|
|
19
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
20
|
+
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
|
21
|
+
warnings.filterwarnings("ignore", category=FutureWarning)
|
|
22
|
+
|
|
23
|
+
FIELD_TYPES_MAP_REV = fiona.schema.FIELD_TYPES_MAP_REV
|
|
24
|
+
FIELD_TYPES_MAP = fiona.schema.FIELD_TYPES_MAP
|
|
25
|
+
MODEL_CRS = "epsg:28992"
|
|
26
|
+
SCHEMAS_DIR = Path(__file__).parent.joinpath("schemas", "hydamo")
|
|
27
|
+
|
|
28
|
+
GEOTYPE_MAPPING = {
|
|
29
|
+
"LineString": LineString,
|
|
30
|
+
"MultiLineString": MultiLineString,
|
|
31
|
+
"Point": Point,
|
|
32
|
+
"PointZ": Point,
|
|
33
|
+
"Polygon": Polygon,
|
|
34
|
+
"MultiPolygon": MultiPolygon,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
DTYPE_MAPPING = {
|
|
38
|
+
"string": "str",
|
|
39
|
+
"integer": "int64",
|
|
40
|
+
"date-time": "datetime",
|
|
41
|
+
"number": "float",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
default_properties: dict[str, Any] = {
|
|
45
|
+
"id": None,
|
|
46
|
+
"dtype": "str",
|
|
47
|
+
"required": False,
|
|
48
|
+
"unique": False,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def map_definition(definition: dict[str, Any]) -> list[dict[str, Any]]:
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
definition : dict[str, Any]
|
|
59
|
+
HyDAMO definition as specified in the HyDAMO JSON specification.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
list[dict[str, Any]]
|
|
64
|
+
Validation schema for the HyDAMO class.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# start with an empty list to populate
|
|
69
|
+
result = []
|
|
70
|
+
|
|
71
|
+
for k, v in definition.items():
|
|
72
|
+
# convert geometry if shape
|
|
73
|
+
if k == "shape":
|
|
74
|
+
properties: dict[str, Any] = {"id": "geometry"}
|
|
75
|
+
dtype = v["type"]
|
|
76
|
+
if not isinstance(dtype, list):
|
|
77
|
+
dtype = [dtype]
|
|
78
|
+
properties["dtype"] = dtype
|
|
79
|
+
properties["required"] = True
|
|
80
|
+
result.append(properties)
|
|
81
|
+
|
|
82
|
+
# set properties if not shape
|
|
83
|
+
else:
|
|
84
|
+
properties = default_properties.copy()
|
|
85
|
+
properties["id"] = k
|
|
86
|
+
if "type" in v.keys():
|
|
87
|
+
properties["dtype"] = DTYPE_MAPPING[v["type"]]
|
|
88
|
+
if "format" in v.keys():
|
|
89
|
+
properties["dtype"] = DTYPE_MAPPING[v["format"]]
|
|
90
|
+
|
|
91
|
+
# set required
|
|
92
|
+
if "minItems" in v.keys():
|
|
93
|
+
if v["minItems"] == 1:
|
|
94
|
+
properties["required"] = True
|
|
95
|
+
|
|
96
|
+
# set unique
|
|
97
|
+
if "uniqueItems" in v.keys():
|
|
98
|
+
properties["unique"] = v["uniqueItems"]
|
|
99
|
+
|
|
100
|
+
# set domain
|
|
101
|
+
if "enum" in v.keys():
|
|
102
|
+
properties["domain"] = [{"value": i} for i in v["enum"]]
|
|
103
|
+
result.append(properties)
|
|
104
|
+
return result
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ExtendedGeoDataFrame(gpd.GeoDataFrame): # type: ignore
|
|
108
|
+
"""A GeoPandas GeoDataFrame with extended properties and methods."""
|
|
109
|
+
|
|
110
|
+
# ignores subclassing Any: https://github.com/geopandas/geopandas/discussions/2750
|
|
111
|
+
|
|
112
|
+
_metadata = [
|
|
113
|
+
"required_columns",
|
|
114
|
+
"geotype",
|
|
115
|
+
"layer_name",
|
|
116
|
+
] + gpd.GeoDataFrame._metadata
|
|
117
|
+
|
|
118
|
+
def __init__(
|
|
119
|
+
self,
|
|
120
|
+
validation_schema: list[dict[str, Any]],
|
|
121
|
+
geotype: (
|
|
122
|
+
list[
|
|
123
|
+
Literal[
|
|
124
|
+
"LineString",
|
|
125
|
+
"MultiLineString",
|
|
126
|
+
"Point",
|
|
127
|
+
"PointZ",
|
|
128
|
+
"Polygon",
|
|
129
|
+
"MultiPolygon",
|
|
130
|
+
]
|
|
131
|
+
]
|
|
132
|
+
| None
|
|
133
|
+
),
|
|
134
|
+
layer_name: str = "",
|
|
135
|
+
required_columns: list[str] = [],
|
|
136
|
+
logger=logging,
|
|
137
|
+
*args,
|
|
138
|
+
**kwargs,
|
|
139
|
+
):
|
|
140
|
+
# Check type
|
|
141
|
+
required_columns = [i.lower() for i in required_columns]
|
|
142
|
+
|
|
143
|
+
# Add required columns to column list
|
|
144
|
+
kwargs["columns"] = required_columns
|
|
145
|
+
kwargs["geometry"] = GeoSeries()
|
|
146
|
+
|
|
147
|
+
super().__init__(*args, **kwargs)
|
|
148
|
+
|
|
149
|
+
self.validation_schema = validation_schema
|
|
150
|
+
self.required_columns = required_columns
|
|
151
|
+
self.layer_name = layer_name
|
|
152
|
+
self.geotype = geotype
|
|
153
|
+
|
|
154
|
+
if "geometry" not in self.required_columns:
|
|
155
|
+
self.required_columns += ["geometry"]
|
|
156
|
+
self.crs = MODEL_CRS
|
|
157
|
+
|
|
158
|
+
def _check_columns(self, gdf):
|
|
159
|
+
"""Check presence of columns in GeoDataFrame"""
|
|
160
|
+
present_columns = gdf.columns.tolist()
|
|
161
|
+
for column in self.required_columns:
|
|
162
|
+
if column not in present_columns:
|
|
163
|
+
raise KeyError(
|
|
164
|
+
'Column "{}" not found. Got {}, Expected at least {}'.format(
|
|
165
|
+
column,
|
|
166
|
+
", ".join(present_columns),
|
|
167
|
+
", ".join(self.required_columns),
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _check_geotype(self):
|
|
172
|
+
"""Check geometry type"""
|
|
173
|
+
if self.geotype:
|
|
174
|
+
if not all(
|
|
175
|
+
any(isinstance(geo, GEOTYPE_MAPPING[i]) for i in self.geotype)
|
|
176
|
+
for geo in self.geometry
|
|
177
|
+
):
|
|
178
|
+
raise TypeError(
|
|
179
|
+
'Geometry-type "{}" required in layer "{}". The input feature-file has geometry type(s) {}.'.format(
|
|
180
|
+
re.findall("([A-Z].*)'", repr(self.geotype))[0],
|
|
181
|
+
self.layer_name,
|
|
182
|
+
self.geometry.type.unique().tolist(),
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def _get_schema(self):
|
|
187
|
+
"""Return fiona schema dict from validation_schema."""
|
|
188
|
+
properties = {
|
|
189
|
+
i["id"]: i["dtype"] for i in self.validation_schema if i["id"] != "geometry"
|
|
190
|
+
}
|
|
191
|
+
# properties = {k: (v if v != "datetime" else "str") for k, v in properties}
|
|
192
|
+
geometry = next(
|
|
193
|
+
(i["dtype"] for i in self.validation_schema if i["id"] == "geometry"),
|
|
194
|
+
None,
|
|
195
|
+
)
|
|
196
|
+
return {"properties": properties, "geometry": geometry}
|
|
197
|
+
|
|
198
|
+
def set_data(
|
|
199
|
+
self,
|
|
200
|
+
gdf,
|
|
201
|
+
layer="",
|
|
202
|
+
index_col=None,
|
|
203
|
+
check_columns=True,
|
|
204
|
+
check_geotype=True,
|
|
205
|
+
extra_attributes={},
|
|
206
|
+
):
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
Parameters
|
|
211
|
+
----------
|
|
212
|
+
gdf : GeoDataFrame
|
|
213
|
+
GeoDataFrame with a HyDAMO object-layer
|
|
214
|
+
index_col : str, optional
|
|
215
|
+
Column to be used as index. The default is None.
|
|
216
|
+
check_columns : bool, optional
|
|
217
|
+
Check if all required columns are present in the GeoDataFrame.
|
|
218
|
+
The default is True.
|
|
219
|
+
check_geotype : bool, optional
|
|
220
|
+
Check if the geometry is of the required type. The default is True.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
None.
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
if not self.empty:
|
|
229
|
+
self.delete_all()
|
|
230
|
+
|
|
231
|
+
# reproject to crs if necessary
|
|
232
|
+
if (gdf.crs is not None) and ("geometry" in self.required_columns):
|
|
233
|
+
if f"epsg:{gdf.crs.to_epsg()}" == MODEL_CRS:
|
|
234
|
+
gdf.set_crs(MODEL_CRS, inplace=True, allow_override=True)
|
|
235
|
+
else:
|
|
236
|
+
gdf.to_crs(MODEL_CRS, inplace=True)
|
|
237
|
+
|
|
238
|
+
# Check columns
|
|
239
|
+
gdf.columns = [i.lower() for i in gdf.columns]
|
|
240
|
+
if check_columns:
|
|
241
|
+
self._check_columns(gdf)
|
|
242
|
+
|
|
243
|
+
# Copy content
|
|
244
|
+
for col, values in gdf.items():
|
|
245
|
+
self[col] = values.to_numpy()
|
|
246
|
+
|
|
247
|
+
if index_col is None:
|
|
248
|
+
self.index = gdf.index
|
|
249
|
+
self.index.name = gdf.index.name
|
|
250
|
+
|
|
251
|
+
else:
|
|
252
|
+
self.index = gdf[index_col]
|
|
253
|
+
self.index.name = index_col
|
|
254
|
+
|
|
255
|
+
# Check geometry types
|
|
256
|
+
if check_geotype:
|
|
257
|
+
self._check_geotype()
|
|
258
|
+
|
|
259
|
+
# Set extra attribute-values
|
|
260
|
+
for k, v in extra_attributes.items():
|
|
261
|
+
if k not in self.columns:
|
|
262
|
+
self[k] = v
|
|
263
|
+
|
|
264
|
+
def delete_all(self):
|
|
265
|
+
"""Empty the dataframe"""
|
|
266
|
+
if not self.empty:
|
|
267
|
+
self.iloc[:, 0] = np.nan
|
|
268
|
+
self.dropna(inplace=True)
|
|
269
|
+
|
|
270
|
+
def snap_to_branch(self, branches, snap_method, maxdist=5):
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
branches : GeoDataFrame
|
|
277
|
+
GeoDataFrame with branches
|
|
278
|
+
snap_method : str
|
|
279
|
+
Options for snapping
|
|
280
|
+
maxdist : float, optional
|
|
281
|
+
The maximal distance for snapping. The default is 5.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
None.
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
"""Snap the geometries to the branch."""
|
|
290
|
+
geometry.find_nearest_branch(
|
|
291
|
+
branches=branches, geometries=self, method=snap_method, maxdist=maxdist
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class HyDAMO:
|
|
296
|
+
"""Definition of the HyDAMO datamodel."""
|
|
297
|
+
|
|
298
|
+
def __init__(
|
|
299
|
+
self,
|
|
300
|
+
version: str = "2.2",
|
|
301
|
+
schemas_path: Path = SCHEMAS_DIR,
|
|
302
|
+
ignored_layers: list[str] = [
|
|
303
|
+
"afvoeraanvoergebied",
|
|
304
|
+
"imwa_geoobject",
|
|
305
|
+
"leggerwatersysteem",
|
|
306
|
+
"leggerwaterveiligheid",
|
|
307
|
+
"waterbeheergebied",
|
|
308
|
+
],
|
|
309
|
+
):
|
|
310
|
+
self.version = version
|
|
311
|
+
self.schema_json = schemas_path.joinpath(f"HyDAMO_{version}.json")
|
|
312
|
+
self.layers: list[str] = []
|
|
313
|
+
self.ignored_layers = ignored_layers
|
|
314
|
+
|
|
315
|
+
self.init_datamodel()
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def data_layers(self):
|
|
319
|
+
return [layer for layer in self.layers if not getattr(self, layer).empty]
|
|
320
|
+
|
|
321
|
+
def init_datamodel(self) -> None:
|
|
322
|
+
"""Initialize DataModel from self.schemas_path."""
|
|
323
|
+
self.validation_schemas: dict[str, Any] = {}
|
|
324
|
+
|
|
325
|
+
# read schema as dict
|
|
326
|
+
with open(self.schema_json) as src:
|
|
327
|
+
schema = json.load(src)
|
|
328
|
+
hydamo_layers = [
|
|
329
|
+
Path(i["$ref"]).name for i in schema["properties"]["HyDAMO"]["anyOf"]
|
|
330
|
+
]
|
|
331
|
+
self.layers = [i for i in hydamo_layers if i not in self.ignored_layers]
|
|
332
|
+
|
|
333
|
+
for hydamo_layer in self.layers:
|
|
334
|
+
definition = schema["definitions"][hydamo_layer]["properties"]
|
|
335
|
+
layer_schema = map_definition(definition)
|
|
336
|
+
self.validation_schemas[hydamo_layer] = layer_schema
|
|
337
|
+
|
|
338
|
+
# add layer to data_model
|
|
339
|
+
geotype = next(
|
|
340
|
+
(i["dtype"] for i in layer_schema if i["id"] == "geometry"), None
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
required_columns = [
|
|
344
|
+
i["id"]
|
|
345
|
+
for i in [i for i in layer_schema if "required" in i.keys()]
|
|
346
|
+
if i["required"]
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
setattr(
|
|
350
|
+
self,
|
|
351
|
+
hydamo_layer,
|
|
352
|
+
ExtendedGeoDataFrame(
|
|
353
|
+
validation_schema=layer_schema,
|
|
354
|
+
layer_name=hydamo_layer,
|
|
355
|
+
geotype=geotype,
|
|
356
|
+
required_columns=required_columns,
|
|
357
|
+
),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def get(self, layer: str, global_id: str):
|
|
361
|
+
"""
|
|
362
|
+
Get a DataFrame row (feature) providing a layer an global_id.
|
|
363
|
+
|
|
364
|
+
Parameters
|
|
365
|
+
----------
|
|
366
|
+
layer : str
|
|
367
|
+
DESCRIPTION.
|
|
368
|
+
global_id : str
|
|
369
|
+
DESCRIPTION.
|
|
370
|
+
|
|
371
|
+
Returns
|
|
372
|
+
-------
|
|
373
|
+
TYPE
|
|
374
|
+
DESCRIPTION.
|
|
375
|
+
|
|
376
|
+
"""
|
|
377
|
+
return getattr(self, layer).set_index("globalid").loc[global_id]
|
|
378
|
+
|
|
379
|
+
def set_data(
|
|
380
|
+
self,
|
|
381
|
+
gdf,
|
|
382
|
+
layer,
|
|
383
|
+
index_col=None,
|
|
384
|
+
check_columns=True,
|
|
385
|
+
check_geotype=True,
|
|
386
|
+
extra_attributes={},
|
|
387
|
+
):
|
|
388
|
+
"""
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
Parameters
|
|
392
|
+
----------
|
|
393
|
+
gdf : GeoDataFrame
|
|
394
|
+
GeoDataFrame with a HyDAMO object-layer
|
|
395
|
+
layer : TYPE
|
|
396
|
+
HyDAMO layer to be set
|
|
397
|
+
index_col : str, optional
|
|
398
|
+
Column to be used as index. The default is None.
|
|
399
|
+
check_columns : bool, optional
|
|
400
|
+
Check if all required columns are present in the GeoDataFrame.
|
|
401
|
+
The default is True.
|
|
402
|
+
check_geotype : bool, optional
|
|
403
|
+
Check if the geometry is of the required type. The default is True.
|
|
404
|
+
|
|
405
|
+
Returns
|
|
406
|
+
-------
|
|
407
|
+
None.
|
|
408
|
+
|
|
409
|
+
"""
|
|
410
|
+
|
|
411
|
+
getattr(self, layer).set_data(
|
|
412
|
+
gdf,
|
|
413
|
+
index_col=index_col,
|
|
414
|
+
check_columns=check_columns,
|
|
415
|
+
check_geotype=check_geotype,
|
|
416
|
+
extra_attributes={},
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
def to_geopackage(self, file_path, use_schema=True):
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
Parameters
|
|
423
|
+
----------
|
|
424
|
+
file_path : path-string
|
|
425
|
+
Path-string where the file should be written to
|
|
426
|
+
use_schema : bool, optional
|
|
427
|
+
Use the schema to specify column-properties The default is True.
|
|
428
|
+
|
|
429
|
+
Returns
|
|
430
|
+
-------
|
|
431
|
+
None.
|
|
432
|
+
|
|
433
|
+
"""
|
|
434
|
+
file_path = Path(file_path)
|
|
435
|
+
for layer in self.layers:
|
|
436
|
+
gdf = getattr(self, layer).copy()
|
|
437
|
+
if not gdf.empty:
|
|
438
|
+
if use_schema:
|
|
439
|
+
# match fiona layer schema keys with gdf.columns
|
|
440
|
+
schema = getattr(self, layer)._get_schema()
|
|
441
|
+
schema_cols = list(schema["properties"].keys()) + ["geometry"]
|
|
442
|
+
drop_cols = [i for i in gdf.columns if i not in schema_cols]
|
|
443
|
+
gdf.drop(columns=drop_cols, inplace=True)
|
|
444
|
+
|
|
445
|
+
schema["properties"] = {
|
|
446
|
+
k: v
|
|
447
|
+
for k, v in schema["properties"].items()
|
|
448
|
+
if k in gdf.columns
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
# write gdf to geopackage, including schema
|
|
452
|
+
if gdf.index.name in gdf.columns:
|
|
453
|
+
gdf.reset_index(drop=True, inplace=True)
|
|
454
|
+
gdf.to_file(
|
|
455
|
+
file_path,
|
|
456
|
+
layer=layer,
|
|
457
|
+
driver="GPKG",
|
|
458
|
+
engine="pyogrio",
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
# write gdf to geopackage as is
|
|
462
|
+
if gdf.index.name in gdf.columns:
|
|
463
|
+
gdf = gdf.reset_index(drop=True).copy()
|
|
464
|
+
gdf.to_file(file_path, layer=layer, driver="GPKG", engine="pyogrio")
|
|
465
|
+
if file_path.is_file():
|
|
466
|
+
add_styles_to_geopackage(file_path)
|
|
467
|
+
|
|
468
|
+
@classmethod
|
|
469
|
+
def from_geopackage(
|
|
470
|
+
cls, file_path, version="2.2", check_columns=True, check_geotype=True
|
|
471
|
+
):
|
|
472
|
+
"""
|
|
473
|
+
Initialize HyDAMO class from GeoPackage
|
|
474
|
+
|
|
475
|
+
Parameters
|
|
476
|
+
----------
|
|
477
|
+
file_path : path-string
|
|
478
|
+
Path-string to the hydamo GeoPackage
|
|
479
|
+
check_columns : bool, optional
|
|
480
|
+
Check if all required columns are present in the GeoDataFrame.
|
|
481
|
+
The default is True.
|
|
482
|
+
check_geotype : bool, optional
|
|
483
|
+
Check if the geometry is of the required type. The default is True.
|
|
484
|
+
|
|
485
|
+
Returns
|
|
486
|
+
-------
|
|
487
|
+
hydamo : HyDAMO
|
|
488
|
+
HyDAMO object initialized with content of GeoPackage
|
|
489
|
+
|
|
490
|
+
"""
|
|
491
|
+
hydamo = cls(version=version)
|
|
492
|
+
for layer in fiona.listlayers(file_path):
|
|
493
|
+
if layer in hydamo.layers:
|
|
494
|
+
hydamo_layer = getattr(hydamo, layer)
|
|
495
|
+
hydamo_layer.set_data(
|
|
496
|
+
gpd.read_file(file_path, layer=layer),
|
|
497
|
+
check_columns=check_columns,
|
|
498
|
+
check_geotype=check_geotype,
|
|
499
|
+
)
|
|
500
|
+
return hydamo
|