flood-adapt 0.3.1__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
- flood_adapt/__init__.py +1 -1
- flood_adapt/adapter/fiat_adapter.py +35 -1
- flood_adapt/config/config.py +58 -36
- flood_adapt/config/fiat.py +7 -7
- flood_adapt/config/gui.py +189 -82
- flood_adapt/config/site.py +2 -2
- flood_adapt/database_builder/database_builder.py +100 -65
- flood_adapt/dbs_classes/database.py +16 -53
- flood_adapt/dbs_classes/dbs_event.py +4 -1
- flood_adapt/dbs_classes/dbs_measure.py +7 -7
- flood_adapt/dbs_classes/dbs_projection.py +4 -1
- flood_adapt/dbs_classes/dbs_scenario.py +17 -8
- flood_adapt/dbs_classes/dbs_static.py +3 -5
- flood_adapt/dbs_classes/dbs_strategy.py +7 -5
- flood_adapt/dbs_classes/dbs_template.py +21 -22
- flood_adapt/dbs_classes/interface/database.py +0 -8
- flood_adapt/dbs_classes/interface/element.py +1 -1
- flood_adapt/flood_adapt.py +135 -273
- flood_adapt/objects/__init__.py +40 -17
- flood_adapt/objects/benefits/benefits.py +6 -6
- flood_adapt/objects/events/event_set.py +4 -4
- flood_adapt/objects/events/events.py +17 -4
- flood_adapt/objects/events/historical.py +11 -8
- flood_adapt/objects/events/hurricane.py +11 -8
- flood_adapt/objects/events/synthetic.py +9 -7
- flood_adapt/objects/forcing/forcing.py +9 -1
- flood_adapt/objects/forcing/plotting.py +1 -0
- flood_adapt/objects/forcing/tide_gauge.py +14 -14
- flood_adapt/objects/forcing/time_frame.py +13 -0
- flood_adapt/objects/measures/measures.py +38 -15
- flood_adapt/objects/object_model.py +2 -2
- flood_adapt/objects/projections/projections.py +18 -18
- flood_adapt/objects/strategies/strategies.py +22 -1
- flood_adapt/workflows/benefit_runner.py +5 -2
- flood_adapt/workflows/scenario_runner.py +8 -7
- flood_adapt-0.3.3.dist-info/LICENSE +674 -0
- flood_adapt-0.3.3.dist-info/METADATA +859 -0
- {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/RECORD +41 -41
- flood_adapt-0.3.1.dist-info/LICENSE +0 -21
- flood_adapt-0.3.1.dist-info/METADATA +0 -183
- /flood_adapt/database_builder/templates/{mapbox_layers → output_layers}/bin_colors.toml +0 -0
- {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/WHEEL +0 -0
- {flood_adapt-0.3.1.dist-info → flood_adapt-0.3.3.dist-info}/top_level.txt +0 -0
flood_adapt/__init__.py
CHANGED
|
@@ -319,9 +319,11 @@ class FiatAdapter(IImpactAdapter):
|
|
|
319
319
|
fiat_log = path / "fiat.log"
|
|
320
320
|
with cd(path):
|
|
321
321
|
with FloodAdaptLogging.to_file(file_path=fiat_log):
|
|
322
|
+
FiatAdapter._ensure_correct_hash_spacing_in_csv(path)
|
|
323
|
+
|
|
322
324
|
self.logger.info(f"Running FIAT in {path}")
|
|
323
325
|
process = subprocess.run(
|
|
324
|
-
|
|
326
|
+
args=[Path(exe_path).resolve().as_posix(), "run", "settings.toml"],
|
|
325
327
|
stdout=subprocess.PIPE,
|
|
326
328
|
stderr=subprocess.PIPE,
|
|
327
329
|
text=True,
|
|
@@ -1500,3 +1502,35 @@ class FiatAdapter(IImpactAdapter):
|
|
|
1500
1502
|
)
|
|
1501
1503
|
# Save as geopackage
|
|
1502
1504
|
roads.to_file(output_path, driver="GPKG")
|
|
1505
|
+
|
|
1506
|
+
@staticmethod
|
|
1507
|
+
def _ensure_correct_hash_spacing_in_csv(
|
|
1508
|
+
model_root: Path, hash_spacing: int = 1
|
|
1509
|
+
) -> None:
|
|
1510
|
+
"""
|
|
1511
|
+
Ensure that the CSV file has the correct number of spaces between hashes.
|
|
1512
|
+
|
|
1513
|
+
When writing csv files, FIAT does not add spaces between the hashes and the line, which leads to errors on linux.
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
Parameters
|
|
1517
|
+
----------
|
|
1518
|
+
file_path : Path
|
|
1519
|
+
The path to the model root.
|
|
1520
|
+
hash_spacing : int, optional
|
|
1521
|
+
The number of spaces between hashes, by default 1.
|
|
1522
|
+
"""
|
|
1523
|
+
for dirpath, _, filenames in os.walk(model_root):
|
|
1524
|
+
for filename in filenames:
|
|
1525
|
+
if not filename.lower().endswith(".csv"):
|
|
1526
|
+
continue
|
|
1527
|
+
file_path = os.path.join(dirpath, filename)
|
|
1528
|
+
|
|
1529
|
+
with open(file_path, "r") as file:
|
|
1530
|
+
lines = file.readlines()
|
|
1531
|
+
|
|
1532
|
+
with open(file_path, "w") as file:
|
|
1533
|
+
for line in lines:
|
|
1534
|
+
if line.startswith("#"):
|
|
1535
|
+
line = "#" + " " * hash_spacing + line.lstrip("#")
|
|
1536
|
+
file.write(line)
|
flood_adapt/config/config.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from os import environ, listdir
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from platform import system
|
|
4
|
-
from typing import ClassVar
|
|
5
4
|
|
|
6
5
|
import tomli
|
|
7
6
|
import tomli_w
|
|
@@ -13,6 +12,48 @@ from pydantic import (
|
|
|
13
12
|
)
|
|
14
13
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
15
14
|
|
|
15
|
+
DEFAULT_SYSTEM_FOLDER = Path(__file__).parents[1] / "system"
|
|
16
|
+
DEFAULT_EXE_PATHS: dict[str, dict[str, Path]] = {
|
|
17
|
+
"windows": {
|
|
18
|
+
"sfincs": DEFAULT_SYSTEM_FOLDER / "win-64" / "sfincs" / "sfincs.exe",
|
|
19
|
+
"fiat": DEFAULT_SYSTEM_FOLDER / "win-64" / "fiat" / "fiat.exe",
|
|
20
|
+
},
|
|
21
|
+
"linux": {
|
|
22
|
+
"sfincs": DEFAULT_SYSTEM_FOLDER / "linux-64" / "sfincs" / "bin" / "sfincs",
|
|
23
|
+
"fiat": DEFAULT_SYSTEM_FOLDER / "linux-64" / "fiat" / "fiat",
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _default_exe_path(exe_name: str) -> Path:
|
|
29
|
+
"""
|
|
30
|
+
Get the default path for the given executable name based on the system type.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
exe_name : str
|
|
35
|
+
The name of the executable (e.g., "sfincs", "fiat").
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
Path
|
|
40
|
+
The default path to the executable.
|
|
41
|
+
|
|
42
|
+
Raises
|
|
43
|
+
------
|
|
44
|
+
ValueError
|
|
45
|
+
If the system type is not recognized.
|
|
46
|
+
"""
|
|
47
|
+
if system().lower() not in DEFAULT_EXE_PATHS:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
f"System type '{system()}' is not recognized. Supported types are: {', '.join(DEFAULT_EXE_PATHS.keys())}."
|
|
50
|
+
)
|
|
51
|
+
if exe_name not in DEFAULT_EXE_PATHS[system().lower()]:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Executable name '{exe_name}' is not recognized. Supported names are: {', '.join(DEFAULT_EXE_PATHS[system().lower()].keys())}."
|
|
54
|
+
)
|
|
55
|
+
return DEFAULT_EXE_PATHS[system().lower()][exe_name]
|
|
56
|
+
|
|
16
57
|
|
|
17
58
|
class Settings(BaseSettings):
|
|
18
59
|
"""
|
|
@@ -64,12 +105,6 @@ class Settings(BaseSettings):
|
|
|
64
105
|
If required settings are missing or invalid.
|
|
65
106
|
"""
|
|
66
107
|
|
|
67
|
-
SYSTEM_SUFFIXES: ClassVar[dict[str, str]] = {
|
|
68
|
-
"Windows": ".exe",
|
|
69
|
-
"Linux": "",
|
|
70
|
-
"Darwin": "",
|
|
71
|
-
}
|
|
72
|
-
|
|
73
108
|
model_config = SettingsConfigDict(env_ignore_empty=True, validate_default=True)
|
|
74
109
|
|
|
75
110
|
database_root: Path = Field(
|
|
@@ -83,11 +118,7 @@ class Settings(BaseSettings):
|
|
|
83
118
|
default="",
|
|
84
119
|
description="The name of the database site, should be a folder inside the database root. The site must contain an 'input' and 'static' folder.",
|
|
85
120
|
)
|
|
86
|
-
|
|
87
|
-
alias="SYSTEM_FOLDER", # environment variable: SYSTEM_FOLDER
|
|
88
|
-
default=Path(__file__).parents[1] / "system",
|
|
89
|
-
description="The path of the system folder containing the kernels that run the calculations. Default is to look for the system folder in `FloodAdapt/flood_adapt/system`",
|
|
90
|
-
)
|
|
121
|
+
|
|
91
122
|
delete_crashed_runs: bool = Field(
|
|
92
123
|
alias="DELETE_CRASHED_RUNS", # environment variable: DELETE_CRASHED_RUNS
|
|
93
124
|
default=True,
|
|
@@ -101,15 +132,19 @@ class Settings(BaseSettings):
|
|
|
101
132
|
exclude=True,
|
|
102
133
|
)
|
|
103
134
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
135
|
+
sfincs_path: Path = Field(
|
|
136
|
+
default=_default_exe_path("sfincs"),
|
|
137
|
+
alias="SFINCS_BIN_PATH", # environment variable: SFINCS_BIN_PATH
|
|
138
|
+
description="The path of the sfincs binary.",
|
|
139
|
+
exclude=True,
|
|
140
|
+
)
|
|
108
141
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
142
|
+
fiat_path: Path = Field(
|
|
143
|
+
default=_default_exe_path("fiat"),
|
|
144
|
+
alias="FIAT_BIN_PATH", # environment variable: FIAT_BIN_PATH
|
|
145
|
+
description="The path of the fiat binary.",
|
|
146
|
+
exclude=True,
|
|
147
|
+
)
|
|
113
148
|
|
|
114
149
|
@computed_field
|
|
115
150
|
@property
|
|
@@ -119,7 +154,6 @@ class Settings(BaseSettings):
|
|
|
119
154
|
@model_validator(mode="after")
|
|
120
155
|
def validate_settings(self):
|
|
121
156
|
self._validate_database_path()
|
|
122
|
-
self._validate_system_folder()
|
|
123
157
|
self._validate_fiat_path()
|
|
124
158
|
self._validate_sfincs_path()
|
|
125
159
|
self._update_environment_variables()
|
|
@@ -128,7 +162,8 @@ class Settings(BaseSettings):
|
|
|
128
162
|
def _update_environment_variables(self):
|
|
129
163
|
environ["DATABASE_ROOT"] = str(self.database_root)
|
|
130
164
|
environ["DATABASE_NAME"] = self.database_name
|
|
131
|
-
environ["
|
|
165
|
+
environ["SFINCS_BIN_PATH"] = str(self.sfincs_path)
|
|
166
|
+
environ["FIAT_BIN_PATH"] = str(self.fiat_path)
|
|
132
167
|
environ["DELETE_CRASHED_RUNS"] = str(self.delete_crashed_runs)
|
|
133
168
|
environ["VALIDATE_ALLOWED_FORCINGS"] = str(self.validate_allowed_forcings)
|
|
134
169
|
return self
|
|
@@ -165,11 +200,6 @@ class Settings(BaseSettings):
|
|
|
165
200
|
|
|
166
201
|
return self
|
|
167
202
|
|
|
168
|
-
def _validate_system_folder(self):
|
|
169
|
-
if not self.system_folder.is_dir():
|
|
170
|
-
raise ValueError(f"System folder {self.system_folder} does not exist.")
|
|
171
|
-
return self
|
|
172
|
-
|
|
173
203
|
def _validate_sfincs_path(self):
|
|
174
204
|
if not self.sfincs_path.exists():
|
|
175
205
|
raise ValueError(f"SFINCS binary {self.sfincs_path} does not exist.")
|
|
@@ -180,18 +210,10 @@ class Settings(BaseSettings):
|
|
|
180
210
|
raise ValueError(f"FIAT binary {self.fiat_path} does not exist.")
|
|
181
211
|
return self
|
|
182
212
|
|
|
183
|
-
@field_serializer(
|
|
184
|
-
"database_root", "system_folder", "sfincs_path", "fiat_path", "database_path"
|
|
185
|
-
)
|
|
213
|
+
@field_serializer("database_root", "database_path")
|
|
186
214
|
def serialize_path(self, path: Path) -> str:
|
|
187
215
|
return str(path)
|
|
188
216
|
|
|
189
|
-
@staticmethod
|
|
190
|
-
def _system_extension() -> str:
|
|
191
|
-
if system() not in Settings.SYSTEM_SUFFIXES:
|
|
192
|
-
raise ValueError(f"Unsupported system {system()}")
|
|
193
|
-
return Settings.SYSTEM_SUFFIXES[system()]
|
|
194
|
-
|
|
195
217
|
@staticmethod
|
|
196
218
|
def read(toml_path: Path) -> "Settings":
|
|
197
219
|
"""
|
flood_adapt/config/fiat.py
CHANGED
|
@@ -198,18 +198,18 @@ class FiatModel(BaseModel):
|
|
|
198
198
|
|
|
199
199
|
Attributes
|
|
200
200
|
----------
|
|
201
|
-
risk : RiskModel
|
|
202
|
-
Configuration of probabilistic risk runs.
|
|
201
|
+
risk : Optional[RiskModel]
|
|
202
|
+
Configuration of probabilistic risk runs. default=None
|
|
203
203
|
config : FiatConfigModel
|
|
204
204
|
Configuration for the FIAT model.
|
|
205
|
-
benefits : BenefitsModel
|
|
206
|
-
Configuration for running benefit calculations.
|
|
205
|
+
benefits : Optional[BenefitsModel]
|
|
206
|
+
Configuration for running benefit calculations. default=None
|
|
207
207
|
"""
|
|
208
208
|
|
|
209
|
-
risk: RiskModel
|
|
210
|
-
|
|
211
209
|
config: FiatConfigModel
|
|
212
|
-
|
|
210
|
+
|
|
211
|
+
benefits: Optional[BenefitsModel] = None
|
|
212
|
+
risk: Optional[RiskModel] = None
|
|
213
213
|
|
|
214
214
|
@staticmethod
|
|
215
215
|
def read_toml(path: Path) -> "FiatModel":
|
flood_adapt/config/gui.py
CHANGED
|
@@ -1,103 +1,210 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import geopandas as gpd
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import tomli
|
|
8
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
9
|
|
|
7
10
|
from flood_adapt.config.fiat import DamageType
|
|
8
11
|
from flood_adapt.objects.forcing import unit_system as us
|
|
9
12
|
|
|
10
13
|
|
|
11
|
-
class
|
|
14
|
+
class Layer(BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
Base class for layers in the GUI.
|
|
17
|
+
|
|
18
|
+
Attributes
|
|
19
|
+
----------
|
|
20
|
+
bins : list[float]
|
|
21
|
+
The bins for the layer.
|
|
22
|
+
colors : list[str]
|
|
23
|
+
The colors for the layer.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
bins: list[float]
|
|
27
|
+
colors: list[str]
|
|
28
|
+
|
|
29
|
+
@model_validator(mode="after")
|
|
30
|
+
def check_bins_and_colors(self) -> "Layer":
|
|
31
|
+
"""Check that the bins and colors have the same length."""
|
|
32
|
+
if (len(self.bins) + 1) != len(self.colors):
|
|
33
|
+
raise ValueError(
|
|
34
|
+
f"Number of bins ({len(self.bins)}) must be one less than number of colors ({len(self.colors)})"
|
|
35
|
+
)
|
|
36
|
+
return self
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FloodMapLayer(Layer):
|
|
40
|
+
zbmax: float
|
|
41
|
+
depth_min: float
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class AggregationDmgLayer(Layer):
|
|
45
|
+
damage_decimals: Optional[int] = 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class FootprintsDmgLayer(Layer):
|
|
49
|
+
type: DamageType = DamageType.absolute
|
|
50
|
+
damage_decimals: Optional[int] = 0
|
|
51
|
+
buildings_min_zoom_level: int = 13
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class BenefitsLayer(Layer):
|
|
55
|
+
threshold: Optional[float] = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class OutputLayers(BaseModel):
|
|
12
59
|
"""The configuration of the mapbox layers in the gui.
|
|
13
60
|
|
|
14
61
|
Attributes
|
|
15
62
|
----------
|
|
16
|
-
|
|
17
|
-
The
|
|
18
|
-
|
|
19
|
-
The
|
|
20
|
-
|
|
21
|
-
The
|
|
22
|
-
flood_map_bins : list[float]
|
|
23
|
-
The bins for the flood map layer.
|
|
24
|
-
flood_map_colors : list[str]
|
|
25
|
-
The colors for the flood map layer.
|
|
26
|
-
aggregation_dmg_bins : list[float]
|
|
27
|
-
The bins for the aggregation damage layer.
|
|
28
|
-
aggregation_dmg_colors : list[str]
|
|
29
|
-
The colors for the aggregation damage layer.
|
|
30
|
-
footprints_dmg_type : DamageType
|
|
31
|
-
The type of damage for the footprints layer.
|
|
32
|
-
footprints_dmg_bins : list[float]
|
|
33
|
-
The bins for the footprints layer.
|
|
34
|
-
footprints_dmg_colors : list[str]
|
|
35
|
-
The colors for the footprints layer.
|
|
36
|
-
svi_bins : Optional[list[float]]
|
|
37
|
-
The bins for the SVI layer.
|
|
38
|
-
svi_colors : Optional[list[str]]
|
|
39
|
-
The colors for the SVI layer.
|
|
40
|
-
benefits_bins : list[float]
|
|
41
|
-
The bins for the benefits layer.
|
|
42
|
-
benefits_colors : list[str]
|
|
43
|
-
The colors for the benefits layer.
|
|
44
|
-
benefits_threshold : Optional[float], default=None
|
|
45
|
-
The threshold for the benefits layer.
|
|
46
|
-
damage_decimals : Optional[int], default=0
|
|
47
|
-
The number of decimals for the damage layer.
|
|
63
|
+
floodmap : FloodMapLayer
|
|
64
|
+
The configuration of the floodmap layer.
|
|
65
|
+
aggregation_dmg : AggregationDmgLayer
|
|
66
|
+
The configuration of the aggregation damage layer.
|
|
67
|
+
footprints_dmg : FootprintsDmgLayer
|
|
68
|
+
The configuration of the footprints damage layer.
|
|
48
69
|
|
|
70
|
+
benefits : BenefitsLayer
|
|
71
|
+
The configuration of the benefits layer.
|
|
49
72
|
"""
|
|
50
73
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
aggregation_dmg_bins: list[float]
|
|
57
|
-
aggregation_dmg_colors: list[str]
|
|
58
|
-
footprints_dmg_type: DamageType = DamageType.absolute
|
|
59
|
-
footprints_dmg_bins: list[float]
|
|
60
|
-
footprints_dmg_colors: list[str]
|
|
61
|
-
svi_bins: Optional[list[float]] = Field(default_factory=list)
|
|
62
|
-
svi_colors: Optional[list[str]] = Field(default_factory=list)
|
|
63
|
-
benefits_bins: list[float]
|
|
64
|
-
benefits_colors: list[str]
|
|
65
|
-
benefits_threshold: Optional[float] = None
|
|
66
|
-
damage_decimals: Optional[int] = 0
|
|
74
|
+
floodmap: FloodMapLayer
|
|
75
|
+
aggregation_dmg: AggregationDmgLayer
|
|
76
|
+
footprints_dmg: FootprintsDmgLayer
|
|
77
|
+
|
|
78
|
+
benefits: Optional[BenefitsLayer] = None
|
|
67
79
|
|
|
68
80
|
|
|
69
|
-
class
|
|
81
|
+
class VisualizationLayer(Layer):
|
|
82
|
+
"""The configuration of a layer to visualize in the gui.
|
|
83
|
+
|
|
84
|
+
name : str
|
|
85
|
+
The name of the layer to visualize.
|
|
86
|
+
long_name : str
|
|
87
|
+
The long name of the layer to visualize.
|
|
88
|
+
path : str
|
|
89
|
+
The path to the layer data to visualize.
|
|
90
|
+
field_name : str
|
|
91
|
+
The field names of the layer to visualize.
|
|
92
|
+
decimals : Optional[int]
|
|
93
|
+
The number of decimals to use for the layer to visualize. default is None.
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
name: str
|
|
97
|
+
long_name: str
|
|
98
|
+
path: str
|
|
99
|
+
field_name: str
|
|
100
|
+
decimals: Optional[int] = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
_DEFAULT_BIN_NR = 4
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def interpolate_hex_colors(
|
|
107
|
+
start_hex="#FFFFFF", end_hex="#860000", number_bins=_DEFAULT_BIN_NR
|
|
108
|
+
):
|
|
109
|
+
"""
|
|
110
|
+
Interpolate between two hex colors and returns a list of number_bins hex color codes.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
start_hex : str
|
|
115
|
+
Starting color in hex format (e.g., "#FFFFFF").
|
|
116
|
+
end_hex : str
|
|
117
|
+
Ending color in hex format (e.g., "#000000").
|
|
118
|
+
number_bins : int
|
|
119
|
+
Number of colors to generate between the start and end colors.
|
|
120
|
+
|
|
121
|
+
Returns
|
|
122
|
+
-------
|
|
123
|
+
list[str]
|
|
124
|
+
List of hex color codes interpolated between the start and end colors.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
def hex_to_rgb(hex_color):
|
|
128
|
+
hex_color = hex_color.lstrip("#")
|
|
129
|
+
return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4))
|
|
130
|
+
|
|
131
|
+
def rgb_to_hex(rgb_color):
|
|
132
|
+
return "#{:02X}{:02X}{:02X}".format(*rgb_color)
|
|
133
|
+
|
|
134
|
+
start_rgb = hex_to_rgb(start_hex)
|
|
135
|
+
end_rgb = hex_to_rgb(end_hex)
|
|
136
|
+
|
|
137
|
+
interpolated_colors = []
|
|
138
|
+
for i in range(number_bins):
|
|
139
|
+
ratio = i / (number_bins - 1) if number_bins > 1 else 0
|
|
140
|
+
interpolated_rgb = tuple(
|
|
141
|
+
int(start + (end - start) * ratio) for start, end in zip(start_rgb, end_rgb)
|
|
142
|
+
)
|
|
143
|
+
interpolated_colors.append(rgb_to_hex(interpolated_rgb))
|
|
144
|
+
|
|
145
|
+
return interpolated_colors
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class VisualizationLayers(BaseModel):
|
|
70
149
|
"""The configuration of the layers you might want to visualize in the gui.
|
|
71
150
|
|
|
72
151
|
Attributes
|
|
73
152
|
----------
|
|
74
|
-
|
|
75
|
-
The default
|
|
76
|
-
|
|
77
|
-
The
|
|
78
|
-
layer_names : list[str]
|
|
79
|
-
The names of the layers to visualize.
|
|
80
|
-
layer_long_names : list[str]
|
|
81
|
-
The long names of the layers to visualize.
|
|
82
|
-
layer_paths : list[str]
|
|
83
|
-
The paths to the layers to visualize.
|
|
84
|
-
field_names : list[str]
|
|
85
|
-
The field names of the layers to visualize.
|
|
86
|
-
bins : Optional[list[list[float]]]
|
|
87
|
-
The bins for the layers to visualize.
|
|
88
|
-
colors : Optional[list[list[str]]]
|
|
89
|
-
The colors for the layers to visualize.
|
|
153
|
+
default : Layer
|
|
154
|
+
The default layer settings the visualization layers.
|
|
155
|
+
layers : list[VisualizationLayer]
|
|
156
|
+
The layers to visualize.
|
|
90
157
|
"""
|
|
91
158
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
159
|
+
layers: list[VisualizationLayer] = Field(default_factory=list)
|
|
160
|
+
|
|
161
|
+
def add_layer(
|
|
162
|
+
self,
|
|
163
|
+
name: str,
|
|
164
|
+
long_name: str,
|
|
165
|
+
path: str,
|
|
166
|
+
field_name: str,
|
|
167
|
+
database_path: Path,
|
|
168
|
+
decimals: Optional[int] = None,
|
|
169
|
+
bins: Optional[list[float]] = None,
|
|
170
|
+
colors: Optional[list[str]] = None,
|
|
171
|
+
) -> None:
|
|
172
|
+
if not Path(path).is_absolute():
|
|
173
|
+
raise ValueError(f"Path {path} must be absolute.")
|
|
174
|
+
|
|
175
|
+
data = gpd.read_file(path)
|
|
176
|
+
if field_name not in data.columns:
|
|
177
|
+
raise ValueError(
|
|
178
|
+
f"Field name {field_name} not found in data. Available fields: {data.columns.tolist()}"
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if bins is None:
|
|
182
|
+
_, _bins = pd.qcut(
|
|
183
|
+
data[field_name], _DEFAULT_BIN_NR, retbins=True, duplicates="drop"
|
|
184
|
+
)
|
|
185
|
+
bins = _bins.tolist()[1:-1]
|
|
186
|
+
|
|
187
|
+
if decimals is None:
|
|
188
|
+
non_zero_bins = [abs(b) for b in bins if b != 0]
|
|
189
|
+
min_non_zero = min(non_zero_bins) if non_zero_bins else 1
|
|
190
|
+
decimals = max(int(-np.floor(np.log10(min_non_zero))), 0)
|
|
191
|
+
|
|
192
|
+
if colors is None:
|
|
193
|
+
nr_bins = len(bins) + 1
|
|
194
|
+
colors = interpolate_hex_colors(number_bins=nr_bins)
|
|
195
|
+
|
|
196
|
+
relative_path = Path(path).relative_to(database_path / "static")
|
|
197
|
+
self.layers.append(
|
|
198
|
+
VisualizationLayer(
|
|
199
|
+
bins=bins,
|
|
200
|
+
colors=colors,
|
|
201
|
+
name=name,
|
|
202
|
+
long_name=long_name,
|
|
203
|
+
path=relative_path.as_posix(),
|
|
204
|
+
field_name=field_name,
|
|
205
|
+
decimals=decimals,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
101
208
|
|
|
102
209
|
|
|
103
210
|
class GuiUnitModel(BaseModel):
|
|
@@ -203,22 +310,22 @@ class GuiModel(BaseModel):
|
|
|
203
310
|
----------
|
|
204
311
|
units : GuiUnitModel
|
|
205
312
|
The unit system used in the GUI.
|
|
206
|
-
|
|
313
|
+
output_layers : OutputLayers
|
|
207
314
|
The configuration of the mapbox layers in the GUI.
|
|
208
|
-
visualization_layers :
|
|
315
|
+
visualization_layers : VisualizationLayers
|
|
209
316
|
The configuration of the visualization layers in the GUI.
|
|
210
317
|
plotting : PlottingModel
|
|
211
318
|
The configuration for creating hazard forcing plots.
|
|
212
319
|
"""
|
|
213
320
|
|
|
214
321
|
units: GuiUnitModel
|
|
215
|
-
|
|
216
|
-
visualization_layers:
|
|
322
|
+
output_layers: OutputLayers
|
|
323
|
+
visualization_layers: VisualizationLayers
|
|
217
324
|
plotting: PlottingModel
|
|
218
325
|
|
|
219
326
|
@staticmethod
|
|
220
327
|
def read_toml(path: Path) -> "GuiModel":
|
|
221
328
|
with open(path, mode="rb") as fp:
|
|
222
|
-
toml_contents =
|
|
329
|
+
toml_contents = tomli.load(fp)
|
|
223
330
|
|
|
224
331
|
return GuiModel(**toml_contents)
|
flood_adapt/config/site.py
CHANGED